I/O复用:一个进程或者一个线程能够同时对多个文件描述符(socket)提供服务
服务器上的进程或者线程如何将多个文件描述符统一监听,当任意一个文件描述符上有时间发生,其都能及时处理。
Linux提供了三种I/O复用:
1.select
2.poll
3.epoll (Linux独有的)
单进程单线程同时处理多个文件描述符:select
select函数原型:
int select(int nfds, struct fd_set * readfds, struct fd_set * writefds, fd_set * exceptfds, struct timeval *timeout);
int nfds:所监听的最大的文件描述符的值 + 1;//提高底层实现效率
readfds,writefds,exceptfds:分别传递用户关注的可读、可写、异常事件的文件描述符。
timeout:设置超时时间;如果timeout为NULL,则select移一直阻塞。
如果返回值>0,返回就绪文件描述符的个数
如果返回值= =0,超时
如果返回值= =-1, 出错
select使用时要注意的点:
①.如何将文件描述符分别设置到readfds writefds excefds上去?
②.如何select返回后如何知道哪些文件描述符就绪?
因此我们要了解fd_set结构体:如下
fd_set其实就是个整型数组,固定了32个元素
即为该结构体:如果每一个元素32位,4字节表示一个文件描述符(一般不会太大),最多表示32个,太浪费。
①因此,采用按位操作将文件描述符分别设置到readfds writefds excefds上去,系统提供一系列操作函数:
②与此同时,select返回可以通过FD_ISSET知道哪些文件描述符就绪
还要注意:
每次调用select之前都必须重新设置readfds; writefds; exceptfds。
select每次都会将所有的文件描述符返回。
select返回后还必须循环探测具体哪些是就绪的文件描述符。O(n)
select服务器编码流程:
selec注意:
1.记录每种事件的结构,在数组按位来记录关注的文件描述符上的事件
2.每次最多可以监听1024个文件描述符,并且其最大值1023
3.select函数返回时,通过传递的结构体变量将结果带回(就绪的文件描述符&未就绪),并且内核会修改y用户变量
3.1每次都必须循环探测哪些文件描述符就绪 O(n)
3.2每次调用select之前都必须重新设置三个结构体变量
4.select函数第一个参数为最大文件描述符值 + 1,提高底层效率
select底层实现:
①用户态有fd_set 设置一个readfds;//关注的文件描述符设置上去
②调用select时,会从用户态切换到内核态,会将readfds拷贝一份到内核的fd_set上。(值从readfds拷贝给
read)
③底层完成一系列监听将自己的read修改,返回时候将自己修改的read反馈给readfds。
最终readfds拿到的是已修改的结果,拿到的是所有的文件描述符(包含就绪的和未就绪的),因此循环探测哪些文件描述符就绪。
I/O复用使用select下TCP服务器的实现(客户端与TCP的相同):
一个客户端与服务器连接如图(连接成功):
多个客户端与服务器连接如图(连接成功):