TCP并发服务器
单循环服务器:服务端同一时刻只能处理一个客户端的任务
并发服务器:服务端同一时刻可以处理多个客户端的任务
1 TCP服务端并发模型
1.1 多进程(fork)
父进程监听端口,每接一个客服端就fork子进程处理该连接
优点:个进程独立,稳定性和安全性高(子进程崩溃不影响父进程)
缺点:
1.2 多线程(pthread)
主线程监听端口,每接一个客户端就创建子线程处理该连接
优点:线程开销资源比进程小,相同资源环境下,并发量比进程大。
内存共享方便
1.3 线程池
为了解决多线程或者多进程模型,在服务器运行过程,频繁创建和销毁线程(进程)带来的时间消耗问题。
基于生产者和消费者编程模型,以及任务队列等,实现的一套多线程框架。
1.4 I/O多路复用
2.多进程模型
操作步骤:
1.主进程从初始化:创建监听socket()、绑定端口bind()、设置监听队列listen()。
2.循环等待连接:主进程accept()阻塞等待客户端连接,成功后获得通信套接字。
3.创建子进程处理连接:主进程调用fork()创建子进程,子进程继承通信套接字,负责与客户端收发数据(recv()/send())。
主进程关闭监听套接字,回到accept()继续等待新连接。
4。子进程资源回收:子进程处理完客户端请求后退出,主进程需通过wait()/waitpid()回收子进程资源,避免产生“僵尸进程”
优点:个进程独立,稳定性和安全性高(子进程崩溃不影响父进程)
缺点:
3.多线程
主线程创建监听套接字,循环等待客户端连接
每接收到一个客户端连接,创建新线程处理该客户端的通信
子线程负责与客户端的读写交互,主线程继续等待新连接
优点:
缺点:
4. IO多路复用
I-->O:fd
对多个文件描述符的读写可以复用一个进程。
在不创建新的进程和线程的前提下,使用一个进程实现对多个文件读写的同时监测。
fgets(stdin);
recv(connfd);
阻塞IO模式:
1. 多个任务之间是同步的效果
(1)select
(2)poll
(3)epoll
4.1 select实现IO多路复用
select通过监控多个文件描述符的读写/异常事件,实现单进程/线程同时处理多个I/O操作,核心是阻塞等待多个FD就绪,避免为每个FD单独创建进程线程。
4.1.1 操作流程
1. 创建文件描述符集合 fd_set
2. 添加关注的文件描述符到集合 FD_SET();
3. 使用select传递集合表给内核,内核开始监测事件 select()
4. 当内核监测到事件时,应用层select将解除阻塞,并获得相关的事件结果
5. 根据select返回的结果做不同的任务处理
关键函数与参数
#include <sys/select.h> 核心函数:监控 FD 集合,返回就绪 FD 数量(超时返回0,错误返回-1) int select(int nfds, fd_set *readfds, fd_set *writedfds, fd_set *exceptfds, struct timeval *timeout); 参数: nfds : 关注的最大文件描述符+1 readfds:读事件的文件描述符集合 writefds:写事件的文件描述符集合 exceptfds:其他事件的文件描述符集合 timeout:设置select监测时的超时时间 NULL : 不设置超时时间(select一直阻塞等待) 辅助宏:操作 fd_set 集合 void FD_ZERO(fd_set *set); 清空集合(初始化必做) void FD_SET(int fd, fd_set *set); 将 FD 加入集合 void FD_CLR(int fd, fd_set *set); 将 FD 从集合中移除 int FD_ISSET(int fd, fd_Set *set); 检查 FD 是否在“就绪”集合中(返回0表示就绪)
select特点:
1.使用位图实现对文件描述符集合的保存,最多允许同时监测1024个文件描述符。
2.需要应用层和内核层的反复数据(文件描述符集合表)拷贝。
3.返回的集合表需要需要遍历寻找到达的事件。
4.只能工作在水平触发模式(低速模式),不能工作在边沿触发模式(高速模式)。
epoll特点:
1.使用红黑树(二叉树)实现文件描述符集合的存储,没有文件描述符上限限制,提高查找小琉球。
2.
epoll:
1. 创建文件描述符集合 : int epoll_create(int size);
2. 添加关注的文件描述符:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
3. epoll通知内核开始进行事件监测 :int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
4. epoll返回时,获取到达事件的结果
5. 根据到达事件做任务处理
函数原型:
#include <sys/epoll.h> int eopll_create(int size);
功能:
通知内核创建文件描述符集合
参数:
size:监测的文件描述符个数
返回值:
成功:文件描述符(代表内核创建的集合)
失败:-1,errno
函数原型:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:
对epoll的文件描述符集合进行操作
参数:
epfd:由epoll_create()创建的epoll文件描述符
op:对文件描述符集合进行的操作
EPOLL_CTL_ADD : 添加文件描述符到集合
EPOLL_CTL_MOD : 修改集合中的文件描述符
EPOLL_CTL_DEL :删除集合中的文件描述符
fd:要操作的文件描述符
event:文件描述符对应的事件
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events:文件描述符的事件:
EPOLLIN: 读事件
EOPLLOUT:写事件
data.fd : 关注的文件描述符返回值:
成功:0
失败:-1
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
功能:通知内核开始监测文件描述符的事件
参数:
epfd:监测的文件描述符集合
events:保存返回的到达事件的结果(数组)
struct epoll_event evs[MAX_FD_CNT];
evs;
maxevents:最大的事件个数
timeout:监测的超时时间
-1 :不设置超时(一直阻塞)返回值:
成功:到达的事件的个数
失败:-1
epoll
1.创建文件描述符集合:int epoll_create(int size);
int epoll_create(int size);
功能:通知内核创建文件描述符集合
参数:
size:检测的文件描述符个数
返回值:
成功:文件描述符(代表内核创建的集合)
失败:-1,errno
int epoll_ctl(int epfd, int op, struct epoll_event *evtt
ent)