1. Review NIO
Before learning Netty, let's review the communication steps of NIO:
①Create ServerSocketChannel and configure non-blocking mode for it.
②Bind monitoring, configure TCP parameters, enter backlog size, etc.
③ Create an independent IO thread for polling the multiplexer Selector.
④Create a Selector, register the previously created ServerSocketChannel on the Selector, and set the listening flag SelectionKey.OP_ACCEPT.
⑤ Start the IO thread, execute the Selector.select() method in the loop body, and poll the ready channel.
⑥ When the channel in the ready state is polled, the operation bit needs to be judged. If it is in the ACCEPT state, it means that a new client is connected, and the accept method is called to receive the new client.
⑦ Set some parameters of the newly accessed client, such as non-blocking, and continue to register it on the Selector, set the monitoring flag, etc.
⑧ If the polled channel identification bit is READ, read it, construct a Buffer object, etc.
⑨ For more detailed problems, there is also the problem that the data is not sent and continues to be sent...
The reference code is as follows:
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Iterator; import java.util.Vector; public class ChatServer implements Runnable { //Selector private Selector selector; //Select key after registering ServerSocketChannel private SelectionKey serverKey; //Identify whether to run private boolean isRun; //List of user names in the current chat room private Vector<String> unames; //time formatter SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /** * Constructor * @param port The port number monitored by the server */ public ChatServer(int port) { isRun = true; unames = new Vector<String>(); init(port); } /** * Initialize selector and server socket * * @param port The port number monitored by the server */ private void init(int port) { try { // get the selector instance selector = Selector.open(); // get server socket instance ServerSocketChannel serverChannel = ServerSocketChannel.open(); // bind port number serverChannel.socket().bind(new InetSocketAddress(port)); // set to non-blocking serverChannel.configureBlocking(false); //Register the ServerSocketChannel to the selector and specify its behavior as "waiting to accept a connection" serverKey = serverChannel.register(selector, SelectionKey.OP_ACCEPT); printInfo("server starting..."); } catch (IOException e) { e.printStackTrace (); } } @Override public void run() { try { // poll the selector to select the key while (isRun) { //Select the key of a group of channels that are ready for IO operation. When it is equal to 1, it means that there is such a key int n = selector.select(); if (n > 0) { //Get the set of selected keys from the selector and iterate Iterator<SelectionKey> iter = selector.selectedKeys().iterator(); while (iter.hasNext()) { SelectionKey key = iter.next(); //If the channel of this key is waiting to accept a new socket connection if (key.isAcceptable()) { //Remember to remove this key, otherwise new connections will be blocked and cannot connect to the server iter.remove(); //Get the channel corresponding to the key ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); //Accept new connections and return the socket channel to the client peer SocketChannel channel = serverChannel.accept(); if (channel == null) { continue; } // set to non-blocking channel.configureBlocking(false); // Register this socket channel with the selector, specifying its behavior as "read" channel.register(selector, SelectionKey.OP_READ); } //If the behavior of this key's channel is "read" if (key.isReadable()) { readMsg(key); } //If the behavior of the channel of the key is "write" if (key.isWritable()) { writeMsg(key); } } } } } catch (IOException e) { e.printStackTrace (); } } /** * Read data from the socket channel corresponding to the key * @param key selection key * @throws IOException */ private void readMsg(SelectionKey key) throws IOException { //Get the socket channel corresponding to this key SocketChannel channel = (SocketChannel) key.channel(); //Create a buffer of size 1024k ByteBuffer buffer = ByteBuffer.allocate(1024); StringBuffer sb = new StringBuffer(); //Read the data of the channel into the buffer int count = channel.read(buffer); if (count > 0) { //Flip the buffer area (change the buffer area from write data mode to read data mode) buffer.flip(); // Convert the data in the buffer area to String sb.append(new String(buffer.array(), 0, count)); } String str = sb.toString(); //If there is "open_" in the message, it means that the client is ready to enter the chat interface //The data format passed by the client is "open_zing", which means that the user named zing requests to open the chat window //If the user name list is updated, the user name data should be written to each connected client if (str.indexOf("open_") != -1) {//The client connects to the server String name = str.substring(5); printInfo(name + " online"); unames.add(name); //Get the key selected by the selector and iterate Iterator<SelectionKey> iter = selector.selectedKeys().iterator(); while (iter.hasNext()) { SelectionKey selKey = iter.next(); //If it is not the key of the server socket channel, set the data to this key //And update the action that this key is interested in if (selKey != serverKey) { selKey.attach(unames); selKey.interestOps(selKey.interestOps() | SelectionKey.OP_WRITE); } } } else if (str.indexOf("exit_") != -1) {// client sends exit command String uname = str.substring(5); // delete this username unames.remove(uname); //Append "close" string to key key.attach("close"); //Update the action that this key is interested in key.interestOps(SelectionKey.OP_WRITE); // Get the selected key on the selector and iterate //Append the updated namelist data to each socket channel key, and reset the operation that the key is interested in Iterator<SelectionKey> iter = key.selector().selectedKeys().iterator(); while (iter.hasNext()) { SelectionKey selKey = iter.next(); if (selKey != serverKey && selKey != key) { selKey.attach(unames); selKey.interestOps(selKey.interestOps() | SelectionKey.OP_WRITE); } } printInfo(uname + " offline"); } else {// Read client chat message String uname = str.substring(0, str.indexOf("^")); String msg = str.substring(str.indexOf("^") + 1); printInfo("("+uname+")说:" + msg); String dateTime = sdf.format(new Date()); String smsg = uname + " " + dateTime + "\n " + msg + "\n"; Iterator<SelectionKey> iter = selector.selectedKeys().iterator(); while (iter.hasNext()) { SelectionKey selKey = iter.next(); if (selKey != serverKey) { selKey.attach(smsg); selKey.interestOps(selKey.interestOps() | SelectionKey.OP_WRITE); } } } } /** * Write data to the socket channel corresponding to the key * @param key * @throws IOException */ private void writeMsg(SelectionKey key) throws IOException { SocketChannel channel = (SocketChannel) key.channel(); Object obj = key.attachment(); //Here it is necessary to set the additional data of the key to be empty, otherwise there will be problems key.attach(""); //The additional value is "close", then cancel the key and close the corresponding channel if (obj.toString().equals("close")) { key.cancel(); channel.socket().close(); channel.close(); return; }else { // write data to the channel channel.write(ByteBuffer.wrap(obj.toString().getBytes())); } //Reset this key interest key.interestOps(SelectionKey.OP_READ); } private void printInfo(String str) { System.out.println("[" + sdf.format(new Date()) + "] -> " + str); } public static void main(String[] args) { ChatServer server = new ChatServer(19999); new Thread(server).start(); } }