当前位置: 首页 > web >正文

之前说的要写的TCP高性能服务器,今天来了

通过C语言搭建了有个TCP的服务器端,其中有些自定义函数直接写在下方供参考:
server.c

#include "server.h"
#include "client.h"
#include "message.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/epoll.h>#define MAX_EVENTS 1024void run_server(int port) {int listen_fd, epfd;struct epoll_event ev, events[MAX_EVENTS];// 创建监听socketlisten_fd = socket(AF_INET, SOCK_STREAM, 0);if (listen_fd < 0) {perror("socket");exit(1);}struct sockaddr_in serv_addr;serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(port);serv_addr.sin_addr.s_addr = INADDR_ANY;if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {perror("bind");exit(1);}if (listen(listen_fd, 10) < 0) {perror("listen");exit(1);}// 创建epollepfd = epoll_create(MAX_EVENTS);ev.events = EPOLLIN;ev.data.fd = listen_fd;epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);printf("Server started on port %d\n", port);while (1) {int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);for (int i = 0; i < nfds; i++) {int sockfd = events[i].data.fd;if (sockfd == listen_fd) {// 新客户端连接int conn_fd = accept(listen_fd, NULL, NULL);add_client(conn_fd);ev.events = EPOLLIN;ev.data.fd = conn_fd;epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev);printf("New client connected: %d\n", conn_fd);} else if (events[i].events & EPOLLIN) {// 处理客户端消息char buf[1024] = {0};int n = read(sockfd, buf, sizeof(buf));if (n <= 0) {printf("Client %d disconnected\n", sockfd);remove_client(sockfd);epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);close(sockfd);} else {buf[n] = '\0';handle_message(sockfd, buf);}}}}close(listen_fd);
}

client.c

#include "client.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>static Client clients[MAX_CLIENTS];void add_client(int fd) {for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i].fd == 0) {clients[i].fd = fd;snprintf(clients[i].name, sizeof(clients[i].name), "user%d", fd);return;}}
}void remove_client(int fd) {for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i].fd == fd) {clients[i].fd = 0;break;}}
}Client* find_client_by_name(const char* name) {for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i].fd != 0 && strcmp(clients[i].name, name) == 0) {return &clients[i];}}return NULL;
}void broadcast_message(int sender_fd, const char* msg) {for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i].fd != 0 && clients[i].fd != sender_fd) {write(clients[i].fd, msg, strlen(msg));}}
}void private_message(int sender_fd, const char* target, const char* msg) {Client* client = find_client_by_name(target);if (client) {write(client->fd, msg, strlen(msg));} else {const char* err = "User not found\n";write(sender_fd, err, strlen(err));}
}

message.c

#include "message.h"
#include "client.h"
#include <string.h>
#include <stdio.h>void handle_message(int sender_fd, const char* msg) {if (msg[0] == '@') {// 私聊 @username: messagechar target[32], text[1024];if (sscanf(msg, "@%31[^:]: %[^\n]", target, text) == 2) {private_message(sender_fd, target, text);}} else {// 广播broadcast_message(sender_fd, msg);}
}

使用的构建方法是通过makefile,这里我贴上自己的makefile,供参考:

CC = gcc -Wall -g
OBJ = main.o server.o client.o message.ochat_server: $(OBJ)$(CC) -o $@ $(OBJ)%.o: %.c$(CC) -c $<clean:rm -f *.o chat_server

客户端测试的话,可以通过c++ QT来实现。之前想通过多线程的方式,每有一个连接就创建一个线程,后来看了企业的设计方案,使用的是epoll函数来处理大量的连接请求,底层是通过事件驱动的方式,当时一直不理解为什么会使用监听的socket的文件描述符和新建立连接的文件描述符进行比较来判断是否为新连接,通过查资料发现是自己理解的大方向出问题了,下面是我查资料总结如下:

sockfd == listen_fd 这个判断的核心作用是 “区分事件的来源”—— 明确当前触发事件的是 “监听 socket”(专门负责接新连接),而不是 “客户端 socket”(负责和已连接客户端通信)。

用 “现实场景类比” 理解:

假设你是一家公司的前台经理,负责处理两种事务:

  1. 新客户上门(对应 “新连接请求”):由公司大门(listen_fd)的门铃通知你。
  2. 老客户消息(对应 “客户端发数据”):由各个客户经理(conn_fd)的电话通知你。

