一、多线程并发服务器
在 高并发的 TCP 服务器 中,单线程或 fork() 多进程 方式会导致 资源浪费和性能瓶颈。因此,我们可以使用 多线程 来高效处理多个客户端的连接。
承接上文中的多进程并发服务器,代码优化目标:
1.使用 pthread 实现多线程服务器
2.每个客户端连接后,服务器创建一个独立线程进行处理
3.回显(Echo)客户端发送的消息
4.支持多个客户端同时连接
5.主线程负责监听连接,子线程负责处理客户端请求
完整代码:
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // exit()、malloc()、free()
#include <string.h> // 字符串操作
#include <unistd.h> // read(), write(), close()
#include <arpa/inet.h> // sockaddr_in, inet_addr()
#include <sys/socket.h> // 套接字 API
#include <netinet/in.h> // sockaddr_in 结构体
#include <pthread.h> // 线程 API
#define PORT 8080 // 服务器监听端口
#define BUFFER_SIZE 1024 // 缓冲区大小
#define MAX_CLIENTS 100 // 最大客户端连接数
// **线程处理客户端请求**
void *handle_client(void *arg) {
int client_fd = *((int *)arg);
free(arg); // 释放动态分配的内存
char buffer[BUFFER_SIZE];
int bytes_read;
printf("✅ 客户端线程启动,处理客户端 %d\n", client_fd);
while (1) {
memset(buffer, 0, BUFFER_SIZE); // 清空缓冲区
bytes_read = read(client_fd, buffer, BUFFER_SIZE);
if (bytes_read <= 0) {
printf("❌ 客户端 %d 断开连接\n", client_fd);
break; // 退出循环,关闭连接
}
printf("📩 收到客户端 %d 消息: %s\n", client_fd, buffer);
// **发送回显消息**
write(client_fd, buffer, bytes_read);
}
// **关闭客户端连接**
close(client_fd);
printf("关闭客户端 %d 连接\n", client_fd);
return NULL;
}
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
pthread_t thread_id;
// 1️⃣ 创建服务器套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("❌ Socket 创建失败");
exit(EXIT_FAILURE);
}
// 2️⃣ 绑定服务器地址和端口
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("❌ 绑定失败");
close(server_fd);
exit(EXIT_FAILURE);
}
// 3️⃣ 监听客户端连接
if (listen(server_fd, MAX_CLIENTS) < 0) {
perror("❌ 监听失败");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("⚡ 多线程 TCP 服务器已启动,监听端口 %d...\n", PORT);
while (1) {
printf("\n等待客户端连接...\n");
// 4️⃣ 接受客户端连接
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
if (client_fd < 0) {
perror("❌ 接受客户端连接失败");
continue; // 继续等待下一个客户端
}
printf("✅ 客户端连接成功!IP: %s, 端口: %d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// 5️⃣ 创建线程处理客户端
int *new_sock = malloc(sizeof(int)); // 动态分配内存,避免线程冲突
*new_sock = client_fd;
if (pthread_create(&thread_id, NULL, handle_client, (void *)new_sock) != 0) {
perror("❌ 线程创建失败");
close(client_fd);
free(new_sock);
} else {
pthread_detach(thread_id); // 让线程自动回收
}
}
// 6️⃣ 关闭服务器(通常不会执行到这里)
close(server_fd);
return 0;
}
✅ 代码运行步骤
- 编译:(假设文件名为
tcp_server_threads.c
)
gcc tcp_server_threads.c -o tcp_server_threads -pthread
- 运行服务器
./tcp_server_threads
输出示例:
多线程 TCP 服务器已启动,监听端口 8080...
等待客户端连接...
✅ 连接测试
方式 1:使用 telnet
telnet 127.0.0.1 8080
# 输入消息后按 Enter,服务器会返回相同的消息。
方式 2:使用 nc(Netcat)
🔹 启动多个客户端
nc 127.0.0.1 8080
输入内容,服务器会回显,如:
Hello Server
Hello Server # 服务器返回相同内容
详细步骤流程:
1. 创建 TCP 套接字 -- socket() -- 创建服务器 socket
2. 绑定 IP 和端口 -- bind() -- 监听 8080 端口
3. 监听连接 -- listen() -- 允许最多 MAX_CLIENTS 个客户端排队
4. 等待客户端连接 -- accept() -- 接受一个客户端连接
5. 创建线程 -- pthread_create() -- 让每个客户端由一个线程处理
6. 处理客户端请求 -- read() -- 读取客户端发送的数据
7. 发送回显数据 -- write() -- 把数据发回客户端
8. 关闭连接 -- close() -- 释放资源
该代码是一个基本的 TCP 多线程并发服务器,适用于 中等并发负载, 相比 fork()
,使用 pthread
可以减少资源消耗,提升并发性能。
后续代码可优化
1.使用线程池
线程池可以复用线程,避免 pthread_create() 过多消耗资源。
参考 pthread pool 机制,预创建固定数量线程,避免频繁创建销毁。
2.使用 epoll 结合线程池
结合 epoll 监听 accept(),减少 CPU 负担。
3.日志管理
服务器可以使用 syslog() 或文件写入方式记录 客户端连接信息。
4.超时处理
服务器可以设置 setsockopt() 限制客户端连接时间:
struct timeval timeout = {5, 0}; // 5 秒超时
setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
二、I/O 复用服务器(select / poll)
在 UNIX/Linux
下主要有4种 I/O 模型:
阻塞I/O: 最常用、最简单、效率最低
非阻塞I/O:可防止进程阻塞在I/O操作上,需要轮询
I/O 多路复用:允许同时对多个I/O进行控制
信号驱动I/O: 一种异步通信模型
阻塞I/O 模式是最普遍使用的 I/O 模式,大部分程序使用的都是阻塞模式的 I/O ;缺省情况下,套接字建立后所处于的模式就是阻塞 I/O 模式。很多读写函数在调用过程中会发生阻塞,例如:读操作中的 read
、recv
、recvfrom
,写操作中的 write
、send
,其他操作:accept
、connect
。
读阻塞:以 read
函数为例:
进程调用 read 函数从套接字上读取数据,当套接字的接收缓冲区中还没有数据可读,函数 read 将发生阻塞。它会一直阻塞下去,等待套接字的接收缓冲区中有数据可读。经过一段时间后,缓冲区内接收到数据,于是内核便去唤醒该进程,通过 read 访问这些数据。但如果在进程阻塞过程中,对方发生故障,那这个进程将永远阻塞下去。
写阻塞:
在写操作时发生阻塞的情况要比读操作少。主要发生在要写入的缓冲区的大小小于要写入的数据量的情况下。这时,写操作不进行任何拷贝工作,将发生阻塞。一旦发送缓冲区内有足够的空间,内核将唤醒进程,将数据从用户缓冲区中拷贝到相应的发送数据缓冲区。UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区满的情况,在UDP套接字上执行的写操作永远都不会阻塞。
非阻塞模式I/O:
当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。”
当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)。 应用程序不停的 polling
内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。也正因如此,这种模式在使用中不普遍,太浪费资源了。
fcntl()函数
一开始建立一个套接字描述符的时候,系统内核将其设置为阻塞IO模式。
可以使用函数fcntl()设置一个套接字的标志为O_NONBLOCK 来实现非阻塞。
int fcntl(int fd, int cmd, long arg);
int flag;
flag = fcntl(sockfd, F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(sockfd, F_SETFL, flag);
多路复用I/O
应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的。可是,若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂;比较好的方法是使用I/O多路复用。其基本思想是:
先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行 I/O时函数才返回。
函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。
在 高并发的 TCP 服务器 中,传统的 fork()
多进程 或 pthread
多线程 方式容易导致 资源浪费和性能瓶颈。因此,我们才使用 I/O 复用技术(select
/ poll
/ epoll
),使 单线程 就能监听 多个客户端连接,从而提高并发性能。
多路复用select/poll
/* According to POSIX.1-2001, POSIX.1-2008 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
/**********************************************************************
@brief: 多路复用,将所需要使用的或者需要关注的文件描述符放在一个集合中,当集合中的文件描述符
被触发了会去执行相应的任务
@nfds: 最大文件描述符 + 1
@readfds: 所有要读的文件文件描述符的集合
@writefds: 所有要的写文件文件描述符的集合
@exceptfds:其他要向我们通知的文件描述符
@timeout: 超时设置.
NULL:一直阻塞,直到有文件描述符就绪或出错
时间值为0:仅仅检测文件描述符集的状态,然后立即返回
时间值不为0:在指定时间内,如果没有事件发生,则超时返回。
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
@retval: 成功:返回就绪的文件描述符的个数
失败:返回-1,并且设置全局错误码
为了设置文件描述符我们要使用几个宏:
宏的形式:
void FD_ZERO(fd_set *fdset) //从fdset中清除所有的文件描述符
void FD_SET(int fd,fd_set *fdset) //将fd加入到fdset
void FD_CLR(int fd,fd_set *fdset) //将fd从fdset里面清除
int FD_ISSET(int fd,fd_set *fdset) //判断fd是否在fdset集合中
**********************************************************************/
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
/**********************************************************************
@brief: 多路复用,监管文件描述符
@fds: 要监管的文件描述符的结构体指针
struct pollfd {
int fd; /* file descriptor 希望被触发的文件描述符 用户赋值*/
short events; /* requested events 希望被触发的事件POLLIN 用户赋值*/
short revents; /* returned events 希望被触发的事件发生与否POLLIN 系统赋值*/
};
The bits that may be set/returned in events and revents are defined in <poll.h>:
//可在man手册中查询别的events和revents的选值
POLLIN There is data to read.
POLLPRI
There is some exceptional condition on the file descriptor. Possibilities
include:
* There is out-of-band data on a TCP socket (see tcp(7)).
* A pseudoterminal master in packet mode has seen a state change on the
slave (see ioctl_tty(2)).
* A cgroup.events file has been modified (see cgroups(7)).
POLLOUT
Writing is now possible, though a write larger that the available space in a
socket or pipe will still block (unless O_NONBLOCK is set).
/**
@nfds: 最大文件描述符 + 1
@timeout: >0:阻塞对应的时间(毫秒级)
=0:不阻塞
<0:一直阻塞
@retval: >0:集合中已就绪的文件描述符个数
=0:集合中没有已就绪的文件描述符
-1:poll调用失败,并且设置全局错误码
**********************************************************************/
📌 I/O 复用的三种方式
方法 | 特点 | 适用场景 |
---|---|---|
select() | 需要遍历整个文件描述符集合,最大支持 1024 个连接 | 适用于 少量连接 的情况 |
poll() | 使用链表存储,支持更多连接,但仍然需要遍历整个集合 | 适用于 中等并发 |
epoll() | 事件驱动,只处理活跃的连接,性能远高于 select /poll | 适用于 高并发服务器 |
select() 多路复用服务器
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
#define PORT 8080
#define MAX_CLIENTS 100
#define BUFFER_SIZE 1024
int main() {
int server_fd, client_fd, max_fd, activity, i;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
fd_set read_fds, master_fds;
// 1️⃣ 创建 TCP 套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("❌ Socket 创建失败");
exit(EXIT_FAILURE);
}
// 2️⃣ 绑定服务器地址和端口
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("❌ 绑定失败");
close(server_fd);
exit(EXIT_FAILURE);
}
// 3️⃣ 监听客户端连接
if (listen(server_fd, MAX_CLIENTS) < 0) {
perror("❌ 监听失败");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("`select()` 多路复用服务器已启动,监听端口 %d...\n", PORT);
// 4️⃣ 初始化 `select` 的文件描述符集合
FD_ZERO(&master_fds);
FD_SET(server_fd, &master_fds);
max_fd = server_fd;
while (1) {
read_fds = master_fds; // 每次循环都复制 `master_fds`
// 5️⃣ 监听多个文件描述符 `select`
activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
if (activity < 0) {
perror("❌ `select` 调用失败");
continue;
}
// 6️⃣ 处理新客户端连接
if (FD_ISSET(server_fd, &read_fds)) {
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
if (client_fd < 0) {
perror("❌ 客户端连接失败");
continue;
}
printf("新客户端连接:%s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
FD_SET(client_fd, &master_fds);
if (client_fd > max_fd) {
max_fd = client_fd;
}
}
// 7️⃣ 处理已连接的客户端数据
for (i = server_fd + 1; i <= max_fd; i++) {
if (FD_ISSET(i, &read_fds)) {
memset(buffer, 0, BUFFER_SIZE);
int bytes_read = read(i, buffer, BUFFER_SIZE);
if (bytes_read <= 0) {
printf("❌ 客户端断开连接\n");
close(i);
FD_CLR(i, &master_fds);
} else {
printf("📩 客户端消息: %s\n", buffer);
write(i, buffer, bytes_read); // 回显
}
}
}
}
close(server_fd);
return 0;
}
poll() 多路复用服务器
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080
#define MAX_CLIENTS 100
#define BUFFER_SIZE 1024
int main() {
int server_fd, client_fd, i;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
struct pollfd fds[MAX_CLIENTS];
int nfds = 1;
// 1️⃣ 创建 TCP 套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("❌ Socket 创建失败");
exit(EXIT_FAILURE);
}
// 2️⃣ 绑定服务器地址和端口
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("❌ 绑定失败");
close(server_fd);
exit(EXIT_FAILURE);
}
// 3️⃣ 监听客户端连接
if (listen(server_fd, MAX_CLIENTS) < 0) {
perror("❌ 监听失败");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("`poll()` 多路复用服务器已启动,监听端口 %d...\n", PORT);
fds[0].fd = server_fd;
fds[0].events = POLLIN;
while (1) {
// 4️⃣ 监听多个文件描述符 `poll`
int activity = poll(fds, nfds, -1);
if (activity < 0) {
perror("❌ `poll` 调用失败");
continue;
}
// 5️⃣ 处理新客户端连接
if (fds[0].revents & POLLIN) {
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
if (client_fd < 0) {
perror("❌ 客户端连接失败");
continue;
}
printf("新客户端连接:%s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
fds[nfds].fd = client_fd;
fds[nfds].events = POLLIN;
nfds++;
}
// 6️⃣ 处理已连接的客户端数据
for (i = 1; i < nfds; i++) {
if (fds[i].revents & POLLIN) {
memset(buffer, 0, BUFFER_SIZE);
int bytes_read = read(fds[i].fd, buffer, BUFFER_SIZE);
if (bytes_read <= 0) {
printf("❌ 客户端断开连接\n");
close(fds[i].fd);
fds[i] = fds[nfds - 1]; // 移除断开的客户端
nfds--;
} else {
printf("📩 客户端消息: %s\n", buffer);
write(fds[i].fd, buffer, bytes_read); // 回显
}
}
}
}
close(server_fd);
return 0;
}
使用 epoll 的高并发服务器(C 语言)
select()
和 poll()
适用于 1000 以内的连接,但随着连接数增加,性能下降。对于高并发服务器,建议使用 epoll()
(Linux) 或 kqueue()
(BSD/macOS)。
epoll
是 Linux 下 高效的 I/O 复用方式,相比 select()
和 poll()
,它支持:
- O(1) 事件触发:只处理活跃的文件描述符,不用遍历整个
fd_set
。 - 支持大规模并发:适用于 上万级别的连接,比
select()
/poll()
性能高很多。 - Edge Trigger (ET) & Level Trigger (LT):支持 边缘触发 和 水平触发,进一步优化性能。
epoll 关键 API
函数 功能
epoll_create1(0) 创建 epoll 实例,返回 epoll_fd
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) 添加监听的 fd
epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) 修改监听的 fd
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &event) 删除监听的 fd
epoll_wait(epoll_fd, events, MAX_EVENTS, timeout) 等待事件触发
epoll
多路复用服务器(代码实现)(重点)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <fcntl.h>
#define PORT 8080 // 服务器监听端口
#define MAX_EVENTS 1000 // epoll 最大监听事件数
#define BUFFER_SIZE 1024 // 缓冲区大小
// **🔹 设置 fd 为非阻塞模式**
void set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
int main() {
int server_fd, client_fd, epoll_fd, event_count, i;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
struct epoll_event event, events[MAX_EVENTS];
char buffer[BUFFER_SIZE];
// 1️⃣ 创建 TCP 套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("❌ Socket 创建失败");
exit(EXIT_FAILURE);
}
// 2️⃣ 绑定服务器地址和端口
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("❌ 绑定失败");
close(server_fd);
exit(EXIT_FAILURE);
}
// 3️⃣ 开始监听
if (listen(server_fd, MAX_EVENTS) < 0) {
perror("❌ 监听失败");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("⚡ `epoll` 服务器启动,监听端口 %d...\n", PORT);
// 4️⃣ 创建 epoll 实例
epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("❌ epoll_create1 失败");
close(server_fd);
exit(EXIT_FAILURE);
}
// 5️⃣ 设置 server_fd 为非阻塞模式,并添加到 epoll 监听
set_nonblocking(server_fd);
event.events = EPOLLIN; // 监听可读事件(LT 模式)
event.data.fd = server_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event);
while (1) {
// 6️⃣ 等待事件触发
event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
for (i = 0; i < event_count; i++) {
if (events[i].data.fd == server_fd) {
// 7️⃣ 处理新客户端连接
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
if (client_fd < 0) {
perror("❌ 接受客户端连接失败");
continue;
}
printf("✅ 新客户端连接:%s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
set_nonblocking(client_fd); // 设置非阻塞模式
event.events = EPOLLIN | EPOLLET; // 监听可读事件,ET 模式
event.data.fd = client_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event);
} else {
// 8️⃣ 处理客户端数据
int client_fd = events[i].data.fd;
memset(buffer, 0, BUFFER_SIZE);
int bytes_read = read(client_fd, buffer, BUFFER_SIZE);
if (bytes_read <= 0) {
printf("❌ 客户端断开连接\n");
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);
close(client_fd);
} else {
printf("📩 客户端消息: %s\n", buffer);
write(client_fd, buffer, bytes_read); // 回显
}
}
}
}
close(server_fd);
close(epoll_fd);
return 0;
}
✅ 代码运行步骤
- 编译(假设文件名为
epoll_server.c
)
gcc epoll_server.c -o epoll_server
- 运行服务器
./epoll_server
输出示例:
`epoll` 服务器启动,监听端口 8080...
✅ 连接测试
📌 使用 nc(Netcat)
nc 127.0.0.1 8080
输入内容,服务器会回显,如:
Hello Server
Hello Server # 服务器返回相同内容
epoll
工作模式
🔹 水平触发(LT,Level Trigger)
- 默认模式,事件未处理时会 持续触发。
- 适用于阻塞 I/O,确保数据不会丢失。
🔹 边缘触发(ET,Edge Trigger)
- 仅在状态变化时触发,不会重复触发。
- 必须使用非阻塞 I/O,否则可能丢失数据。
✅ epoll vs select / poll
特点 | select() | poll() | epoll() |
---|---|---|---|
最大连接数 | 1024(Linux 默认) | 无限制(但扫描所有) | 无限制(事件驱动) |
性能 | O(n) ,遍历 fd_set | O(n) ,遍历 pollfd | O(1) ,只处理活跃连接 |
适用场景 | 少量连接(<1000) | 中等连接数 | 高并发(>10000) |
代码可优化
使用线程池
epoll_wait() 只负责监听,线程池 处理数据,提高吞吐量。
使用 EPOLLET(边缘触发)
结合 非阻塞 read(),减少 epoll_wait() 触发次数,提高效率。
TCP SO_REUSEADDR
避免服务器重启时 bind() 失败:
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
该代码是一个高效的 TCP 并发服务器,使用 epoll 事件驱动,适用于大规模连接。 相比 select()
,epoll
在高并发情况下性能更好,是 Linux 服务器的首选方案! 🎯
以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!