fdset 集合:(就是说)
- fd_set是一个位图(bitmap)结构
- 每个位代表一个文件描述符
- 0表示不在集合中,1表示在集合中
fd_set结构(简化):
[0][1][2][3][4][5]...[1023]
0 0 1 0 1 0 0
↑ ↑
| |
sockfd clientfd
// select方式
fd_set rfds;
select(maxfd+1, &rfds, NULL, NULL, NULL);// epoll方式
struct epoll_event events[1024];
epoll_wait(epfd, events, 1024, -1);
maxfd就是1023,rfds就是read fds 读事件,
select的返回值是就绪事件操作如下:
int ret = select(nfds, &readfds, &writefds, &exceptfds, &timeout);
if (ret > 0) {// 有事件发生printf("有 %d 个文件描述符就绪\n", ret);// 检查读事件if (FD_ISSET(sockfd, &readfds)) {// 处理读事件}// 检查写事件if (FD_ISSET(sockfd, &writefds)) {// 处理写事件}// 检查异常事件if (FD_ISSET(sockfd, &exceptfds)) {// 处理异常事件}
}
fd操作如下:
// 清空集合
FD_ZERO(&rfds);
// 结果:[0][0][0][0][0]...[0]// 添加fd到集合
FD_SET(sockfd, &rfds);
// 例如sockfd=2:[0][0][1][0][0]...[0]// 从集合中删除fd
FD_CLR(sockfd, &rfds);
// 例如sockfd=2:[0][0][0][0][0]...[0]// 检查fd是否在集合中
FD_ISSET(sockfd, &rfds);
// 返回1表示在集合中,0表示不在
a) 一个连接一个线程:
优点:
- 实现简单
- 逻辑清晰
- 适合并发量小的场景
缺点:
- 线程资源消耗大
- 线程切换开销大
- 不适合高并发
select
优点:
- 单线程处理多连接
- 资源消耗小
- 适合中等并发
缺点:
- 效率较低
- 连接数有限
- 需要轮询
epoll
优点:
- 高效的事件通知
- 适合高并发
- 资源消耗小
缺点:
- 实现较复杂
- 需要系统支持
// 多线程方式
void *client_thread(void *arg) {int clientfd = *(int *)arg;while (1) {recv(clientfd, buffer, 1024, 0);// 处理数据}
}// epoll方式
while (1) {epoll_wait(epfd, events, 1024, -1);for (i = 0; i < nready; i++) {if (events[i].data.fd == sockfd) {// 处理新连接} else {// 处理数据}}
}
// 方式1:一个连接一个线程
while (1) {int clientfd = accept(sockfd, ...);pthread_t thid;pthread_create(&thid, NULL, client_thread, &clientfd);
}// 方式2:单线程处理多个连接(select)
while (1) {select(maxfd+1, &rset, NULL, NULL, NULL);// 处理多个连接
}// 方式3:单线程处理多个连接(epoll)
while (1) {epoll_wait(epfd, events, 1024, -1);// 处理多个连接
}