深入Netty(一)-NIO基础编程

508 阅读3分钟

通过一段简单的代码来了解一下java NIO编程。需求很简单,就是客户端发送一条消息至服务端,服务端在返回一条消息就行。

首先编写服务端代码。

public class NIOServer {

    public static void main(String[] args) throws IOException {

        //开启一个多路I/O复用器
        Selector selector = Selector.open();

        new Thread(() -> {
            try {
                //开启一个服务器socket,该socket的唯一作用是接收客户端连接
                ServerSocketChannel socketChannel = ServerSocketChannel.open();

                //将接收连接这个socket设置为阻塞模式
                socketChannel.configureBlocking(true);

                //绑定端口
                socketChannel.bind(new InetSocketAddress(8000));

                while (true) {
                    //因为设置成了阻塞模式,所以只有当连接进来时,代码才会往下走,不然会阻塞在这里
                    SocketChannel channel = socketChannel.accept();

                    //这个channel为服务端与客户端的连接,既然是用NIO,这个地方就设置成非阻塞模式
                    channel.configureBlocking(false);

                    //将新进来的连接注册到复用器上,并设置关注的事件为读事件
                    channel.register(selector, SelectionKey.OP_READ);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {

            //分配两个内存缓冲区,分别为接收和发送事件使用
            ByteBuffer receiveBuf = ByteBuffer.allocate(1024);

            ByteBuffer sendBuf = ByteBuffer.allocate(1024);

            try {
                while (true) {
                    //查询复用器上是否有事件发生
                    if (selector.select(1000) > 0) {
                        //获取发生事件的key
                        Set<SelectionKey> keys = selector.selectedKeys();
                        Iterator<SelectionKey> iterator = keys.iterator();

                        while (iterator.hasNext()) {
                            SelectionKey key = iterator.next();
                            //获取key的channel
                            SocketChannel channel = (SocketChannel) key.channel();
                            try {
                                if (key.isValid()) {
                                    //如果该key关注的是读事件
                                    if (key.isReadable()) {
                                        //判断是否有数据可读,如果read为-1的话,就是客户端发送的断开连接的指令
                                        int read = channel.read(receiveBuf);

                                        if (read == -1) {
                                            channel.close();
                                            continue;
                                        }

                                        //读取客户端发送的消息
                                        receiveBuf.flip();
                                        System.out.println(Charset.defaultCharset().newDecoder().decode(receiveBuf).toString());
                                        receiveBuf.clear();

                                        //返回客户端的消息
                                        sendBuf.put("this is server".getBytes());
                                        sendBuf.flip();
                                        channel.write(sendBuf);
                                        sendBuf.clear();

                                    }
                                    //...通常这边还会有写事件或连接事件等等
                                } else {
                                    channel.close();
                                }
                            } finally {
                                //处理完成后需要将该key从迭代器移除,不然下次会重复处理
                                iterator.remove();
                            }
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

    }

}

其次编写客户端代码

public class NioClient {

    public static void main(String[] args) throws IOException {

        //开启一个客户端socket,并连接至服务端
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress(8000));

        //往服务端写入数据
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put("this is client".getBytes());
        byteBuffer.flip();
        socketChannel.write(byteBuffer);

        byteBuffer.clear();

        //从客户端读取数据并展示
        socketChannel.read(byteBuffer);
        byteBuffer.flip();
        System.out.println(Charset.defaultCharset().newDecoder().decode(byteBuffer).toString());

        //关闭连接
        socketChannel.close();

    }

}

NIO编程的基础组件在上述代码中基本都展示了。配合注释应该很容易理解,我们主要要了解几个关键类的作用。

  1. Selector 多路选择复用器,一个Selector可以注册多个SocketChannel,每当SocketChannel发生我们感兴趣的事件的时候,Selector就可以将他们挑选出来。这样的话即便一万个连接中只有一个接收到了消息,我们也可以很轻易的找出来。
  2. ServerSocketChannel 这个类的主要作用就是接收客户端的连接,并生成SocketChannel
  3. SocketChannel 这个类就是负责服务端和客户端的通信了。每个连接都会生成一个SocketChannel,我们可以通过他来接收来自客户端的消息,并返回消息给客户端。

在了解的基本的java API之后,下一步就开始学习Netty的源代码。