C语言实现TCP Server:使用select实现非阻塞多客户端连接
C语言实现TCP Server:使用select实现非阻塞多客户端连接/n/n本文将介绍如何使用C语言编写一个简单的TCP server服务。该服务可以通过Linux脚本启动,并接收三个参数:/n/n1. 监听端口(port)/n2. 最大允许连接的客户端数量(max/_client)/n3. 超过最大连接数量后的处理方式(over)/n/n其中,over 参数有两种取值:/n/n* 0: 不允许超过最大连接数的客户端接入。/n* 1: 允许新客户端接入,并踢掉最早连接的客户端。/n/n该服务使用 select 函数实现非阻塞的方式管理多个客户端连接,并采用链表数据结构存储客户端套接字信息。/n/n## 代码实现/n/nc/n#include <stdio.h>/n#include <stdlib.h>/n#include <string.h>/n#include <unistd.h>/n#include <sys/socket.h>/n#include <arpa/inet.h>/n#include <sys/select.h>/n/n#define MAX_CLIENTS 10/n/nint main(int argc, char *argv[]) {/n if (argc != 4) {/n fprintf(stderr, /'Usage: %s <port> <max_client> <over>/n/', argv[0]);/n return 1;/n }/n/n int port = atoi(argv[1]);/n int max_client = atoi(argv[2]);/n int over = atoi(argv[3]);/n/n if (port < 1024 || port > 65535) {/n fprintf(stderr, /'Invalid port number/n/');/n return 1;/n }/n if (max_client <= 0) {/n fprintf(stderr, /'Invalid max_client number/n/');/n return 1;/n }/n/n // 创建监听套接字/n int server_sock = socket(AF_INET, SOCK_STREAM, 0);/n if (server_sock < 0) {/n perror(/'socket/');/n return 1;/n }/n/n // 绑定地址和端口/n struct sockaddr_in server_addr;/n memset(&server_addr, 0, sizeof(server_addr));/n server_addr.sin_family = AF_INET;/n server_addr.sin_addr.s_addr = htonl(INADDR_ANY);/n server_addr.sin_port = htons(port);/n if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {/n perror(/'bind/');/n return 1;/n }/n/n // 开始监听/n if (listen(server_sock, max_client) < 0) {/n perror(/'listen/');/n return 1;/n }/n/n printf(/'Server started on port %d/n/', port);/n/n // 客户端套接字链表/n int client_sock[MAX_CLIENTS] = {0};/n int num_clients = 0;/n/n while (1) {/n // 用select非阻塞地等待客户端连接或数据/n fd_set read_fds;/n FD_ZERO(&read_fds);/n FD_SET(server_sock, &read_fds);/n int max_fd = server_sock;/n for (int i = 0; i < num_clients; i++) {/n if (client_sock[i] != 0) {/n FD_SET(client_sock[i], &read_fds);/n if (client_sock[i] > max_fd) {/n max_fd = client_sock[i];/n }/n }/n }/n if (select(max_fd + 1, &read_fds, NULL, NULL, NULL) < 0) {/n perror(/'select/');/n return 1;/n }/n/n // 处理客户端连接请求/n if (FD_ISSET(server_sock, &read_fds)) {/n if (num_clients < max_client || over == 1) {/n struct sockaddr_in client_addr;/n socklen_t client_addr_len = sizeof(client_addr);/n int new_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_addr_len);/n if (new_sock < 0) {/n perror(/'accept/');/n } else {/n printf(/'New client connected: %s:%d/n/', inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));/n if (num_clients < max_client) {/n client_sock[num_clients++] = new_sock;/n } else {/n if (over == 1) {/n close(client_sock[0]);/n for (int i = 1; i < num_clients; i++) {/n client_sock[i - 1] = client_sock[i];/n }/n client_sock[num_clients - 1] = new_sock;/n } else {/n printf(/'Max clients reached, rejected new connection/n/');/n close(new_sock);/n }/n }/n }/n } else {/n printf(/'Max clients reached, rejected new connection/n/');/n }/n }/n/n // 处理客户端数据/n for (int i = 0; i < num_clients; i++) {/n if (FD_ISSET(client_sock[i], &read_fds)) {/n char buf[1024];/n int n = recv(client_sock[i], buf, sizeof(buf), 0);/n if (n < 0) {/n perror(/'recv/');/n } else if (n == 0) {/n close(client_sock[i]);/n printf(/'Client disconnected/n/');/n client_sock[i] = 0;/n for (int j = i; j < num_clients - 1; j++) {/n client_sock[j] = client_sock[j + 1];/n }/n num_clients--;/n } else {/n if (send(client_sock[i], buf, n, 0) < 0) {/n perror(/'send/');/n }/n }/n }/n }/n }/n/n return 0;/n}/n/n/n## 代码解释/n/n1. 头文件: 包含必要的库文件,例如 stdio.h、stdlib.h、string.h、unistd.h、sys/socket.h、arpa/inet.h 和 sys/select.h。/n2. 宏定义: 定义 MAX_CLIENTS 为最大允许连接的客户端数量。/n3. 主函数: 获取命令行参数,验证参数的合法性,创建监听套接字,绑定地址和端口,开始监听连接请求。/n4. 创建监听套接字: 使用 socket() 函数创建监听套接字,指定协议族为 AF_INET,套接字类型为 SOCK_STREAM,协议为 0(表示使用默认协议)。/n5. 绑定地址和端口: 使用 bind() 函数将监听套接字绑定到指定的地址和端口。/n6. 开始监听: 使用 listen() 函数开始监听连接请求,指定最大允许排队的连接数量。/n7. 客户端套接字链表: 创建一个数组 client_sock 来存储所有连接的客户端套接字,并初始化为 0。/n8. 循环处理: 使用 while 循环不断地等待客户端连接请求或数据。/n9. 使用 select 函数: 使用 select() 函数非阻塞地等待客户端连接或数据,将监听套接字和所有已连接的客户端套接字添加到 read_fds 集合中。/n10. 处理连接请求: 如果监听套接字处于可读状态,则表示有新的客户端连接请求,使用 accept() 函数接受连接,并根据参数 over 的值决定是否接受新的连接。/n11. 处理客户端数据: 如果客户端套接字处于可读状态,则表示有客户端数据到达,使用 recv() 函数接收数据,并使用 send() 函数将数据回发给客户端。/n12. 关闭连接: 如果客户端套接字处于可读状态,但是 recv() 函数返回 0,则表示客户端断开连接,关闭该客户端套接字,并将其从链表中移除。/n/n## 运行代码/n/n1. 将代码保存为 tcp_server.c 文件。/n2. 使用 GCC 编译代码:gcc tcp_server.c -o tcp_server/n3. 启动服务:./tcp_server <port> <max_client> <over>,例如:./tcp_server 8080 5 1/n/n## 总结/n/n本代码演示了如何使用C语言实现一个简单的TCP server服务,并使用 select 函数实现非阻塞的方式管理多个客户端连接。该服务可以根据需要进行扩展,例如添加数据加密、身份验证等功能。
原文地址: https://www.cveoy.top/t/topic/oOtj 著作权归作者所有。请勿转载和采集!