「Netty源码一」网络IO介绍

397 阅读3分钟

Channel

IO操作纽带,一个Channel代表与实体开放开放连接。在网络IO中一般就是和Buffer读/写数据用。

image.png

JAVA NIO中的一些主要Channel的实现

  • FileChannel:从文件中读写数据。
  • DatagramChannel:能通过UDP读写网络中的数据。
  • SocketChannel:能通过TCP读写网络中的数据。
  • ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

常用方法

  • int read(ByteBuffer var1)从Buffer中读取字节
  • int write(ByteBuffer src)向Buffer中写数据
  • boolean connect(SocketAddress remote) 客户端连接某个地址
  • SocketChannel accept()接受连接并创建SocketChannel

Buffer

Buffer本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。

Java NIO里关键的Buffer实现

这些实现基本上覆盖了所有IO传送的基本数据类型

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

常用方法

  • ByteBuffer allocate(int capacity):创建一个指定大小的buffer
  • ByteBuffer put(byte b):将指定字节写入buffer ,channel的read是将channel的数据写入到buffer
  • byte get():从buffer中读取数据,channel的write是将buffer中的数据写入到channel中
  • Buffer flip() 读写模式切换

Selector

Selector (多路复用器)。它是 Java NIO 核心组件中的一个,同时对多个Channel监听。当某个channel发生特定事件的时候,这些通道就会变成可用状态。

常用方法

  • public static Selector open(): 创建selector
  • final SelectionKey register(Selector sel, int ops) 这个方法是channel的,selector作为参数传入,第二个参数在,为事件。当channel触发一个事件的时候,就变为就绪状态。这四个事件分别是
    • SelectionKey.OP_READ::读事件,适用于两端
    • SelectionKey.OP_WRITE::写时间,适用于两端
    • SelectionKey.OP_CONNECT::连接完成事件( TCP 连接 ),仅适用于客户端
    • SelectionKey.OP_ACCEPT::接受新连接事件,仅适用于服务端
  • Set selectedKeys():返回对应的SelectionKeys
  • int select():阻塞到一个channel就绪了,返回多少channel就绪了
  • int select(long timeout):增加超时机制
  • public abstract int selectNow():立即返回数量,而不阻塞。

SelectionKey

SelectionKey表示一个channel向一个selector的注册的令牌

常用方法

  • SelectionKey interestOps():返回感兴趣的事件集合
  • Object attach(Object ob):往SelectionKey中添加附件
  • Object attachment():在SelectionKey中取出附件

SocketChannel

SocketChannel是连接到TCP网络套接字的通道。 创建方式为SocketChannel.open或者ServerSocketChannel.accept()上面有介绍,重复介绍下

ServerSocketChannel

ServerSocketChannel 是监听新进来的TCP连接的通道

传统的IO编程【简化版本】

上面这些基础概念知道后,下面就开始网络编程部分了,先来个简单的例子。

服务端

public class NIOServer {
    public static void main(String[] args) throws IOException {
        //创建ServerSocketChannel,处理接入连接
        ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
        //设置是否为非阻塞
        serverSocketChannel.configureBlocking(false);
        //绑定端口号
        serverSocketChannel.socket().bind(new InetSocketAddress(8080));
        while (true){
            if(serverSocketChannel.isOpen()){
                SocketChannel socketChannel=serverSocketChannel.accept();
                if(socketChannel!=null){
                    //读取数据
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int bytesRead = socketChannel.read(buffer);
                    buffer.flip();
                    byte[] bytes = new byte[buffer.remaining()];
                    System.arraycopy(buffer.array(), buffer.position(), bytes, 0, buffer.remaining());
                    System.out.println(new String(bytes, "UTF-8"));
                    //通道关闭
                    socketChannel.close();
                }
            }
        }
    }
}

客户端

public static void main(String[] args) throws IOException, InterruptedException {
    for (int i = 0; i < 30; i++) {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress(8080));
        if(socketChannel.isConnected()){
            ByteBuffer buffer=ByteBuffer.allocate(1024);
            buffer.put("hello world".getBytes());
            System.out.println("hello world");
            buffer.flip();
            while(buffer.hasRemaining()) {
                socketChannel.write(buffer);
            }
            socketChannel.close();
        }

    }
}

就是简单的发送数据

image.png image.png

那么问题也来了,这是个简单的网络编程,这个还是有很多问题的,例如,服务端是只能处理新连接,并处理第一次接受到的信息,对于后续发送的数据是无法处理的。问题还是很多的。下面我门使用Selector,对这个例子优化下

传统的IO编程

服务端

public class NIOServer2 {
    public static void main(String[] args) throws IOException {
        //创建ServerSocketChannel,处理接入连接
        ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
        //创建Selector
        Selector selector=Selector.open();
        //设置是否为非阻塞
        serverSocketChannel.configureBlocking(false);
        //创建注册channel进selector的创立连接时间
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //绑定端口号
        serverSocketChannel.socket().bind(new InetSocketAddress(8080));
        while (true){
            if(serverSocketChannel.isOpen()){
                // 通过 Selector 选择 Channel
                int selectNums = selector.select(1000L);
                if (selectNums == 0) {
                    continue;
                }
                // 遍历可选择的 Channel 的 SelectionKey 集合
                for (SelectionKey selectKey:selector.selectedKeys()) {
                    // 忽略无效的 SelectionKey
                    if (!selectKey.isValid()) { 
                        continue;
                    }
                    //新建立的连接
                    if(selectKey.isAcceptable()){
                        //获取新连接创建的channel
                        SocketChannel socketChannel= ((ServerSocketChannel) selectKey.channel()).accept();
                        if(socketChannel!=null){
                            //设置为非阻塞
                            socketChannel.configureBlocking(false);
                            //注册进selector
                            socketChannel.register(selector,SelectionKey.OP_READ);
                        }
                    }
                    //处理读时间
                    if(selectKey.isReadable()){
                        SocketChannel socketChannel= (SocketChannel) selectKey.channel();
                        if(socketChannel!=null){
                            //读取数据
                            ByteBuffer buffer = ByteBuffer.allocate(1024);
                            int bytesRead = socketChannel.read(buffer);
                            if(bytesRead==-1){
                                socketChannel.register(selector,0);
                                socketChannel.close();
                            }else{
                                buffer.flip();
                                byte[] bytes = new byte[buffer.remaining()];
                                System.arraycopy(buffer.array(), buffer.position(), bytes, 0, buffer.remaining());
                                System.out.println(new String(bytes, "UTF-8"));
                            }

                        }
                    }
                }
            }
        }
    }
}

客户端

public static void main(String[] args) throws IOException, InterruptedException {
    SocketChannel socketChannel = SocketChannel.open();
    socketChannel.connect(new InetSocketAddress(8080));
    for (int i = 0; i < 30; i++) {

        if(socketChannel.isConnected()){
            ByteBuffer buffer=ByteBuffer.allocate(1024);
            buffer.put("hello world".getBytes());
            System.out.println("hello world");
            buffer.flip();
            while(buffer.hasRemaining()) {
                socketChannel.write(buffer);
            }
        }

    }
    socketChannel.close();

}

现在可以在针对不同的事件进行不同的处理

参考文章 ifeve.com/java-nio-al…