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

之前说的要写的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.lryc.cn/news/625117.html

相关文章:

  • 给linux的root磁盘扩容
  • Ansible 部署LNMP
  • 每日AI要闻【20250818】
  • 自回归图像生成新突破!140亿参数自回归模型NextStep-1开源,图像生成无需扩散模型
  • 基于SFM的三维重建MATLAB程序
  • MBTI职业规划指南:发掘你的人格潜能,照亮职业发展之路
  • Elasticsearch查询中的track_total_hits参数
  • 力扣hot100:移动零问题的巧妙解决:双指针与原地交换策略(283)
  • 构建高效智能语音代理:技术架构、实现细节与API服务推荐
  • shell脚本第一阶段
  • Linux命令大全-rm命令
  • 音频算法工程师技能1
  • Docker常见指令速查
  • mq存量消息如何处理
  • 电商API接口实录对接:1688混批价格函数处理
  • python DataFrame基础操作
  • 烟草行政处罚案卷制作与评查平台被中国信通院认定为2025年商业产品及企业典型案例
  • 第一阶段C#基础-13:索引器,接口,泛型
  • AI出题人给出的Java后端面经(十八)(日更)
  • 什么是系统设计
  • 电竞酒店和高校宿舍对AI云电竞游戏盒子的需求有什么不同?
  • 从虚拟到现实:数字孪生赋能智能制造
  • docker部署flask并迁移至内网
  • 前端面试通关:Cesium+Three+React优化+TypeScript实战+ECharts性能方案
  • css word-pass
  • 强化学习-CH2 状态价值和贝尔曼等式
  • 【新手易混】find 命令中 -perm 选项的知识点
  • Unity2022打包安卓报错的奇葩问题
  • 云原生俱乐部-docker知识点归纳(1)
  • 2-4〔O҉S҉C҉P҉ ◈ 研记〕❘ 漏洞扫描▸AWVS(WEB扫描)