基于I/O复用的高并发服务器:使用select/epoll实现
基于I/O复用的高并发服务器:使用select/epoll实现
本文介绍如何利用 I/O 复用技术(select 或 epoll)构建高性能服务器,实现同时处理多个客户端请求。
需求分析
服务器端:
- 监听指定端口,接收客户端连接请求。* 打印连接客户端的 IP 地址和端口号。* 接收客户端发送的字符串,并打印该字符串以及其来源客户端的信息。* 将接收到的字符串原样返回给对应的客户端。* 当某个客户端断开连接时,打印该客户端在连接生命周期内发送的所有字符串。
客户端:
- 通过命令行参数指定服务器地址,并向服务器发起连接请求。* 连接成功后,从标准输入读取字符串。* 将读取到的字符串发送至服务器。* 等待并打印服务器返回的数据。
代码实现 (使用select)
服务器端代码 (server.c)c#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/socket.h>#include <arpa/inet.h>#include <sys/select.h>#include <string.h>
#define MAX_CLIENTS 10#define BUFFER_SIZE 1024
int main() { int server_sock, client_socks[MAX_CLIENTS]; struct sockaddr_in server_addr, client_addr; socklen_t client_addr_len; fd_set readfds; char buffer[BUFFER_SIZE]; char client_data[MAX_CLIENTS][BUFFER_SIZE * 10]; // 存储每个客户端发送的数据
// 创建服务器socket server_sock = socket(AF_INET, SOCK_STREAM, 0); if (server_sock == -1) { perror('socket'); exit(1); }
// 设置服务器地址和端口号 server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8080); server_addr.sin_addr.s_addr = INADDR_ANY;
// 绑定服务器socket到指定地址和端口号 if (bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { perror('bind'); exit(1); }
// 开始监听连接请求 if (listen(server_sock, MAX_CLIENTS) == -1) { perror('listen'); exit(1); }
printf('Server is running...
');
// 初始化客户socket数组和数据存储 for (int i = 0; i < MAX_CLIENTS; i++) { client_socks[i] = -1; client_data[i][0] = '�'; }
while (1) { // 清空读文件描述符集 FD_ZERO(&readfds);
// 将服务器socket添加到读文件描述符集 FD_SET(server_sock, &readfds);
int max_fd = server_sock;
// 将已连接的客户socket添加到读文件描述符集 for (int i = 0; i < MAX_CLIENTS; i++) { int client_sock = client_socks[i]; if (client_sock != -1) { FD_SET(client_sock, &readfds); if (client_sock > max_fd) { max_fd = client_sock; } } }
// 使用select函数等待文件描述符变为可读 int activity = select(max_fd + 1, &readfds, NULL, NULL, NULL); if (activity == -1) { perror('select'); exit(1); }
// 如果服务器socket有新的连接请求 if (FD_ISSET(server_sock, &readfds)) { // 接受连接请求 client_addr_len = sizeof(client_addr); int new_client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &client_addr_len); if (new_client_sock == -1) { perror('accept'); continue; }
// 找到一个空闲的客户socket位置 int index = -1; for (int i = 0; i < MAX_CLIENTS; i++) { if (client_socks[i] == -1) { index = i; break; } }
// 如果没有找到空闲位置,关闭新的客户socket if (index == -1) { close(new_client_sock); printf('Too many clients. Connection rejected.
'); } else { // 添加新的客户socket到数组 client_socks[index] = new_client_sock;
// 打印客户IP地址和端口号 printf('New client connected: %s:%d
', inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); } }
// 遍历已连接的客户socket,并处理接收和发送数据 for (int i = 0; i < MAX_CLIENTS; i++) { int client_sock = client_socks[i]; if (client_sock != -1 && FD_ISSET(client_sock, &readfds)) { // 接收客户发送的数据 int num_bytes = recv(client_sock, buffer, BUFFER_SIZE, 0); if (num_bytes <= 0) { // 客户断开连接 getpeername(client_sock, (struct sockaddr*)&client_addr, &client_addr_len); printf('Client disconnected: %s:%d
', inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); printf('Client data: %s ', client_data[i]);
// 关闭客户socket close(client_sock);
// 清空客户socket位置和数据 client_socks[i] = -1; client_data[i][0] = '�'; } else { // 打印接收到的数据和来自哪个客户 buffer[num_bytes] = '�'; getpeername(client_sock, (struct sockaddr*)&client_addr, &client_addr_len); printf('Received from %s:%d: %s
', inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buffer);
// 将数据存储到对应客户端的数据缓存 strcat(client_data[i], buffer);
// 发送相同的数据给客户 send(client_sock, buffer, num_bytes, 0); } } } }
// 关闭服务器socket close(server_sock);
return 0;}
客户端代码 (client.c)c#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/socket.h>#include <arpa/inet.h>#include <string.h>
#define BUFFER_SIZE 1024
int main(int argc, char *argv[]) { // 检查命令行参数 if (argc != 2) { printf('Usage: %s <server_ip> ', argv[0]); exit(1); }
int client_sock; struct sockaddr_in server_addr; char buffer[BUFFER_SIZE];
// 创建客户端socket client_sock = socket(AF_INET, SOCK_STREAM, 0); if (client_sock == -1) { perror('socket'); exit(1); }
// 设置服务器地址和端口号 server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8080); server_addr.sin_addr.s_addr = inet_addr(argv[1]);
// 发起连接请求 if (connect(client_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { perror('connect'); exit(1); }
printf('Connected to server.
');
while (1) { // 从标准输入读取字符串 printf('Input message: '); fgets(buffer, BUFFER_SIZE, stdin);
// 发送字符串给服务器 send(client_sock, buffer, strlen(buffer), 0);
// 等待接收服务器响应 int num_bytes = recv(client_sock, buffer, BUFFER_SIZE - 1, 0); if (num_bytes <= 0) { // 服务器断开连接 printf('Disconnected from server.
'); break; }
// 打印接收到的信息 buffer[num_bytes] = '�'; printf('Received from server: %s
', buffer); }
// 关闭客户端socket close(client_sock);
return 0;}
总结
本文介绍了如何使用 select 函数实现一个简单的 I/O 复用服务器,并提供了服务器端和客户端的代码示例。您可以根据实际需求对代码进行修改和扩展。
注意:
- 以上示例代码仅供学习参考,实际应用中需要根据具体需求进行完善和优化。*
epoll相比于select具有更高的性能,适用于处理大量并发连接的场景.
原文地址: https://www.cveoy.top/t/topic/Tsn 著作权归作者所有。请勿转载和采集!