Java network programming - NIO architecture

Table of contents

1. What is NIO

2. NIO structure

3. Realization of chat system based on NIO

4.Netty


1. What is NIO

NIO: java non-blocking IO, synchronous non-blocking IO.

BIO is blocking IO, that is, each event needs to be assigned a process to it, and if the client is not connected, it will be blocked and waited.

And NIO, asynchronous I/O is a method of reading and writing data without blocking: under this architecture, we can register for specific I/O events such as data readability, new connection arrival, etc., and when such events of interest occur , the system will tell you instead of waiting.

For example:

Five people (requests) write homework. Under the BIO architecture, five teachers (five processes) watch and write, and students write directly on the book (memory reading and writing).
Under the NIO architecture, a teacher (process) looks at the writing and asks the students to write in the notebook (buffer, buffer) first, and keeps asking the assistant (selector) to hand in the written (event ready)!

2. NIO structure

NIO has three main core parts: Channel (channel), Buffer (buffer), Selector.

Channel : Similar to Stream in IO. It's just that Stream is one-way, and channel is two-way, which can be used for both read and write operations.

Buffer : The buffer in NIO is essentially a readable memory block. When writing data to the buffer, the buffer will record how much data was written. Once the data is to be read, the Buffer needs to be switched from write mode to read mode through the flip() method to read all the data previously written to the buffer.

The biggest difference between NIO and ordinary IO in java is the way of data packaging and transmission. Traditional IO operates based on byte stream and character stream, while NIO operates based on Channel and Buffer (buffer). Data is always read from the channel to the buffer or written from the buffer to the channel.

As shown in the figure above: the client/server stores the data to be transmitted into the buffer buffer, and then obtains the corresponding channel through the java stream, then uses the channel to write/read the data in the buffer, and outputs to the corresponding in the target (file/bytestream).

selector : To use a Selector, you must register a Channel with the Selector, and then call its select() method to monitor: this method will block until at least one registered channel has an event ready; when the event is ready, the corresponding selectionKey ( for Associated channels, each type of event corresponds to a selectionKey, which can help to obtain the corresponding channel for subsequent operations) to the internal collection, and return; once this method returns, the thread can process these events.

Note: The select() method is blocking, the select(1000) method is blocking for 1000 milliseconds; the selectNow() method is non-blocking

After the client initiates a request event, the response process of the NIO architecture is shown in the following figure:

First, the server will obtain a ServerSocketChannel and register with the selector, and then wait for the client to connect.

The client initiates a request to the server, and the selector monitors the occurrence of an event (such as a client read event) through the select() method, and allocates a thread to process the event; the server returns the selectionKey corresponding to the event type, and then obtains the channel for processing through the key. Write the data into the buffer through the channel.read() method, and return it to the client; the client reads the required data from the buffer, and the request ends.

Selector (selection area) is used to monitor the events of multiple channels (such as: connection opening, data arrival), only when the channel actually has read and write events, it will read and write, which greatly reduces system overhead and avoids multi-threading. context switching between. This allows one I/O thread to process multiple client connections and read and write operations concurrently, greatly improving performance. 

3. Realization of chat system based on NIO

Based on the above architecture, we can write a chat system according to the NIO structure:

(1) Server:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

public class ChatServer {
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;
    public static final int port = 8080;

