基于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 具有更高的性能,适用于处理大量并发连接的场景.
基于I/O复用的高并发服务器:使用select/epoll实现

原文地址: https://www.cveoy.top/t/topic/Tsn 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录