NIO底层实现
背景
当前,Java工程师需要开发网络应用程序,Netty几乎是首选,下面简单列一下Netty的使用场景:
-
构建高性能、低延迟的java中间件,如MQ,RPC框架等。
-
基于其它应用层协议的基础通信框架,如开发IM程序相关的WebSocket等。
-
其它领域:游戏等。
而Netty底层是基于NIO开发的,NIO是java 1.4版本开始提供的非阻塞通信API。和之前的BIO相比,能够实现IO多路复用,实现单线程同时处理多个连接请求和IO。一个典型的NIO程序如下:
public class Main { public static void main(String[] args) throws IOException { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(9000)); serverSocketChannel.configureBlocking(false); //注册selector事件 Selector selector = Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("服务器启动..."); while (true) { //阻塞等待事件发生 selector.select(); //获取selector中注册的全部事件的SelectionKey实例 Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); if (selectionKey.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel(); SocketChannel client = server.accept(); client.configureBlocking(false); //注册读事件,需要向客户端发数据可注册写事件 client.register(selector, SelectionKey.OP_READ); System.out.println("客户端连接成功"); } else if (selectionKey.isReadable()) { SocketChannel client = (SocketChannel) selectionKey.channel(); ByteBuffer buffer = ByteBuffer.allocate(128); int len = client.read(buffer); if (len > 0) { System.out.println("收到客户端消息:" + new String(buffer.array(), StandardCharsets.UTF_8)); } else if (len == -1) { System.out.println("客户端断开连接"); client.close(); } } //从事件集合中移除本次已处理的key,防止下次重复处理 iterator.remove(); } } } }其中实现IO多路复用的关键代码是以下三句:
//创建selector Selector selector = Selector.open(); //注册监听 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //阻塞等待事件发生 selector.select();
通过将所有的连接事件和IO事件注册到操作系统,操作系统收到相应事件后,进行回调,告知上层发生相应事件,实现了单线程非阻塞地处理多个连接请求。下面对底层实现做简要分析。
Linux系统函数
linux系统提供了三个系统函数,分别是:
//创建一个epoll文件描述符
int epoll_create(int size);
//向epoll中添加、删除或者修改事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//等待事件发生,其中timeout表示该方法需阻塞的时长:若置为-1,则无限阻塞,直到有事件发生;若置为0,则立即返回,不管有没有事件发生;若置为大于0的值,则至多阻塞timeout时长后返回
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
执行Selector.open()时会触发epoll_create函数;
而注册事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT)发生时,NIO会将该事件存到一个array或者map;
最后执行selector.select()时,会将上述array或者map中未注册的或者有修改的事件使用epoll_ctl函数进行注册,最后调用epoll_wait方法监听事件的发生。
关于epoll的原理,参见:epoll原理 epoll原理2
IO多路复用的理解:

其中第七部,需要解析包体,根据端口号找到对应的socket

浙公网安备 33010602011771号