Netty learning (4): NIO network programming

Outline

In Netty learning (3), we have learned the concepts and Channel Buffer, and then let us NIO-depth understanding of the first three components of NIO by implementing a multi-user chat server: Selector .

purpose

In this article, we will implement an Internet chat room procedures, to grasp the concept and how to use NIO Selector to complete the network programming.

demand

  • Service-Terminal
    • It may detect a user logs off, and inform the other users;
    • Forwards a client's message to other online clients.
  • Client:
    • You can send messages to all other online users;
    • By forwarding the message received by other users.

FIG implementation logic

Construction of NIO_Server.png process

Code

Server

Setting common attributes
public class NioNetworkServer {
    private Logger logger = LoggerFactory.getLogger(NioNetworkServer.class);

    ServerSocketChannel serverSocketChannel;
    Selector selector;
    InetSocketAddress inetSocketAddress;

    public NioNetworkServer() {
        try {
            // 生成一个 ServerSocketChannel 和 Selector,并将 ServerSocketChannel 绑定到指定端口
            serverSocketChannel = ServerSocketChannel.open();
            selector = Selector.open();
            inetSocketAddress = new InetSocketAddress(6666);
            serverSocketChannel.socket().bind(inetSocketAddress);

            serverSocketChannel.configureBlocking(false);

            // 将 serverSocketChannel(一开始的服务器Channel) 注册到指定selector上
            // 后面给每一个连接生成的 SocketChannel,就是通过 ServerSocketChannel 来生成的
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        } catch (IOException e) {
            logger.error("建立Server失败,失败原因:{0}", e);
        }
    }
}

Set some properties, and set the values ​​of these properties in the constructor

Listener methods
    public void listen() {
        try {
            while (true) {
                // 没有事件发生,就干其他事
                if (selector.select(3000) == 0) {
                    continue;
                }

                // 得到有事件发生的事件集合,然后在后面可以通过其反向获取channel
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    // 事件:有新的连接
                    if (selectionKey.isAcceptable()) {
                        whenAccept();
                    }
                    // 事件:读从客户端获取的数据
                    if (selectionKey.isReadable()) {
                        readData(selectionKey);
                    }
                    iterator.remove();
                }
            }
        } catch (IOException e) {
            logger.error("读写错误:{0}", e);
        } finally {

        }
    }
Connection request
 private void whenAccept() throws IOException {
        // 因为此时已经有连接事件进入了,因此虽然 accept() 是阻塞的,但是在这里会直接返回
        SocketChannel socketChannel = serverSocketChannel.accept();
        logger.info("connect success,socketChannel : " + socketChannel.toString());
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));

        //将某人上线的消息进行显示
        logger.info(socketChannel.getRemoteAddress() + "上线");
    }
Read and write data
    private void readData(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        try {
            socketChannel.read(byteBuffer);
            String message = new String(byteBuffer.array());
            logger.info("{}{}{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), "\n\t\t", message);

            // 向其他的客户端转发消息
            sendInfoToOtherClients(socketChannel, message);
        } catch (Exception e) {
            // 在捕获到异常后,就是有客户端发送了断开连接的请求
            logger.info("{},离线了。。。", socketChannel.getRemoteAddress());
            sendInfoToOtherClients(socketChannel, socketChannel.getRemoteAddress() + " 离线了。。。");
            // 将关闭连接的 channel 关闭
            socketChannel.close();
            // 将该键移除出 set
            selectionKey.cancel();
        }
    }

Here, we come to the reverse channel is acquired according selectionKey, by the channel data is read into the buffer, so as to read into memory.

Subsequently, it forwards the message to other clients.

    private void sendInfoToOtherClients(SocketChannel socketChannel, String message) throws IOException {
        for (SelectionKey key : selector.keys()) {
            SelectableChannel sourceChannel = key.channel();
            if (sourceChannel instanceof SocketChannel && sourceChannel != socketChannel) {
                SocketChannel targetChannel = (SocketChannel) sourceChannel;
                // 根据转发过来的字节长度,直接生成目标大小的 Buffer,然后将数据写入到客户端的 channel 中
                ByteBuffer targetByteBuffer = ByteBuffer.wrap(message.getBytes());
                targetChannel.write(targetByteBuffer);
            }
        }
    }

Finally, mainstart to function.

    public static void main(String[] args) {
        NioNetworkServer nioNetworkServer = new NioNetworkServer();
        nioNetworkServer.listen();
    }

Client

Setting common attributes
public class NioNetworkChatClient {
    private Logger logger = LoggerFactory.getLogger(NioNetworkChatClient.class);

    private Selector selector;
    private SocketChannel socketChannel;
    private String username;

    public NioNetworkChatClient() {
        try {
            selector = Selector.open();
            InetSocketAddress inetSocketAddress = new InetSocketAddress("localhost", 6666);
            socketChannel = SocketChannel.open(inetSocketAddress);
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);
            username = socketChannel.getLocalAddress().toString().substring(1);
        } catch (Exception e) {
            logger.error("构建客户端错误,错误原因:{0}", e);
        }
    }
}

