NIO—NonBlocking IO(new IO)
- io flow oriented programming can only be used as one of the input or output flows. It is synchronous blocking. Every connection needs to create a thread to handle. The thread context switching overhead is very large, which creates a big bottleneck
- So the pseudo blocking IO implemented by thread pool solves the problem of too many threads to some extent, but does not fundamentally solve the problem of blocking. Moreover, too many threads and too little thread pool will also cause a big bottleneck
- Since the root cause of the bottleneck is the number of threads and blocking IO, is there any way for us to handle multiple client connections with only one thread? That's why NIO came out
NIO mainly consists of three core parts:
- Buffer buffer
- Channel pipeline
- Selector
nio is for block block and buffer buffer programming. The bottom layer is array. Buffer provides data access. Channel reads and writes to buffer and buffer reads and writes to channel. The channel from buffer to program is bidirectional
Understanding NIO requires understanding the event programming model
NIO core:
NIO changes from blocking read and write (occupying thread) to single thread polling event, and finds the network descriptor that can be read and written to read and write. Except that event polling is blocked (nothing to do must be blocked), the remaining I/O operations are pure CPU operations, and there is no need to start multithreading.
The efficiency of single thread processing I/O is really very high. There is no thread switching, just desperately reading, writing and selecting events.
NIO brings us:
- Event driven model -- asynchronous programming is inseparable from events
- Single thread processing multi connection multiplexing makes processing more efficient
- Non blocking IO, only blocking to get operable events
- block based transport is more efficient than stream based transport
- Zero copy - DirectBuffer
Disadvantages:
NIO does not completely shield the platform differences. It is still based on the I/O system of each operating system, and the differences still exist. It is not easy to build event driven model by NIO.
NIO mature framework Netty is recommended
Buffer
A buffer is essentially a block of memory from which data can be written and then read. This memory is wrapped as NIO Buffer object, and a set of methods are provided to access the block memory conveniently.
Capacity,Position,Limit
0 <= mark <= position <= limit <= capacity
- capacity
As a memory block, Buffer has a fixed size value, also known as "capacity". You can only write capacity byte s, long, char and other types. Once the Buffer is full, it needs to be emptied (by reading or clearing data) to continue writing data into it.
- position
When you write data to Buffer, position represents the current location. The initial position value is 0. When a byte, long and other data is written to Buffer, the position will move forward to the next Buffer unit that can insert data. The maximum position can be capacity – 1
When reading data, it is also read from a specific location. When the Buffer is switched from write mode to read mode, the position will be reset to 0. When the data is read from the Buffer's position, the position will move forward to the next readable position.
- limit
In write mode, the Buffer limit indicates how much data you can write to the Buffer at most In write mode, limit is equal to Buffer's capacity.
When you switch Buffer to read mode, limit indicates how much data you can read at most. Therefore, when switching Buffer to read mode, limit will be set to the position value in write mode. In other words, you can read all the previously written data (limit is set to the number of written data, which is position in write mode)
The same buffer can store data of different data types, but when obtaining, you need to specify the type to obtain
ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.putInt(1); buffer.putLong(387524875628742L); buffer.putChar('s'); buffer.flip(); System.out.println(buffer.getInt()); System.out.println(buffer.getLong()); System.out.println(buffer.getChar());
Put method can only put byte type, not int
flip,clear,rewind,mark
- flip
The flip method switches the Buffer from write mode to read mode. Calling the flip() method sets the position back to 0 and sets the limit to the value of the previous position.
public final Buffer flip() { this.limit = this.position; this.position = 0; this.mark = -1; return this; }
- clear
position will be set back to 0, and limit will be set to the value of capacity. In other words, the Buffer is cleared. The data in Buffer is not cleared, but these marks tell us where to start writing data to Buffer.
public final Buffer clear() { this.position = 0; this.limit = this.capacity; this.mark = -1; return this; }
- rewind
Buffer.rewind() sets the position back to 0, so you can reread all the data in the buffer. limit remains unchanged, which still indicates how many elements can be read from the buffer
public final Buffer rewind() { this.position = 0; this.mark = -1; return this; }
- mark
You can mark a specific position in the Buffer. You can then recover to this position by calling the Buffer.reset() method.
public final Buffer mark() { this.mark = this.position; return this; }
- slice fragmentation
Divide the buffer into one buffer according to the set position and limit, with its own position, limit and capacity. The data share the buffer data of one memory address
public static void test2(){ ByteBuffer buffer = ByteBuffer.allocate(1024); for(int i=0;i<buffer.capacity();i++){ buffer.put((byte)i); } buffer.position(10); buffer.limit(20); ByteBuffer buffer1 = buffer.slice();//buffer fragmentation for(int m=0;m<buffer1.capacity();m++){ byte b = buffer1.get(); System.out.print(b+" "); } } //Output: 10 11 12 13 14 15 16 17 18 19
ReadOnlyBuffer
Normal Buffer (readable and writable) can be converted to read-only Buffer at any time, but read-only Buffer cannot be converted to normal Buffer
ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
The transformed Buffer is a new read-only Buffer with independent position, limit and capacity
DirectBuffer
External memory buffer, local JNI non JVM heap memory buffer, allowing direct access
Normal ByteBuffer is managed by the JVM, which allocates memory on the JVM heap
ByteBuffer buf = ByteBuffer.allocate(1024);
DirectBuffer will be allocated in local memory, which will be separated from the heap management of JVM
ByteBuffer buf = ByteBuffer.allocateDirect(1024);
Why do you want to do this?
----------GC again--------
We all know that in the old generation of JVM on the heap, GC will adopt the mark grooming strategy, which will change the address of the object in the heap memory, and it will be difficult for GC to groom when the buffer is too large
So DirectBuffer, which uses unsafe.allocateMemory to allocate memory, is a native method. The address variable of buffer records the address of this memory to provide access
compare
- DirectBuffer: the memory allocated by local method is obviously not as fast as that allocated by JVM heap, but DirectBuffer is faster when it involves IO and network io
DirectByteBuffer inherits MappedByteBuffer
The use of cache can use DirectByteBuffer and HeapByteBuffer. If DirectByteBuffer is used, the copy of system space to user space can be reduced once generally.
In the case of small and medium-sized applications with small amount of data, heapBuffer can be used; otherwise, directBuffer can be used
MappedByteBuffer
ByteBuffer mapped to out of heap memory, DirectByteBuffer inherits this class to realize out of heap memory allocation
Map buffer to out of heap memory as follows
MappedByteBuffer mappedByteBuffer = channel.map(MapMode.READ_WRITE, 0, channel.size());
Use copy file:
RandomAccessFile in = new RandomAccessFile("nio/1.txt", "rw"); RandomAccessFile out = new RandomAccessFile("nio/2.txt", "rw"); FileChannel inChannel = in.getChannel(); FileChannel outChannel = out.getChannel(); MappedByteBuffer inputData = inChannel.map(FileChannel.MapMode.READ_ONLY,0,new File("nio/1.txt").length()); Charset charset = Charset.forName("utf-8");//Code CharsetDecoder decoder = charset.newDecoder(); CharsetEncoder encoder = charset.newEncoder(); CharBuffer charBuffer = decoder.decode(inputData); ByteBuffer buffer = encoder.encode(charBuffer); outChannel.write(buffer); in.close();out.close();
Channel - channel
FileChannel
NIO provides a channel to connect to files for reading and writing files
When using filechannel, you need to obtain the filechannel from the I / O stream or RandomAccessFile
- If you want to read data to FileChannel, you need to apply for a ByteBuffer, read the data from FileChannel to the ByteBuffer, and read() returns how many bytes are read. If you return - 1, the file has reached the end
- If you want to write data to FileChannel, you need to first write the data to ByteBuffer, and then call the write() method when writing to FileChannel from ByteBuffer
Note that Buffer.flip() is required between read and write;
Example:
1. Read file data and print
FileInputStream fileInputStream = new FileInputStream("1.log"); FileChannel channel = fileInputStream.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(512);; channel.read(byteBuffer); byteBuffer.flip(); while(byteBuffer.remaining()>0){ byte b = byteBuffer.get(); System.out.println((char) b); } fileInputStream.close();
2. Write 1.txt data to 2.txt
FileInputStream inputStream = new FileInputStream("1.txt"); FileChannel in = inputStream.getChannel(); FileOutputStream outputStream = new FileOutputStream("2.txt"); FileChannel out = outputStream.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); while(true){ byteBuffer.clear();//If not, it will be read all the time int read = in.read(byteBuffer); System.out.println("read:"+read); if(read==-1){ break;//For - 1 means end of file return } byteBuffer.flip(); out.write(byteBuffer); } inputStream.close(); outputStream.close();
ServerSockerChannel
NIO provides a channel that can listen to new TCP connections, that is, ServerSocketChannel, corresponding to ServerSocket in IO
- Open monitoring channel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(9999)); while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); //do something with socketChannel... }
SocketChannel
A channel provided by NIO to connect to TCP Socket is SocketChannel, which corresponds to Socket in IO
- Open a SocketChannel
SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("localhost", 8080));
Channel reading and writing
- read
ByteBuffer buf = ByteBuffer.allocate(48); int bytesRead = socketChannel.read(buf);
- write
ByteBuffer writeBuffer = ByteBuffer.allocate(48); String msg = "hello"; writeBuffer.put(msg.getBytes()); writeBuffer.flip(); channel.write(writeBuffer);
- Read and write
ByteBuffer buffer = ByteBuffer.allocate(1024); int byteRead = channel.read(buffer); if(byteRead<=0){ channel.close(); break; } buffer.flip(); channel.write(buffer); read += byteRead; buffer.clear();
After each write, if the buffer data does not need to be used again, it is recommended that clear clear the buffer and prepare for the next write operation
Selector - multiplexer (selector)
Multiplexer, the name is very vivid. It uses one thread to process multiple channels, so as to manage multiple channels
Why use one thread to manage multiple channel s?
Thread context switching costs a lot, and the fewer threads, the more efficient channel processing
Create Selector - create competition
Selector selector = Selector.open();
Register channel - buy admission
Channel registers the channel events to the selector for management and returns a SelectionKey
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
- When used with a Selector, the Channel must be in non blocking mode. This means that FileChannel cannot be used with Selector because FileChannel cannot be switched to non blocking mode
channel.configureBlocking(false);
- Get channel and selector and prepared events through SelectionKey
Channel channel = selectionKey.channel(); Selector selector = selectionKey.selector();
Selector performs selection - enter with ticket
After registering the channel with the Selector, we can use the Selector.select(); method to get the ready channel and return an int integer, indicating the ready channel number
Get the ready SelectionKey through the selector.selectedKeys(); method, and then get the channel and selector through the SelectionKey. Generally, iterators are used to traverse these prepared channels
Every time a SelectionKey is processed, it must be deleted from the iterator. After processing, the SelectionKey is useless. It is like an admission ticket. You can enter the arena through it and it has the corresponding information of the entrant and seat. After the game, you can no longer perform any effective operation through it.
- After watching the competition, the organizer will not recycle all the tickets. You need to deal with them by yourself. You can't leave them in the field. You need to put them in the trash can or take them home
iterator.remove();
- wakeUp() method
A thread is blocked after calling the select() method. Even if no channel is ready, it cannot return. The wakeUp method makes it return immediately.
Scatter,Gather
scatter / gather is often used in situations where the data to be transmitted needs to be processed separately. For example, to transmit a message composed of a message header and a message body, you may spread the message body and the message header into different buffer s, so that you can easily process the message header and the message body.
Scatter
scatter reading from the Channel refers to writing the read data to multiple buffers during the read operation. Therefore, the Channel "scatters" the data read from the Channel into multiple buffers.
Gather
gather write Channel refers to writing the data of multiple buffers to the same Channel during write operation. Therefore, the Channel "gathers" the data in multiple buffers and sends it to the Channel.
For example, three buffers of length 3,4,5 are used to store the input string, the first three characters are stored in the first buffer, the 4-7 characters are stored in the second buffer, the length is 4,8-12 is stored in the third buffer, and the length is 5
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); InetSocketAddress inetSocketAddress = new InetSocketAddress(8899); serverSocketChannel.socket().bind(inetSocketAddress); int messageLength = 3 + 4 + 5; ByteBuffer[] byteBuffer = new ByteBuffer[3]; byteBuffer[0] = ByteBuffer.allocate(3); byteBuffer[1] = ByteBuffer.allocate(4); byteBuffer[2] = ByteBuffer.allocate(5); SocketChannel socketChannel = serverSocketChannel.accept(); while (true){ int byteRead = 0; while (byteRead<messageLength){ long r = socketChannel.read(byteBuffer); byteRead += r; System.out.println("byteread:"+byteRead); Arrays.stream(byteBuffer).map(o->"position:"+o.position()+",limit:"+o.limit()).forEach(System.out::println); } Arrays.stream(byteBuffer).forEach(Buffer::flip); int byteWrite = 0; while(byteWrite<messageLength){ long r = socketChannel.write(byteBuffer); byteWrite += r; System.out.println("bytewrite:"+byteWrite); Arrays.stream(byteBuffer).map(o->"position:"+o.position()+",limit:"+o.limit()).forEach(System.out::println); } Arrays.stream(byteBuffer).forEach(Buffer::clear); } //Test: test with linux nc localhost 8899 //Input: helloworld enter //Output: byteread:11 position:3,limit:3 position:4,limit:4 position:4,limit:5 //Interpretation: //Press enter to calculate 11 characters in total for one character. The first three characters are stored in the first buffer, which is full; the middle four characters are stored in the second buffer, which is full; the rest are stored in the third buffer, which is not full
NIO server client
This program demonstrates the use of NIO to create a chat room. The server connects with multiple clients, and the clients can send messages to each other.
- Server server
/** * You can use the linux nc command as the client directly * nc localhost port */ public class Server { private static Map<SocketChannel,String> clientMap = new HashMap<>(); public static void main(String[] args) throws IOException { //Open server channel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //Set non blocking to use selector serverSocketChannel.configureBlocking(false); //Get the socket of the server ServerSocket serverSocket = serverSocketChannel.socket(); //Binding port serverSocket.bind(new InetSocketAddress(8089)); //Open a multiplexer and use a thread to process the client channel Selector selector = Selector.open(); //Register server channel to serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true){ //Block get channel events //Once the select() method is called, and the return value indicates that one or more channels are ready int num = selector.select(); /** * Get the SelectionKey core method channel of the multiplexer to get the registered channel * SelectionKey Every time a channel is registered, a SelectionKey will be created in which the constant defines the channel state **/ Set<SelectionKey> selectionKeys = selector.selectedKeys(); //Operate on each of the selectionkeys selectionKeys.forEach(selectionKey->{ try { //If the server SelectionKey is received if(selectionKey.isAcceptable()){ //Get the server channel ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel(); SocketChannel client = null; //Get the client connected to the server this time client = server.accept(); client.configureBlocking(false); //Register the client to the multiplexer and listen to the client's readable events client.register(selector,SelectionKey.OP_READ); //Assign an id to each client String key = "["+ UUID.randomUUID()+"]"; clientMap.put(client,key); //If the SelectionKey is ready to read, perform the read operation }else if(selectionKey.isReadable()){ //Get the channel SocketChannel channel = (SocketChannel) selectionKey.channel(); //Create read buffer ByteBuffer readBuffer = ByteBuffer.allocate(1024); //Read data in channel to read buffer int read = channel.read(readBuffer); String reMsg = ""; //If there is data if(read>0){ //Flip to write readBuffer.flip(); //Develop the decoding set utf-8 to decode and print the read buffer Charset charset = Charset.forName("utf-8"); reMsg = String.valueOf(charset.decode(readBuffer).array()); System.out.println(clientMap.get(channel)+" receive: "+reMsg); }else if(read==-1) channel.close();//Close the client channel if the client is closed //Mass sending: sending data to other client channel s for(SocketChannel ch:clientMap.keySet()){ if(ch!=channel) { String key = clientMap.get(ch); ByteBuffer writeBuffer = ByteBuffer.allocate(1024); writeBuffer.put(("Come from"+key + ":" + reMsg).getBytes()); writeBuffer.flip(); ch.write(writeBuffer); } } } } catch (IOException e) { e.printStackTrace(); } }); selectionKeys.clear();//Delete the SelectionKey every time the SelectionKey event is processed } } }
- Client
public class Client { public static void main(String[] args) throws IOException { //Open client channel SocketChannel socketChannel = SocketChannel.open(); //Set to non blocking mode, which can be used with selector socketChannel.configureBlocking(false); //Open selector Selector selector = Selector.open(); //Register client channel to multiplexer and listen for connection events socketChannel.register(selector, SelectionKey.OP_CONNECT); //Connect to the specified address socketChannel.connect(new InetSocketAddress("localhost",8089)); while (true){ try{ //Execute the selector method to block the triggering of channel events int num = selector.select(); //Get the SelectionKey registered on the multiplexer Set<SelectionKey> selectionKeys = selector.selectedKeys(); //Traversing SelectionKey by iterator Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); //If the event triggered by SelectionKey is connection ready if(selectionKey.isConnectable()){ //Get the client channel of SelectionKey SocketChannel client = (SocketChannel) selectionKey.channel(); if(client.isConnectionPending()){ //Completing the connection client.finishConnect(); //Create a new write buffer ByteBuffer writeBuffer = ByteBuffer.allocate(1024); //Write client connection success message writeBuffer.put((client.toString()+":Successful connection!").getBytes()); //Flip read / write to perform write writeBuffer.flip(); //Write buffer data to client client.write(writeBuffer); //Open a thread to write, because the standard input is blocked, the current thread cannot block the write ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.submit(()->{ while (true){ writeBuffer.clear(); InputStreamReader reader = new InputStreamReader(System.in); BufferedReader br = new BufferedReader(reader); String msg = br.readLine(); //Read one line at a time, write data to buffer and write to client channel writeBuffer.put(msg.getBytes()); writeBuffer.flip(); client.write(writeBuffer); } }); } //Register client readable events to multiplexer client.register(selector,SelectionKey.OP_READ); //If the SelectionKey on the multiplexer is read ready }else if(selectionKey.isReadable()){ //Get the SelectionKey, trigger the corresponding client channel of the corresponding event, and execute the read operation SocketChannel client = (SocketChannel) selectionKey.channel(); //Create a new read buffer, ByteBuffer readBuffer = ByteBuffer.allocate(1024); //Reading data from a channel ready for a read operation int count = client.read(readBuffer); if (count>0){ //Transcode and use String to save and print data String reMsg = new String(readBuffer.array(),0,count); System.out.println(reMsg); }else if(count==-1) client.close();//Close client } } selectionKeys.clear();//Delete the SelectionKey every time the SelectionKey event is processed } catch (Exception e){ e.printStackTrace(); } } } }
- test
1. Create a server and three clients
2. Client 1, 2 and 3 send data respectively
The server gets the connection information and three clients send the information
Client 1 is created first to get 2,3 connection information and 2,3 sending information
Client 2 is created before 3, and gets 3 connection information and 1,3 sending information
The client 3 is created finally, and can only get 1 and 2 messages
3. At this time, use nc command to create a client
Send message, client can receive
Client 2 sends information, and the terminal client can also receive
NIO case - data transmission across Ports - MultiServer
Realization goal: the server listens to two ports, one 8089 and one 80908089. There is only one main client A connection, and there are multiple client B connections in 8090. Client A receives messages sent by multiple client B connections, and realizes message forwarding across ports
- Server side
Let's first look at the server. The server first needs to listen to two ports. We create two server channel s. The server receives the connection and listens to the sending data event of client B (that is, the writable server readable event of client b). After receiving the message of client B, send it to client A
How does the server send data to client A?
Save A set of client channels, assign the end part of different id to clients of different ports, client A is assigned as wxq, client B is assigned as gzh], save to HashMap when their channel is created, channel as key, id as value
Let's talk about the server process:
- Create two server channel s and bind different ports
- Create a multiplexer selector, register the two servers on the selector, and listen for the acceptable event
-
Execute the selector.select() method, get the SelectionKey collection, and handle different events continuously
- If the event is ready for receiving, get the server channel through the SelectionKey.channel() method, and register different listening events according to different ports. If it is 8090, it means that the connection of client B is completed, get the channel of client B, listen to its readable events, assign id suffix gzh] and save it. If it is 8089, it means that it is the client The connection of terminal A is completed. The channel of client a listens for its writable events, assigns the id suffix to wxq, and saves it to hashmap
- If the event is read ready, it means that after client B has finished writing the data, it can read the data of client B and perform reading; first, read and write the data to readBuffer, use new String(readBuffer.array() to create the msg to be sent, traverse the key of the client channel, if the suffix is wxq), it means that client A, write the data to writeBuffer, and put the Write data to channel of client A
- Delete the SelectionKey every time the event of SelectionKey is completed
Code:
public class Server { private static int CAPACITY = 1024; private static ByteBuffer readBuffer = ByteBuffer.allocate(CAPACITY); private static ByteBuffer writeBuffer = ByteBuffer.allocate(CAPACITY); private static Map<SocketChannel,String> clientMap = new HashMap<>(); public static void main(String[] args) throws IOException { ServerSocketChannel serverSocketChannelWxq = ServerSocketChannel.open(); ServerSocketChannel serverSocketChannelGzh = ServerSocketChannel.open(); serverSocketChannelGzh.configureBlocking(false); serverSocketChannelWxq.configureBlocking(false); ServerSocket serverSocketWxq = serverSocketChannelWxq.socket(); ServerSocket serverSocketGzh = serverSocketChannelGzh.socket(); serverSocketWxq.bind(new InetSocketAddress(8089)); System.out.println("Listen to 8089: wechat wall service port"); serverSocketGzh.bind(new InetSocketAddress(8090)); System.out.println("Monitor 8090: public number service port"); Selector selector = Selector.open(); serverSocketChannelWxq.register(selector, SelectionKey.OP_ACCEPT); serverSocketChannelGzh.register(selector, SelectionKey.OP_ACCEPT); while (true){ int num = selector.select(); Set<SelectionKey> selectionKeys = selector.selectedKeys(); selectionKeys.forEach(selectionKey->{ try { if(selectionKey.isAcceptable()){ ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel(); SocketChannel client = null; client = server.accept(); client.configureBlocking(false); String key = ""; if(server==serverSocketChannelGzh) {//If it is a public number server, register its client's readable event. client.register(selector, SelectionKey.OP_READ); key = "["+ UUID.randomUUID()+":gzh]"; }else if(server==serverSocketChannelWxq){//If it is client.register(selector,SelectionKey.OP_WRITE); key = "["+ UUID.randomUUID()+":wxq]"; } System.out.println(key+":Successful connection!"); clientMap.put(client,key); }else if(selectionKey.isReadable()){ SocketChannel channel = (SocketChannel) selectionKey.channel(); readBuffer.clear(); int read = 0; while(true){ int byteRead = channel.read(readBuffer); if(byteRead<=0){ break; } readBuffer.flip(); channel.write(readBuffer); read += byteRead; readBuffer.clear(); } String reMsg = new String(readBuffer.array(),0,read); System.out.println(clientMap.get(channel)+" send to wxq: "+reMsg); //Write wechat wall service for(SocketChannel ch:clientMap.keySet()){ if(ch!=channel) { String key = clientMap.get(ch); if(key.endsWith("wxq]")) { writeBuffer.clear(); writeBuffer.put(("Come from" + clientMap.get(channel) + ":" + reMsg).getBytes(StandardCharsets.UTF_8)); writeBuffer.flip(); ch.write(writeBuffer); } } } } } catch (IOException e) { e.printStackTrace(); } }); selectionKeys.clear();//Delete the SelectionKey every time the SelectionKey event is processed } } }
Now that the server has finished writing, you can use the nc command under linux or win to connect to the server, simulate client A and client B to send messages
After the client sends a message, it will write one because I wrote the message to the buffer of client B after receiving the message
- Client B - send message
Client B is responsible for sending messages. The main event is to write data
Technological process:
- Create a client channelSocketChannel, open a multiple reservation multiplexer selector, bind connectable events, and connect to port 8090 monitored by the server
-
Execute the selector.select() method to handle the connection ready and write ready events
- If the event is connection ready, just get the channel, execute the finishConnect method to complete the connection, and register the listening event as a writable event
- If the event is write ready, perform the write operation, use standard input to read the input from the console and write it to the writebuffer, and write the data to the client through the channel.write() method
- The SelectionKey of the cleanup event
Code:
public class GzhClient { public static void main(String[] args) throws IOException { SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); Selector selector = Selector.open(); socketChannel.register(selector, SelectionKey.OP_CONNECT); socketChannel.connect(new InetSocketAddress("localhost",8090)); while (true){ try{ int num = selector.select(); Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); if(selectionKey.isConnectable()){ SocketChannel client = (SocketChannel) selectionKey.channel(); if(client.isConnectionPending()){ client.finishConnect(); } client.register(selector,SelectionKey.OP_WRITE); }else if(selectionKey.isWritable()){ SocketChannel client = (SocketChannel) selectionKey.channel(); ByteBuffer writeBuffer = ByteBuffer.allocate(1024); writeBuffer.clear(); InputStreamReader reader = new InputStreamReader(System.in); BufferedReader br = new BufferedReader(reader); String msg = br.readLine(); //Read one line at a time, write data to buffer and write to client channel writeBuffer.put(msg.getBytes()); writeBuffer.flip(); client.write(writeBuffer); } } selectionKeys.clear();//Delete the SelectionKey every time the SelectionKey event is processed } catch (Exception e){ e.printStackTrace(); } } } }
- Client A - receive server forward message
Client A is responsible for sending messages, and the main event is reading data
Technological process:
- Create a client channelSocketChannel, open a multiple reservation multiplexer selector, bind connectable events, and connect to port 8089 monitored by the server
-
Execute the selector.select() method to handle the connection ready and read ready events
- If the event is connection ready, just get the channel, execute the finishConnect method to complete the connection, and register the listening event as a writable event
- If the event is ready for reading, perform the read operation, read the data in the channel into the readBuffer using the read() method, receive the String type data through the new String(readBuffer.array() method, and print it to the console
- The SelectionKey of the cleanup event
Code:
public class WxQClient { public static void main(String[] args) throws IOException { SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); Selector selector = Selector.open(); socketChannel.register(selector, SelectionKey.OP_CONNECT); socketChannel.connect(new InetSocketAddress("localhost",8089)); while (true){ try{ int num = selector.select(); Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); if(selectionKey.isConnectable()){ SocketChannel client = (SocketChannel) selectionKey.channel(); if(client.isConnectionPending()){ client.finishConnect(); } client.register(selector,SelectionKey.OP_READ); }else if(selectionKey.isReadable()){ SocketChannel client = (SocketChannel) selectionKey.channel(); ByteBuffer readBuffer = ByteBuffer.allocate(1024); int count = client.read(readBuffer); if (count>0){ String reMsg = new String(readBuffer.array(),0,count); System.out.println(reMsg); }else if(count==-1) client.close();//Close client } } selectionKeys.clear();//Delete the SelectionKey every time the SelectionKey event is processed } catch (Exception e){ e.printStackTrace(); } } } }
So far, we have finished both server and client AB. now let's test it
- Start the server, start a WxQClient, that is, ClientA, and start two gzhclients, that is, ClientB
Server shows successful connection
- Client B sends message
The server receives the message and prints it, and forwards it to client A, client A prints the message