C++后端开发入门-服务器模型编程
C++后端开发入门-服务器模型编程
分时循环服务器
UDP版
服务端:
1 |
|
客户端:
1 |
|
TCP版
服务端:
1 |
|
客户端:
1 |
|
多进程TCP服务器
服务端:
1 |
|
客户端同上。
多线程并发TCP服务器
服务端:
1 |
|
I/O多路复用服务器
select
select
声明:
1 | typedef long int __fd_mask; |
在select
返回后,readfds、writefds和exceptfds会返回相应套接字。readfds包括有数据可读的(用recv
可立即收到数据的)、连接已关闭/重设/终止的、正请求建立连接的(用accept
会成功)套接字。writefds包括有数据可发出的(用send
可发送数据的)、用connect
连接成功的套接字。exceptfds包括用connect
连接失败的、有带外数据可读的套接字。
fd_set默认最大值为1024个,要改还得重新编译内核,改用下面的poll模型可规避该问题。下面宏可对fd_set进行操作:
1 | //前3个返回值void 最后一个int 参数fd为int 参数fdsetp为fd_set* |
服务端实现:
1 |
|
select和下面的poll模型中,包含大量文件描述符的数组被整体复制于用户态和内核地址空间之间,开销随数量增加而变大。select使用数组维护文件描述符,poll用链表维护,所以不需要担心数量。
poll
poll
用途与select
大致相同,其中fds参数在返回后被清零,需要自己重新设置:
1 | struct pollfd { |
常见错误值如下:
错误值 | 含义 |
---|---|
EBADF | 结构体中有文件描述符无效 |
EFAULT | fds指向地址超出进程地址空间 |
EINTR | 请求的事件之前产生一个信号 调用可重新发起 |
EINVAL | nfds超出PLIMIT_NOFILE |
ENOMEM | 内存不足 |
events与revents域常用取值如下:
事件 | 取值 | 含义 |
---|---|---|
读事件 | POLLIN | 普通或优先带数据可读 |
POLLRDNORM | 普通数据可读 | |
POLLRDBAND | 优先级带数据可读 | |
POLLPRI | 高优先级数据可读 | |
写事件 | POLLOUT | 普通或优先带数据可写 |
POLLWRNORM | 普通数据可写 | |
POLLWRBAND | 优先级带数据可写 | |
错误事件,仅作为revents域取值 | POLLERR | 发生错误 |
POLLHUP | 发生挂起 | |
POLLVAL | 描述不是打开的文件 |
该实例中将send
和recv
改为使用write
与read
。当read
读取常规文件时,若实际读取数据少于想读取的数据,则不阻塞并返回。当从终端读入输入的数据没有换行符时阻塞,否则情况不阻塞。当从网络设备读时若没接收到数据报则阻塞,读取数值少于想读取字节数也不阻塞。
1 | ssize_t write( |
服务端实现:
1 |
|
epoll
epoll可代替poll,在具有大量客户端请求时有更好的性能。select模型中,select
对所有句柄进行轮循,可近似解释为时间复杂度为$O(n)$。poll在实现方式上与select没有较大差别。epoll与poll最大都支持65535个文件描述符。
epoll通过内核与用户空间mmap
同一块内存实现,使得这块物理内存对内核和用户均可见,减少用户态和内核态之间的数据交换,内核可直接看到epoll监听的句柄。epoll采用红黑树存储所有套接字。把事件添加进来时,将该事件与相应网卡设备驱动建立回调关系,该回调函数在内核中称为ep_poll_callback
,就是将该事件添加到rdllist双向链表中。用epoll_wait
时检查rdllist中是否存在注册的事件,所以epoll模型可近似解释为时间复杂度为$O(1)$。
服务器模型分为两种工作方式。水平触发LT为默认工作方式,当一个描述符就绪,内核就会通知处理,若不进行处理,内核下次还会通知;边缘触发ET只支持非阻塞描述符,需程序保证缓冲区数据全被读/写出,描述符就绪后不会再次通知。select和poll模型只支持LT,epoll支持更高效的ET。
用epoll_create
创建一个epoll句柄,在早期epoll实现中,监控文件描述符的组织不是红黑树而是哈希表,所以这里size参数已无意义。在成功创建后可在/proc/PID/fd/下看到该fd,所以使用epoll后必须用close
关闭,否则可能导致fd被耗尽。
1 |
|
用epoll_ctl
控制epoll监控的文件描述符上的事件:
1 | typedef union epoll_data { |
op参数常用取值如下:
取值 | 含义 |
---|---|
EPOLL_CTL_ADD | 注册新的fd到epfd中 |
EPOLL_CTL_MOD | 修改已注册的fd的监听事件 |
EPOLL_CTL_DEL | 从epfd中删除一个fd |
参数event取值可以是以下某些选项的集合:
选项 | 含义 |
---|---|
EPOLLIN | 文件描述符可读,以及套接字正常关闭 |
EPOLLOUT | 文件描述符可写 |
EPOLLPRI | 文件描述符有带外数据到来 |
EPOLLERR | 文件描述符发生错误 |
EPOLLHUP | 文件描述符被挂断 |
EPOLLET | 将epoll设为ET模式 |
EPOLLONESHOT | 只监听一次事件,监听后需再次将该套接字加入epoll队列 |
用epoll_wait
收集epoll监控中已发生的事件:
1 | int epoll_wait( |
服务端实现:
1 |
|