Linux网络编程常见服务器模型详解 select、poll、epoll、libevent
·
Linux网络编程常见服务器模型详解
服务器模型概述
服务器模型决定如何处理并发连接,核心区别在于I/O处理方式和并发策略。主要分为:
- 阻塞式模型(单进程/线程)
- 多进程/多线程模型
- I/O多路复用模型
- 异步I/O模型
一、基本服务器模型
1. 单进程服务器
工作原理:
- 单线程顺序处理请求
- 阻塞式I/O操作
- 一次只能服务一个客户端
框架概念:
C语言实现:
// 服务端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8888
#define BUFFER_SIZE 1024
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
// 创建socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
while(1) {
printf("Waiting for connection...\n");
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
continue;
}
// 处理请求
read(new_socket, buffer, BUFFER_SIZE);
printf("Received: %s\n", buffer);
send(new_socket, buffer, strlen(buffer), 0);
close(new_socket);
}
return 0;
}
客户端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8888
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
char *hello = "Hello from client";
char buffer[1024] = {0};
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("\n Socket creation error \n");
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
printf("\nInvalid address\n");
return -1;
}
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
printf("\nConnection Failed \n");
return -1;
}
send(sock, hello, strlen(hello), 0);
read(sock, buffer, 1024);
printf("Server: %s\n", buffer);
return 0;
}
优缺点:
- ✅ 优点:实现简单,资源消耗少
- ❌ 缺点:无法并发,客户端需排队等待
2. 多进程服务器
工作原理:
- 主进程接受连接
- 为每个客户端fork子进程处理请求
- 父子进程独立地址空间
框架概念:
C语言实现:
// 服务端
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8888
#define BUFFER_SIZE 1024
void handle_client(int client_socket) {
char buffer[1024];
read(client_socket, buffer, 1024);
printf("recv client buffer = %s\n",buffer);
send(client_socket, buffer, strlen(buffer), 0);
close(client_socket);
exit(0);
}
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
// 创建socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
signal(SIGCHLD, SIG_IGN); // 防止僵尸进程
while(1) {
int new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
pid_t pid = fork();
if (pid == 0) { // 子进程
close(server_fd);
handle_client(new_socket);
} else { // 父进程
close(new_socket);
}
}
}
优缺点:
- ✅ 优点:真正并发处理
- ❌ 缺点:进程创建开销大,进程间通信复杂
3. 多线程服务器
工作原理:
- 主线程接受连接
- 为每个客户端创建线程
- 共享进程地址空间
框架概念:
C语言实现:
// 服务端
#include <pthread.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8888
#define BUFFER_SIZE 1024
void *client_handler(void *arg) {
int sock = *(int*)arg;
char buffer[1024];
read(sock, buffer, 1024);
printf("recv client buffer = %s\n",buffer);
send(sock, buffer, strlen(buffer), 0);
close(sock);
pthread_exit(NULL);
}
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
// 创建socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
while(1) {
int *new_sock = malloc(sizeof(int));
*new_sock = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
pthread_t thread;
pthread_create(&thread, NULL, client_handler, (void*)new_sock);
pthread_detach(thread);
}
}
优缺点:
- ✅ 优点:线程开销小于进程
- ❌ 缺点:需处理线程同步,线程过多影响性能
4. 线程池模型
工作原理:
- 预创建N个工作线程
- 任务队列存储客户端连接
- 线程从队列取任务处理
框架概念:
C语言实现:
#include <pthread.h>
#include <semaphore.h>
#include <pthread.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8888
#define BUFFER_SIZE 1024
#define THREAD_POOL_SIZE 5
typedef struct {
int client_sock;
} Task;
Task task_queue[256];
int queue_size = 0;
pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
sem_t queue_sem;
void *worker_thread(void *arg) {
while(1) {
sem_wait(&queue_sem);
pthread_mutex_lock(&queue_mutex);
Task task = task_queue[--queue_size];
pthread_mutex_unlock(&queue_mutex);
// 处理请求
char buffer[1024];
read(task.client_sock, buffer, 1024);
printf("recv client buffer = %s\n",buffer);
send(task.client_sock, buffer, strlen(buffer), 0);
close(task.client_sock);
}
return NULL;
}
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
// 创建socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
sem_init(&queue_sem, 0, 0);
// 创建线程池
pthread_t pool[THREAD_POOL_SIZE];
for(int i=0; i<THREAD_POOL_SIZE; i++) {
pthread_create(&pool[i], NULL, worker_thread, NULL);
}
while(1) {
int client_sock = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
pthread_mutex_lock(&queue_mutex);
task_queue[queue_size++].client_sock = client_sock;
pthread_mutex_unlock(&queue_mutex);
sem_post(&queue_sem); // 通知工作线程
}
}
优缺点:
- ✅ 优点:减少线程创建开销,控制并发量
- ❌ 缺点:实现复杂,需任务队列管理
二、I/O多路复用服务器模型
核心概念
工作原理:
- 单线程监控多个文件描述符
- 当fd就绪时进行I/O操作
- 事件驱动模式
优势:
- 高并发场景下资源消耗低
- 避免进程/线程切换开销
1. select模型
工作原理:
通过fd_set监控文件描述符集合,超时阻塞等待事件发生。
框架流程:
C语言实现:
#include <sys/select.h>
int main() {
// ...socket初始化...
fd_set readfds;
int max_sd = server_fd;
while(1) {
FD_ZERO(&readfds);
FD_SET(server_fd, &readfds);
// 添加已连接客户端
for(int i=0; i<MAX_CLIENTS; i++) {
if(client_sockets[i] > 0) FD_SET(client_sockets[i], &readfds);
if(client_sockets[i] > max_sd) max_sd = client_sockets[i];
}
int activity = select(max_sd+1, &readfds, NULL, NULL, NULL);
if(FD_ISSET(server_fd, &readfds)) {
int new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
// 添加到客户端数组
}
for(int i=0; i<MAX_CLIENTS; i++) {
if(FD_ISSET(client_sockets[i], &readfds)) {
// 处理客户端请求
}
}
}
}
优缺点:
- ✅ 优点:跨平台支持
- ❌ 缺点:fd_set大小受限(通常1024),需遍历所有fd
2. poll模型
工作原理:
使用pollfd结构数组替代fd_set,无文件描述符数量限制。
框架流程:
C语言实现:
#include <poll.h>
int main() {
struct pollfd fds[MAX_CLIENTS+1];
// 初始化server_fd
fds[0].fd = server_fd;
fds[0].events = POLLIN;
int nfds = 1;
while(1) {
int ret = poll(fds, nfds, -1);
if(fds[0].revents & POLLIN) {
int new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
fds[nfds].fd = new_socket;
fds[nfds].events = POLLIN;
nfds++;
}
for(int i=1; i<nfds; i++) {
if(fds[i].revents & POLLIN) {
// 处理客户端
}
}
}
}
优缺点:
- ✅ 优点:无fd数量限制
- ❌ 缺点:仍需遍历所有fd,性能随fd数量线性下降
3. epoll模型
工作原理:
Linux特有高效模型,使用事件驱动机制,仅返回就绪事件。
核心API:
epoll_create()创建实例epoll_ctl()管理fdepoll_wait()等待事件
框架流程:
C语言实现:
#include <sys/epoll.h>
int main() {
int epoll_fd = epoll_create1(0);
struct epoll_event event, events[MAX_EVENTS];
// 添加server_fd
event.events = EPOLLIN;
event.data.fd = server_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event);
while(1) {
int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
for(int i=0; i<n; i++) {
if(events[i].data.fd == server_fd) {
int new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
// 添加到epoll
} else {
// 处理客户端数据
}
}
}
}
优缺点:
- ✅ 优点:O(1)时间复杂度,百万级并发支持
- ❌ 缺点:Linux特有,编程较复杂
4. libevent库
工作原理:
跨平台事件通知库,封装底层I/O复用机制,提供统一API。
核心组件:
event_base事件基座event事件处理器bufferevent带缓冲的事件
C语言实现:
#include <event2/event.h>
#include <event2/listener.h>
void echo_cb(struct bufferevent *bev, void *ctx) {
char buffer[1024];
int n = bufferevent_read(bev, buffer, sizeof(buffer));
bufferevent_write(bev, buffer, n);
}
void accept_cb(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *address, int socklen, void *ctx) {
struct event_base *base = evconnlistener_get_base(listener);
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, echo_cb, NULL, NULL, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE);
}
int main() {
struct event_base *base = event_base_new();
struct sockaddr_in sin = {
.sin_family = AF_INET,
.sin_port = htons(8888)
};
struct evconnlistener *listener = evconnlistener_new_bind(
base, accept_cb, NULL, LEV_OPT_REUSEABLE, -1,
(struct sockaddr*)&sin, sizeof(sin));
event_base_dispatch(base);
return 0;
}
优缺点:
- ✅ 优点:跨平台,自动选择最佳I/O复用机制
- ❌ 缺点:需学习新API,依赖第三方库
模型对比总结
| 模型类型 | 并发能力 | 资源消耗 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| 单进程 | ★☆☆☆☆ | ★☆☆☆☆ | ★☆☆☆☆ | 低并发测试 |
| 多进程 | ★★★☆☆ | ★★★★☆ | ★★☆☆☆ | CPU密集型任务 |
| 多线程 | ★★★★☆ | ★★★☆☆ | ★★★☆☆ | I/O密集型任务 |
| 线程池 | ★★★★☆ | ★★★☆☆ | ★★★★☆ | 稳定并发场景 |
| select | ★★☆☆☆ | ★★★☆☆ | ★★★☆☆ | 跨平台低并发 |
| poll | ★★★☆☆ | ★★★☆☆ | ★★★☆☆ | 中等并发 |
| epoll | ★★★★★ | ★☆☆☆☆ | ★★★★☆ | Linux高并发 |
| libevent | ★★★★★ | ★★☆☆☆ | ★★★☆☆ | 跨平台高并发 |
性能建议:
- Linux平台首选epoll
- 跨平台项目用libevent
- 短连接服务用线程池
- 长连接用I/O多路复用
- 简单应用用select/poll
所有代码均可在Linux系统编译运行:
# 编译服务端
gcc server.c -o server -lpthread # 多线程/线程池需要
gcc server.c -o server -levent # libevent需要
# 编译客户端
gcc client.c -o client
# 运行
./server
./client
更多推荐


所有评论(0)