门铃(listen_fd)和电话(conn_fd)是不同的 “设备”,你需要通过 “哪个设备响了” 来判断要处理哪种事务:

  • 如果是门铃响了(sockfd == listen_fd)→ 处理新客户上门;
  • 如果是某个电话响了(sockfd == 某个conn_fd)→ 处理对应老客户的消息。

代码中的具体逻辑:

在 epoll 事件循环中,epoll_wait 会返回所有触发事件的文件描述符(sockfd),但这些文件描述符可能来自两种 socket:

  1. listen_fd:监听 socket,唯一作用是接收新连接。
  2. conn_fd:客户端 socket,每个已连接的客户端对应一个,用于收发数据。

if (sockfd == listen_fd) 就是通过 “文件描述符的值是否相等” 来判断:

  • 相等:事件来自监听 socket → 必然是 “有新客户端要连接”(因为 listen_fd 只能干这个),所以执行 accept() 接收新连接。
  • 不相等:事件来自某个客户端 socket → 必然是 “该客户端发来了数据”,所以执行 read() 读取消息。

为什么能通过 “相等” 来判断?

  • 文件描述符的唯一性:每个 socket(包括 listen_fd 和所有 conn_fd)的文件描述符都是唯一的整数(比如 listen_fd 可能是 3,第一个 conn_fd 是 4,第二个是 5 等)。
  • listen_fd 的特殊性:整个服务器只有一个 listen_fd,且它的唯一功能是接收新连接,不会参与数据收发,所以它的事件含义是确定的。

一句话总结:

if (sockfd == listen_fd) 就像在问:“这个事件是监听 socket 触发的吗?”
如果是,就说明有新客户端要连接;如果不是,就说明是已连接的客户端发来了消息。这是 epoll 模型中区分 “新连接” 和 “数据通信” 的核心判断。

http://www.xdnf.cn/news/18279.html

相关文章:

  • Elasticsearch全文检索中文分词:IK分词器详解与Docker环境集成
  • 用 Python 实现一个“小型 ReAct 智能体”:思维链 + 工具调用 + 环境交互
  • 如何使用 React 101 的 Highcharts 包装器
  • Pomian语言处理器 研发笔记(一):使用C++的正则表达式构建词法分析器
  • 视频讲解:CatBoost、梯度提升 (XGBoost、LightGBM)对心理健康数据、交通流量及股票价格预测研究
  • 从数据汇总到高级分析,SQL 查询进阶实战(下篇)—— 分组、子查询与窗口函数全攻略
  • 8.18 表达式树|浮点数绝对值
  • 基于Flink CDC实现联系人与标签数据实时同步至ES的实践
  • Ps 2025 图像编辑 Photoshop(Mac中文)
  • 【避坑指南】初始化与更新共享数据赋值的一致性问题
  • 【数模国奖冲刺】备赛过程中的常见问题
  • Linux 服务:RAID 级别解析与 mdadm 工具实操指南
  • SWMM排水管网水力、水质建模及在海绵与水环境中的应用技术-模拟降雨和污染物质经过地面、排水管网、蓄水和处理
  • 计算机大数据毕业设计推荐:基于Hadoop+Spark的食物口味差异分析可视化系统【源码+文档+调试】
  • 第一阶段C#基础-13:索引器,接口,泛型
  • 【网络安全实验报告】实验六: 病毒防护实验
  • 【PZ-ZU47DR-KFB】璞致FPGA ZYNQ UltraScalePlus RFSOC QSPI Flash 固化常见问题说明
  • 【P38 6】OpenCV Python——图片的运算(算术运算、逻辑运算)加法add、subtract减法、乘法multiply、除法divide
  • 如何在服务器 clone github 项目
  • 【Linux开发】错误更改bash.sh导致PATH环境变量被破坏所有命令不可用的解决方法
  • 【菜狗学聚类】时序数据聚类算法和相关论文
  • 算法-每日一题(DAY13)两数之和
  • Centos7使用lamp架构部署wordpress
  • CentOS 7 LAMP快速部署WordPress指南
  • 20. 云计算-Service MeshServerless
  • 时序数据库 Apache IoTDB:从边缘到云端Apache IoTDB 全链路数据管理能力、部署流程与安全特性解读
  • 基于51单片机WIFI心率计脉搏体温测量仪APP设计
  • 加密资产投资的六种策略:稳定币合规后的 Web3 投资和 RWA
  • RabbitMQ ,消息进入死信交换机
  • React diff Vue diff介绍