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