linux 网络编程(3) --- 高并发服务器
高并发服务器
三种实现并发服务器
- 阻塞式
- 非阻塞忙轮询式
- 响应式 — 多路IO转接(能效最好)
多路IO转接
也叫做多任务IO服务器。该类服务器实现的主旨思想是,不再由应用程序自己监视客户端连接, 取而代之由内核代替程序监视文件
select
函数
1 | int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict timeout); |
对fd_set
的一些操作函数
1 | void FD_CLR(int fd, fd_set *set); 将一个文件描述符从描述符中移除 |
使用select
搭建高并发服务器
- 无需多线程和多进程即可实现多线程和多进程的效果(单线程/进程监听多个端口)
大体思路
1 | lfd = socket(); 创建套接字 |
具体代码逻辑
1 |
|
select
优缺点
缺点:
- 监听上限受文件描述符的限制, 最大是1024个
- 检测满足条件的
fd
, 自己添加业务逻辑来提高效率。提高了编码的难度。(注意:由于设计的问题,才导致了这个现象, 性能不低)
优点:
- 跨平台
poll
函数
半成品, 可以不学
1 | int poll(struct pollfd fds[], nfds_t nfds, int timeout); |
使用方法
1 | struct pollfd pfds[1024]; |
poll
优缺点
优点:
- 自带数组结构,可将监听事件集合和返回事件集合分离
- 可以拓展监听上限,可超出1024限制
缺点:
- 不能跨平台
- 无法直接定位满足监听事件的文件描述符,编码难度较大。
epoll
函数
epoll
是linux下提供多路复用io借口select/poll
的增强版,可以显著的提高程序在大量并发连接中只有少量活跃的情况下系统cpu的利用率。目前epoll
是大规模并发网络程序中的热门首选模型。
查看一个进程可以打开的socket描述符的上限
1 | cat /proc/sys/fs/file-max |
epoll_create
函数
1 | 作用:创建一个红黑二叉树 |
epoll_ctl
函数
1 | 作用:设置监听红黑树 |
epoll_wait
函数
1 | 作用:监听事件 |
使用epoll实现多路IO的思路
1 | lfs = socket(); // 监听连接事件 |
使用epoll
搭建高并发服务器
1 |
|
事件触发模型
有两种触发模型
ET
边缘触发:有数据到来的时候才会触发,不管缓冲区中有没有数据
缓冲区剩余未读尽的数据不会导致epoll_wait
触发,新的事件满足才会触发。
1 | struct epoll_event event; |
LT
水平触发(默认):只要有数据就会触发
缓冲区剩余未读尽的数据会导致epoll_wait
触发
1 | struct epoll_event event; |
比较:
- LT是缺省的工作方式, 并且同时支持
block
的和no-block
的两种socket.这样的模式编码出错概率会比较小, 同时select/poll
都是这种模型的代表。当只需要用到一个文件的一部分的时候(例如读取一个文件的属性),就可以使用此模式。 - ET是高速工作模式(推荐使用这种),只支持
no-block
socket,当文件描述符从未就绪变成就绪时,内核通过epoll告诉你。若没有io操作,使得文件变为未就绪态,内核则不会再次通知。通常与忙轮询相结合
fcntl
实现文件的非阻塞(读数据的时候的非阻塞)
结论:
epoll
的ET模式是高效模式,但是只支持非阻塞模式
设计思路:
1 | flag = fcntl(cfd, F_GETFG); //设置非阻塞 |
代码实现
1 |
|
epoll
优缺点
优点:
- 高效
- 可以突破文件描述符的最大上限
缺点:
- 不能跨平台
epoll
反应堆模型
epoll
ET模式 + 非阻塞,轮询 +void *ptr
对比:
原来:
socket,bind,listen
–epoll_create
–epoll_ctl
–epoll-wait
– 对应监听fd
有事件产生 – 返回监听满足数组 – 判断返回的元素 –lfd
满足accept
/cfd
满足处理事物 –write
反应堆:不但要监听
cfd
的读事件,还要监听cfd
的写事件socket,bind,listen
–epoll_create
–epoll_ctl
–epoll-wait
– 对应监听fd
有事件产生 – 返回监听满足数组 – 判断返回的元素 –lfd
满足accept
/cfd
满足处理事物 -> 将cfd
从监听红黑树上摘下 ->EPOLLOUT
-> 回调函数 ->epoll_ctl
->EPOLL_CTL_ADD
重新放回红黑树上,监听写事件 -> 等待epoll_wait
返回(说明cfd
可写) ->write
写 ->cfd
从监听树上摘下 ->EPOLLIN
->epoll_wait
->EPOLL_CTL_ADD
重新放回红黑树上,监听写事件 ->epoll_wait
代码实现
1 | /* epoll反应堆模型 |
线程池
与多路IO转接的区别:
- 多路IO转接处理的是客户端和服务器的连接和传输数据的问题
- 线程池处理的是服务器在拿到数据以后,处理数据的问题
线程池结构体
1 | struct threadpool_t { |
线程池main
架构
1 | 1. main |
代码实现
1 |
|
UDP服务器
TCP通信和UDP通信的优缺点:
**TCP:**面向连接的,可靠数据包传输
- 对于不稳定的网络层,采用完全弥补的通信方式 —> 丢包重传
优点:
- 稳定
- 数据的流量,速度,顺序稳定
缺点:
- 传输速度慢,效率相对较低,系统资源开销大。
**使用场景:**数据的完整性要求较高, 不追求效率。(大数据,文件的传输)
**UDP:**无连接的,不可靠的数据包传递
- 对于不稳定的网络层,采取完全不弥补的通信方式 —> 默认还原网络状况
优点:
- 传输速度快,效率相对较高, 系统资源开销小。
缺点:
- 不稳定
- 数据的流量,速度,顺序不稳定
使用场景:时效性要求较高,稳定性其次。(游戏,视频会议,视频电话)
可以在应用层进行数据校验,从而来弥补udp的不足
UDP实现的C/S模型
accept()和connect()被舍弃
recv()/ send()
只能用于TCP通信。用来代替read()/write()
server:
1 | lfd = socket(AF_INET, SOCK_DGRAM, 0); |
client:
1 | connfd = socket(AF_INET, SOCK_DGRAM, 0); |
recvfrom函数
1 | 作用:连接并从一个地方读取数据 |
sendto函数
1 | 作用:向指定的地方发送数据 |
代码实现
server.cpp
1 |
|
client.cpp
1 |
|
本地套接字
**IPC(进程间通信):**pipe, fifo, mmap, 信号, 本地套接字(domain)
对比网络编程TCP C/S模型:
- socket函数的参数domain:应取
AT_UNIX
, type:无限制, protocal:无限制 - bind函数的地址结构:取
sockaddr_un
类型 - 初始化
1 | struct sockaddr_un { |
- 获取地址类型的大小需要用
offsetof()
函数
1 | 作用:获取成员的偏移量 |
1 | len = offsetof(struct sockaddr_un, sun_path) + strlen("srv.socket"); |
bind
函数创建成功,会创建一个socket。因此,为了保证bind
成功,通常会在bind
前,unlink()
客户端不能依赖隐式绑定。并且在通信建立的过程中,创建且初始化2个地址结构:
- client_addr -> bind
- server_addr -> connect
注意点:
server和client的socket文件都是伪文件,即不占用磁盘的空间(通过bind函数创建出来的)
代码实现
注意:在本代码中,server和client要在同一个目录下
server
1 |
|
client
1 |
|