    public ChatServer(){
        try {
            //获取ServerSocketChannel供客户端连接,并开放端口
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(port));
            serverSocketChannel.configureBlocking(false);
            //得到Selector,并注册channel
            selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("服务器就绪");
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    public void listen(){
        try {
            while (true){
                //阻塞2秒
                int count = selector.select(2000);

                if (count>0){
                    //有事件需要处理
                    Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
                    while (keyIterator.hasNext()){
                        SelectionKey key = keyIterator.next();
                        //客户端连接事件,为客户端生成SocketChannel
                        if (key.isAcceptable()){
                            SocketChannel socketChannel = serverSocketChannel.accept();
                            //非阻塞channel才能进行注册
                            socketChannel.configureBlocking(false);
                            //注册,绑定事件读,并为该channel关联buffer
                            socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                            System.out.println("客户端:"+socketChannel.getRemoteAddress()+" 成功连接");
                        }
                        //读取事件
                        if (key.isReadable()){
                            readMessage(key);
                        }

                        //删除已处理key,避免重复操作
                        keyIterator.remove();
                    }
                }else {
                    continue;
                }
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    //从客户端读取消息
    private void readMessage(SelectionKey key){
        SocketChannel channel = null;
        try {
            //通过key反向获取channel
            channel = (SocketChannel) key.channel();
            //获取该channel关联buffer
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //从buffer中读取数据
            int read = channel.read(buffer);
            if (read>0){
                String msg = new String(buffer.array()).trim();
                //转发消息(排除自己)
                transformMessage(msg,channel);
            }

        }catch (IOException e){
            try {
                System.out.println(channel.getRemoteAddress()+"下线");
                //取消注册
                key.cancel();
                //关闭通道
                channel.close();
            }catch (Exception exception){
                exception.printStackTrace();
            }
        }
    }
    //转发消息给其他客户端
    public void transformMessage(String msg, SocketChannel self) throws IOException {
        System.out.println("服务器转发消息");
        //遍历所有注册到selector的channel,进行转发
        for (SelectionKey key:selector.keys()){
            Channel targetChannel = key.channel();
            //排除自己
            if (targetChannel instanceof SocketChannel && targetChannel!=self){
                //转发消息
                SocketChannel dest = (SocketChannel) targetChannel;
                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8));
                dest.write(buffer);
            }

        }
    }

    public static void main(String[] args) {
        ChatServer chatServer = new ChatServer();
        chatServer.listen();
    }
}
  1. Create a ServerSocketChannel, open the port, and set it to non-blocking.      
  2. Get a selector, register the channel, bind the trigger event and the corresponding SelectionKey
  3. Waiting for the client to connect.
  4. When the select() method listens to the event, get the selectionKey collection of the event; traverse the collection and process the event. (The connection event creates a socketChannel connection; the read event reads the message in the buffer and forwards it to other clients)

Note: Only in blocking mode, the channel can register with the selector ( the SocketChannel obtained by serverSocketChannel.accept() also needs to be set ) ; after the server generates and registers a ServerSocketChannel, the client can use the SocketChannel to connect to it; 

(2) Client:

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.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Scanner;

public class ChatClient implements Runnable{
    private SocketChannel socketChannel;
    private final String host = "127.0.0.1";
    private final int port = 8080;
    private Selector selector;
    private String username;

    public ChatClient(){
        try {
            socketChannel=SocketChannel.open(new InetSocketAddress(host, port));
            socketChannel.configureBlocking(false);
            selector=Selector.open();
            socketChannel.register(selector, SelectionKey.OP_READ);
            username = socketChannel.getLocalAddress().toString().substring(1);
            System.out.println("客户端就绪");
        }catch (IOException e) {
            e.printStackTrace();
        }
    }

    //发送消息
    public void sendMsg(String msg){
        msg = username + ":" + msg;
        try {
            socketChannel.write(ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8)));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //读取回复消息
    public void readMsg(){
        try {
            int select = selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                if (key.isReadable()){
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    channel.read(buffer);

                    String str = new String(buffer.array());
                    System.out.println(str.trim());
                }
                iterator.remove();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true){
            this.readMsg();
            //每隔1秒监听一次
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ChatClient chatClient = new ChatClient();
        //启动线程读取
        new Thread(chatClient).start();
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()){
            String str = scanner.nextLine();
            chatClient.sendMsg(str);
        }

    }
}
  1. Get the socketServer to connect to the server and set the non-blocking mode.
  2. Connect to the server.
  3. Initiate an event: send a message and write the message to the channel.
  4. Read other customer order messages forwarded by the server.

(3) Operation effect:

Server:

 Client 1:

 Client 2:

 Client 3:

4.Netty

Netty is an asynchronous event-driven network application framework (that is, based on NIO architecture) for rapid development of maintainable high-performance protocol servers and clients.

Why is there Netty?

  • 1) The class library and API of NIO are complex and cumbersome to use;
  • 2) The workload and difficulty of development are large, and problems such as disconnection and reconnection, half-package reading and writing, and failed caching are faced;
  • 3) The use of native NIO programming requires additional skills such as Reactor model and multi-threaded programming, which are highly demanding;
  • 4) Java native NIO has certain bugs, which may affect NIO programming.

Netty, based on the Reactor multi-threading model, well encapsulates the NIO API that comes with the JDK to solve the above problems. And Netty has the advantages of high performance, higher throughput, lower latency, reduced resource consumption, and minimized unnecessary memory copying.

Its core lies in the extensible event-driven model, global interactive API, and zero copy.

Netty is fast to use, and its use form is similar to the NIO architecture, which is also divided into server and client:

(1) Server:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;

public class NettyServer {
    public static void main(String[] args) {
        //定义线程组:BossEventLoop(负责连接) , WorkerEventLoop(负责业务读写)
        EventLoopGroup bossEventLoop = new NioEventLoopGroup(1);
        EventLoopGroup workerEventLoop = new NioEventLoopGroup();

        try {
            // 1. ServerBootstrap:启动器,负责组装netty组件,启动服务器
            new ServerBootstrap()
                    // 2.存入线程组
                    .group(bossEventLoop,workerEventLoop)
                    // 3.选择服务器的ServerSocketChannel实现
                    .channel(NioServerSocketChannel.class)
                    // 4. worker(child) , 决定了worker能执行什么操作(handler)
                    .childHandler(
                            // 5. channel 代表和客户端进行读写的通道 Initializer:初始化器,负责添加别的handler
                            new ChannelInitializer<NioSocketChannel>() {
                                @Override
                                protected void initChannel(NioSocketChannel ch) throws Exception {
                                    // 6. 添加具体handler
                                    ch.pipeline().addLast(new StringDecoder()); //将 ByteBuf 转为字符串
                                    ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {   //自定义handler
                                        @Override   //读事件
                                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                            System.out.println(msg);
                                        }
                                    });
                                }
                            })
                    .bind(8080);

        }catch (Exception e){
            System.out.println("客户端断开连接");
        }

    }
}

(2) Client:

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;

import java.net.InetSocketAddress;

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {

        try {
            // 1.启动类
            new Bootstrap()
                    // 2.添加EventLoop
                    .group(new NioEventLoopGroup())
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new StringEncoder());
                        }
                    })
                    .connect(new InetSocketAddress("localhost",8080))
                    .sync()
                    .channel()
                    //发送数据
                    .writeAndFlush("hello world");
        }catch (Exception e){
            System.out.println("服务器异常");
        }

    }
}

The above code realizes the function that the server creates a ServerSocketChannel, the client connects to the server and writes a message, and the server successfully reads it.

おすすめ

転載: blog.csdn.net/tang_seven/article/details/130387986