Try to learn many times, and finally understand NIO!

Keywords: Java socket jvm Programming Linux

NIO—NonBlocking IO(new IO)

  1. 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
  2. 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
  3. 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:

  1. Event driven model -- asynchronous programming is inseparable from events
  2. Single thread processing multi connection multiplexing makes processing more efficient
  3. Non blocking IO, only blocking to get operable events
  4. block based transport is more efficient than stream based transport
  5. 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:

  1. Create two server channel s and bind different ports
  2. Create a multiplexer selector, register the two servers on the selector, and listen for the acceptable event
  3. Execute the selector.select() method, get the SelectionKey collection, and handle different events continuously

    1. 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
    2. 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
  4. 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:

  1. Create a client channelSocketChannel, open a multiple reservation multiplexer selector, bind connectable events, and connect to port 8090 monitored by the server
  2. Execute the selector.select() method to handle the connection ready and write ready events

    1. 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
    2. 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
  3. 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:

  1. Create a client channelSocketChannel, open a multiple reservation multiplexer selector, bind connectable events, and connect to port 8089 monitored by the server
  2. Execute the selector.select() method to handle the connection ready and read ready events

    1. 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
    2. 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
  3. 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

  1. Start the server, start a WxQClient, that is, ClientA, and start two gzhclients, that is, ClientB

Server shows successful connection

  1. Client B sends message

The server receives the message and prints it, and forwards it to client A, client A prints the message

Posted by phast1 on Thu, 14 Nov 2019 22:25:57 -0800