Preface
Based on the Server side of BIO implementation, how many threads will there be when 100 connections are established? How many threads will there be based on NIO?
BIO
The so-called BIO is the most traditional socket link, for example:
int port = 4343; //Port number // Socket server (simple sending information) Thread sThread = new Thread(new Runnable() { @Override public void run() { try { ServerSocket serverSocket = new ServerSocket(port); while (true) { // Waiting for connection Socket socket = serverSocket.accept(); Thread sHandlerThread = new Thread(new Runnable() { @Override public void run() { try (PrintWriter printWriter = new PrintWriter(socket.getOutputStream())) { printWriter.println("hello world!"); printWriter.flush(); } catch (IOException e) { e.printStackTrace(); } } }); sHandlerThread.start(); } } catch (IOException e) { e.printStackTrace(); } } }); sThread.start(); // Socket client (receiving information and printing) try (Socket cSocket = new Socket(InetAddress.getLocalHost(), port)) { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream())); bufferedReader.lines().forEach(s -> System.out.println("Client:" + s)); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
The flow chart is roughly as follows
So there will be 101 threads, one accept thread and 100 link threads.
It's not complicated.
NIO
In fact, we don't know how to write NIO code ourselves, but we can use excellent open source libraries like netty.
This is the schematic logical representation
First, each client will correspond to a socketchannel channel channel (generally, the channel reads and writes data through buffer), and then these socketchannels will be registered into the selector. selector is equivalent to a manager. It will poll all socketchannels, query all available socketchannels, and then handle these socketchannels
Server side
public class NIOServerSocket { //Queue to store SelectionKey private static List<SelectionKey> writeQueue = new ArrayList<SelectionKey>(); private static Selector selector = null; //Add SelectionKey to queue public static void addWriteQueue(SelectionKey key){ synchronized (writeQueue) { writeQueue.add(key); //Wake up main thread selector.wakeup(); } } public static void main(String[] args) throws IOException { // 1. Create ServerSocketChannel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 2. Bind port serverSocketChannel.bind(new InetSocketAddress(60000)); // 3. Set non blocking nio to use non blocking mode only serverSocketChannel.configureBlocking(false); // 4. Create channel selector selector = Selector.open(); /* * 5.Register event type * * sel:Channel selector * ops:Event type = = > selectionkey: a wrapper class that contains the event type and the channel itself. Four constant types represent four event types * SelectionKey.OP_ACCEPT Get message selectionkey.op'connect * SelectionKey.OP_READ Read selectionkey.op'write */ serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { System.out.println("Server side: listening for port 60000"); // 6. Obtain available I/O channels and how many channels are available int num = selector.select(); if (num > 0) { // Determine if there are available channels // Get all keys Set<SelectionKey> selectedKeys = selector.selectedKeys(); // Use iterator to traverse all keys Iterator<SelectionKey> iterator = selectedKeys.iterator(); // Iterate through the current I/O channel while (iterator.hasNext()) { // Get the current key SelectionKey key = iterator.next(); // Calling the remove() method of iterator does not remove the current I/O channel, indicating that the current I/O channel has been processed. iterator.remove(); // Judge the event type and handle it accordingly if (key.isAcceptable()) { ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel(); SocketChannel socketChannel = ssChannel.accept(); System.out.println("Processing request:"+ socketChannel.getRemoteAddress()); // Get client data // Set non blocking status socketChannel.configureBlocking(false); // Register to selector socketChannel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { System.out.println("Read events"); //Cancel monitoring of read events key.cancel(); //Call read operation tool class NIOHandler.read(key); } else if (key.isWritable()) { System.out.println("Writing events"); //Cancel monitoring of read events key.cancel(); //Call write tool class NIOHandler.write(key); } } }else{ synchronized (writeQueue) { while(writeQueue.size() > 0){ SelectionKey key = writeQueue.remove(0); //Register write events SocketChannel channel = (SocketChannel) key.channel(); Object attachment = key.attachment(); channel.register(selector, SelectionKey.OP_WRITE,attachment); } } } } } }
Message processor, where multithreading is used to process messages, the number of threads is generally related to the number of cpu cores of the server, and the main purpose is to play the performance of all cpu cores.
public class NIOHandler { //Construct thread pool private static ExecutorService executorService = Executors.newFixedThreadPool(10); public static void read(final SelectionKey key){ //Get thread and execute executorService.submit(new Runnable() { @Override public void run() { try { SocketChannel readChannel = (SocketChannel) key.channel(); // I/O read data operation ByteBuffer buffer = ByteBuffer.allocate(1024); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int len = 0; while (true) { buffer.clear(); len = readChannel.read(buffer); if (len == -1) break; buffer.flip(); while (buffer.hasRemaining()) { baos.write(buffer.get()); } } System.out.println("Data received by the server:"+ new String(baos.toByteArray())); //Add data to key key.attach(baos); //Add registered writes to the queue NIOServerSocket.addWriteQueue(key); } catch (IOException e) { e.printStackTrace(); } } }); } public static void write(final SelectionKey key) { //Get thread and execute executorService.submit(new Runnable() { @Override public void run() { try { // Write operation SocketChannel writeChannel = (SocketChannel) key.channel(); //Get the data passed by the client ByteArrayOutputStream attachment = (ByteArrayOutputStream)key.attachment(); System.out.println("Data sent by the client:"+new String(attachment.toByteArray())); ByteBuffer buffer = ByteBuffer.allocate(1024); String message = "Hello, I am the server!!"; buffer.put(message.getBytes()); buffer.flip(); writeChannel.write(buffer); writeChannel.close(); } catch (IOException e) { e.printStackTrace(); } } }); } }
Client
public class NIOClientSocket { public static void main(String[] args) throws IOException { //Using threads to simulate concurrent access of users for (int i = 0; i < 1; i++) { new Thread(){ public void run() { try { //1. Create SocketChannel SocketChannel socketChannel=SocketChannel.open(); //2. Connect to the server socketChannel.connect(new InetSocketAddress("localhost",60000)); //Writing data String msg="I am the client"+Thread.currentThread().getId(); ByteBuffer buffer=ByteBuffer.allocate(1024); buffer.put(msg.getBytes()); buffer.flip(); socketChannel.write(buffer); socketChannel.shutdownOutput(); //Read data ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len = 0; while (true) { buffer.clear(); len = socketChannel.read(buffer); if (len == -1) break; buffer.flip(); while (buffer.hasRemaining()) { bos.write(buffer.get()); } } System.out.println("Client received:"+new String(bos.toByteArray())); socketChannel.close(); } catch (IOException e) { e.printStackTrace(); } }; }.start(); } } }
There is a strange code in the above code, that is, writequeue. The main reason for this is
The ready condition for the op'write event does not occur after the channel's write method is called, but when the underlying buffer has free space. Because the write buffer has free space most of the time, if you register the write event, it will make the write event always ready, and the choice of processing site will always occupy CPU resources. So, only when you do have data to write, register the write operation, and cancel the registration immediately after writing.
In fact, in most cases, we just call the channel's write method to write the data directly. There is no need to use the op? Write event. So under what circumstances is the op'write event mainly used?
In fact, the op write event is mainly used when the sending buffer space is full. Such as:
while (buffer.hasRemaining()) { int len = socketChannel.write(buffer); if (len == 0) { selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_WRITE); selector.wakeup(); break; } }
When the buffer still has data, but the buffer is full, socketChannel.write(buffer) will return the number of bytes that have been written out, which is 0. At this time, we need to register the op write event, so that when there is free space in the buffer, the op write event will be triggered, so that we can continue to write the unfinished data.
And after writing, be sure to log off the op ﹣ write event:
selectionKey.interestOps(sk.interestOps() & ~SelectionKey.OP_WRITE);
Note that the wakeup() is called after modifying the interest here; the method is to wake up the blocked selector method, so that when while determines that selector returns 0, it will call selector.select() again. The selectionKey's interest is registered to the system to listen every time the selector.select() operation is performed, so the modified interest after the selector.select() call needs to take effect in the next selector.select() call.
So for NIO, 100 links will not have 100 threads, but will have cpu cores + 1 thread, or cpu cores x2 +1
Reference resources
http://www.imooc.com/article/265871 https://blog.csdn.net/zxcc1314/article/details/80918665 https://www.jianshu.com/p/1af407c043cb