C 语言并发服务器
外观
C语言并发服务器[编辑 | 编辑源代码]
并发服务器是指能够同时处理多个客户端请求的服务器程序。在C语言网络编程中,实现并发服务器通常使用多进程(fork)、多线程(pthread)或I/O多路复用(select/poll/epoll)等技术。本节将详细介绍这些方法及其实现原理。
概述[编辑 | 编辑源代码]
并发服务器的核心目标是提高服务器的吞吐量和响应速度,避免单个客户端请求阻塞其他请求的处理。常见的实现方式包括:
- 多进程并发服务器:为每个客户端连接创建一个新进程。
- 多线程并发服务器:为每个客户端连接创建一个新线程。
- I/O多路复用:使用单个进程/线程监听多个客户端连接。
多进程并发服务器[编辑 | 编辑源代码]
使用fork()
系统调用为每个客户端创建子进程。
代码示例[编辑 | 编辑源代码]
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8080
void handle_client(int client_socket) {
char buffer[1024] = {0};
read(client_socket, buffer, sizeof(buffer));
printf("Received: %s\n", buffer);
send(client_socket, "Hello from server!", 18, 0);
close(client_socket);
}
int main() {
int server_fd, client_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
// 创建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);
// 绑定socket
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听
if (listen(server_fd, 3) < 0) {
perror("listen failed");
exit(EXIT_FAILURE);
}
while (1) {
if ((client_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept failed");
exit(EXIT_FAILURE);
}
// 创建子进程处理客户端
pid_t pid = fork();
if (pid == 0) { // 子进程
close(server_fd); // 关闭不需要的服务器socket
handle_client(client_socket);
exit(0);
} else if (pid > 0) { // 父进程
close(client_socket); // 关闭不需要的客户端socket
} else {
perror("fork failed");
}
}
return 0;
}
执行流程[编辑 | 编辑源代码]
多线程并发服务器[编辑 | 编辑源代码]
使用POSIX线程(pthread)为每个客户端创建线程。
代码示例[编辑 | 编辑源代码]
#include <pthread.h>
// 线程处理函数
void* client_handler(void* arg) {
int client_socket = *(int*)arg;
handle_client(client_socket);
free(arg);
return NULL;
}
// 在主循环中替换fork()部分:
pthread_t thread_id;
int* new_sock = malloc(sizeof(int));
*new_sock = client_socket;
if (pthread_create(&thread_id, NULL, client_handler, (void*)new_sock) < 0) {
perror("could not create thread");
}
I/O多路复用[编辑 | 编辑源代码]
使用select()
/poll()
/epoll()
实现单进程处理多个连接。
select() 示例[编辑 | 编辑源代码]
fd_set readfds;
int max_fd = server_fd;
while (1) {
FD_ZERO(&readfds);
FD_SET(server_fd, &readfds);
// 添加所有客户端socket到集合
for (int i = 0; i < max_clients; i++) {
if (client_sockets[i] > 0) {
FD_SET(client_sockets[i], &readfds);
if (client_sockets[i] > max_fd) {
max_fd = client_sockets[i];
}
}
}
// 等待活动
int activity = select(max_fd + 1, &readfds, NULL, NULL, NULL);
if (activity < 0) {
perror("select error");
}
// 检查服务器socket是否有新连接
if (FD_ISSET(server_fd, &readfds)) {
// 接受新连接...
}
// 检查客户端socket是否有数据
for (int i = 0; i < max_clients; i++) {
if (client_sockets[i] > 0 && FD_ISSET(client_sockets[i], &readfds)) {
// 处理客户端请求...
}
}
}
性能比较[编辑 | 编辑源代码]
方法 | 优点 | 缺点 |
---|---|---|
多进程 | 隔离性好,稳定性高 | 资源消耗大,进程间通信复杂 |
多线程 | 资源共享方便,创建开销小 | 需要处理线程同步问题 |
I/O多路复用 | 资源消耗最小 | 编程复杂度高 |
实际应用案例[编辑 | 编辑源代码]
1. Web服务器:如Apache早期版本使用多进程,Nginx使用事件驱动(epoll) 2. 数据库服务器:MySQL使用多线程处理连接 3. 即时通讯服务器:需要同时维持大量连接
进阶话题[编辑 | 编辑源代码]
- 线程池技术
- 事件驱动架构
- 协程(Coroutine)实现并发
- 异步I/O(AIO)