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);
// 处理多个连接
}