网络io之多路复用

select

       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>
           struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };
       int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

server端

  1. server端关键是把server监听套接字srvfd本身当作一个普通套接字,交给select处理。如果对srvfd特殊处理,将会需要非阻塞的accept,然后调用select处理client的连接,这样将两者串行起来了,统一交给select可以实现统一监听,并行操作。
  2. 把srvfd本身当作一个普通套接字后,先检查srvfd有没有新连接,有的话将该连接加入到监听套接字集中。
  3. 然后遍历所有的被监听的套接字,一一进行io处理。剩下的关键就是对select几个参数的维护了。
  4. select几个参数的变化:
    • nfds:因为file descriptor是从0开始的,所以nfds的值为maxfd+1。需要一个参数记录这个maxfd。
    • readfds
    • writefds
    • exceptfds:这三个,由于select需要修改每个fd对应的标志位来表示哪个有请求到来,所以需要在调用select之前对这三个进行一个备份,然后有新连接到来或者连接关闭时,也是修改备份的描述符集。调用select前拷贝一下备份的描述符集即可。
    • timeout指定超时时间,linux 上select会修改这个时间,来表示距离超时剩余多少时间。
  5. 注意,因为这里监听了很多个套接字,但是具体是哪些套接字,并不得知(因为不是直接连续的),所以需要一个数组来记录有哪些套接字,也即一个套接字池。记录下当前的最大套接字,能避免不必要的套接字io状态检测,优化效率(也可以用容器,用多少申请多少,对于套接字集来说,申请和销毁特别轻量,用套接字池并没有多大优势)。(select的三个套接字字符集中维持了一份,但是我们不可访问,所以还需要自己维持一份)
  6. select的返回值:>0时表示三个描述符集中被设置的描述符的个数的和,=0表示超时;<0表示出错。

client端

对client端来说,一般只要监听用户输入和server端数据到达两个套接字即可。一些注意事项与server端类似。

poll

#include <poll.h>
struct pollfd {
    int   fd;         /* file descriptor */
    short events;     /* requested events */
    short revents;    /* returned events */
};
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

poll和select的显著不同是,poll的参数是一个pollfd数组,pollfd里面有file descriptor和具体要监听的事件events和调用后该fd上的事件信息revents。events和revents上的时间可以通过各种标记位出来。如果fd为负值,则events被忽略,revents被设置为0; 如果events被设置为0,则所有的事件被忽略,revents为0;如果所有pollfd中任何事件都没有指定,则poll等待任何一个事件到达就返回。

poll中的事件

server端

  • 同样要把srvfd当作一个普通的套接字交给poll监听,因为poll中fd非连续,是有选择的,所以使用是要比select简单,记录最大的数组索引,进行监听和检验即可,其他和select类似。
  • 错误处理时:一般与POLLRDNORM|POLLERR进行或操作。
  • read返回0表示EOF,对端关闭。
  • poll返回值:同select一样,0超时;-1出错;大于0表示有事件到来的fd的个数。

client端

client就是从一个fd读,写入另一个fd。

epoll

epoll中的事件

关于pollfd中时间的标志位掩码,详见epoll_ctl,这里选几个常见事件:
- EPOLLIN关联的fd可以读取数据了
- EPOLLOUT关联的fd可以写数据了
- EPOLLRDHUP关联的fd被对端关闭,或者被对端关闭了连接的写半连接。从Linux 2.6.17开始有。
- EPOLLHUP关联的fd上hung up了,epoll_wait会自动监听这个事件,不需要手动添加。注意:在从channel(pipe、stream socket)中读时,这个事件仅仅表示对端关闭了对端的channel,接下来对该channel的读,只有在channel中的数据都被读取后才会返回0(end of file)。
- EPOLLERR关联的fd上发生了错误,epoll_wait会自动报告这个事件,不用手动添加。(如果pipe的读半边被关闭,写半边会受到这个错误)

epoll_ctl的操作类型

  • EPOLL_CTL_ADD
  • EPOLL_CTL_MOD
  • EPOLL_CTL_DEL

猜你喜欢

转载自blog.csdn.net/u013319359/article/details/81272508