当前位置: 首页 > 后端技术 > Java

通俗易懂的大白话——JavaNIO(一)——【宏观把握】大白话旁白,Linux下的BIO、NIO、select、epoll

时间:2023-04-01 22:03:05 Java

,要先宏观掌握,再微观掌握!所谓BIO就是阻塞IO,NIO就是非阻塞IO,什么意思呢?让我们从宏观上来理解吧!首先你要知道Linux下的Java是基于Linux系统调用实现的,那么我们先来了解下LinuxNIO是如何实现的。Java的NIO只是对Linux的NIO接口进行了封装。BIO简介刚开始学习网络编程的时候,我们通常首先接触到类似下面的服务端代码:C语言伪代码:intserverSock=socket(AF_INET,SOCK_STREAM,0);绑定(serverSock,...);听(serverSock,...);intclient_socket_fd=accept(serverSock,...);//阻塞等待客户端连接,如果有连接,返回值为客户端socket的fdread(clientSock,...);...}以上就是Linux服务器下最常见的C语言网络编程代码。bind()和listen()是常规操作,不再解释。当服务器代码执行到accept()时,它卡在这里,等待客户端连接。当一个客户端连接到服务器时,服务器代码接收到连接请求,执行accept()系统调用,返回这个客户端的socket文件的文件句柄socket_fd,然后服务器代码执行到这里的read()。我们都知道所谓的socket就是一个特殊的IO文件。读写socket文件和读写我们磁盘中的txt格式文件其实是同一个意思,只是socket文件不是磁盘上的文件,但是linux把socket也当作文件来对待,所以我们也可以通过socket文件的文件句柄号传递给read()系统调用函数读取socket文件中的内容,但是socket文件和txt磁盘文件内部具体读写代码的实现是不同的,这里,客户端socket文件的文件句柄是clientSocket。这里read()函数的主要作用是读取socket文件中的数据,有数据就返回数据,没有数据就阻塞。当服务器程序执行到read(clientSock,...)时,它会尝试读取客户端套接字文件中的内容。结果发现特殊文件里没有数据,代码卡在这里[阻塞]等待socket文件出现数据,如果有数据就返回。上面的程序很简单,但是有个问题,一次只能连接一个客户端!多来几个client,server程序阻塞在read()函数里,根本收不到,怎么办?为了从多个客户端获取连接和数据,我们主要需要做两件事:1、及时执行accept(),及时发现来自客户端的新连接;2.对每个连接的客户端循环执行read(),及时读出每个客户端socket文件的新数据。方法一:多开几个线程。使用单独的线程循环运行accept()函数,当有新的客户端连接到来时,新建一个线程循环调用read()来读取新客户端的socket文件数据。这是一种方式,但是如果有10万个客户端连接过来,那么就会创建10万个新线程,对资源的浪费很大,而且创建线程这个动作本身也是需要时间的,而且这10万个客户端连接,并不是每一个都是很active,高并发下不建议使用这种方式!方法二:多线程+select系统函数即不直接使用read()函数获取客户端socket文件中是否有新数据,而是使用另一个linux系统调用[select]获取客户??端socket文件是有没有新数据,它和read()有什么区别?最大的区别是read()一次只能监测一个socket文件中是否有新数据,而select可以同时监测多个clientsocket文件是否有数据可读!fd_setread_fds;//socket文件句柄集合【用于存放多个需要监控的fd】FD_SET(socket_fd,&read_fds);//将要监控的套接字文件句柄放入集合read_fdsintnums=select(...&read_fds,...);//监控一次是否有多个socket文件可读,如果有可读文件,返回可读文件个数FD_ISSET(scoket_fd,read_fds);//判断fd指向的文件是否可读利用上面Linux提供的系统函数,我们可以很方便的同时监控多个socket文件是否可读,不需要开那么多线程逐个检查一、但只开一个线程,循环调用select()达到用一个线程监视多个socket文件是否可读的目的,效率很高,节省系统资源!select()函数是这样工作的:在调用它的时候,把你要监听的socket文件句柄放入read_fds集合中,然后传给select()。当集合中部分socket文件可读时,select()会返回当前可读文件的个数,然后调用FD_ISSET()检查哪些文件可读,并执行相应的数据处理逻辑。intnums=select(...&read_fds,...);while(...){if(FD_ISSET(client_socket_fd_1,read_fds)){//如果client1的socket文件是可读的read(cilent_socket_fd_1,&data1);//将新数据读入data1并处理...}elseif(FD_ISSET(client_socket_fd_2,read_fds)){可以读取client2的socket文件read(cilent_socket_fd_2,&data2);}...//判断其他scoket文件是否可读}注意:select只返回可读客户端scoket文件的个数,并不直接返回具体的新数据。需要调用FD_ISSET()查看具体是哪个socket文件,而新数据需要自己调用read()函数取出来,但是此时调用read()可以节省很多时间,因为socket此时读取的文件都是有数据的,所以调用read()读取文件不需要阻塞,直接读出新的数据即可。可以看出,我们使用一个线程调用select来监听多个clientsocket文件,也就是所谓的IO多路复用。多通道是指多个客户端scoket文件;多路复用是指多个套接字文件多路复用同一个线程。与方法1相比,多路复用消耗的资源要少得多。方法一,每个线程调用read()监听一个socket文件,打开的线程过多。这样我们就专门开一个线程执行accept(),再开一个线程执行select()。得到可读socket文件的fd后,我们使用线程池从可读socket文件中取新数据,响应及时,系统资源不会无限增加,省时省力省资源!方法三:多线程+epollepoll()和select()函数是完全一样的函数,使用方法也差不多。它们都监控一次是否有多个套接字文件可读。不同的是epoll可以很快的知道集合中哪些socket文件是可读的,select()的速度比较慢。如果服务器并发比较高,往往会采样epoll。很多netty、tomcat等都使用epoll函数来实现高效的网络数据收发功能。到目前为止,如果想知道连接的clientsocket文件是否有新数据,在linux系统中是否可读,使用epoll是最高效的函数,以后还会有更高效的,即异步IO。这是以后的话题,宏观把握篇暂时不描述。