Method of constructing the client and Server are basically the same, the difference is, Server is built ServerSocketChannel, Client is built SocketChannel, in addition Client is a registered start SelectionKey.OP_READevent. Other similar.

Read data
    /**
     * 1. 获取selector上发生的事件
     * 2. 如果是读事件,则将数据通过 Channel 和 Buffer 进行操作
     * 3. 处理完成后,将该key从待处理keys中删除
     */
    public void readInfo() {
        try {
            int readChannel = selector.select();
            if (readChannel > 0) {
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    if (selectionKey.isReadable()) {
                        SocketChannel handingSocketChannel = (SocketChannel) selectionKey.channel();
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

                        int read = handingSocketChannel.read(byteBuffer);
                        if (read > 0) {
                            String message = new String(byteBuffer.array());
                            logger.info("{},{},{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), "\n\t\t", message);
                        } else {
                            logger.info("client closed");
                            // 将关闭连接的 channel 关闭
                            handingSocketChannel.close();
                            // 将该键移除出 set
                            selectionKey.cancel();
                        }
                    }
                }
                iterator.remove();
            } else {
                logger.info("当前没有 channel 可供使用!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
send data
    public void sendInfo(String info) {
        info = username + " : " + info;
        try {
            logger.info("{},{},{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), "\n\t\t", info);
            socketChannel.write(ByteBuffer.wrap(info.getBytes()));
        } catch (IOException e) {
            logger.error("发送数据错误,错误原因:{0}", e);
        }
    }

Very simple to send data, the data is simply look at the package, can be directly written to socketChannel.

start up
    /**
     * 1. 启动一个线程来定时读取 Server 可能发送的数据,如果没有,就休眠,等待下次读取
     * 2. 启动一个获取控制台输出来进行数据的发送
     *
     * @param args
     */
    public static void main(String[] args) {
        NioNetworkChatClient nioNetworkChatClient = new NioNetworkChatClient();

        // 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程,但跟前面一样,这里因为不是重点,就先这样用着
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    nioNetworkChatClient.readInfo();
                    try {
                        sleep(3000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
            }
        }.start();

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            String message = scanner.nextLine();
            nioNetworkChatClient.sendInfo(message);
        }
    }

Demonstration Results

NIO chat room presentation, results

The result is not in the back of the multi-demonstrated, with similar here, so we've had the procedure a chat room, everyone can communicate in it.

to sum up

In this paper we have a chat room through the program, to demonstrate the effect Selector, Channel, Buffer used in combination, by these three, we implemented a program NIO of network programming.

But in the preparation of this program, I believe that friends also found that abnormal cumbersome, involving the middle of the building Buffer, Selector API usage, and so on. Therefore, in the future of course, we have to use to achieve Netty work now, in order to reduce the workload of the development.

ps: talk so much, finally to Netty, but a good foundation is the key to learning, do not know if Java's NIO and IO model implementations and limitations, not well understood learning Netty.

Article code has been uploaded to GitHub, address https://github.com/wb1069003157/nettyPre-research, welcome to discuss together, progress together.

No public iceWang

Article No. firsthand update on the public "iceWang", interested friends can focus the public number, the first time I see the point of knowledge sharing, thank you! Refill!

Guess you like

Origin www.cnblogs.com/JRookie/p/12460541.html