当前位置: 首页 > 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.dtcms.com/a/337248.html

相关文章:

  • 算法题打卡力扣第26. 删除有序数组中的重复项(easy))
  • 日语学习-日语知识点小记-进阶-JLPT-N1阶段蓝宝书,共120语法(4):31-40语法
  • 在react props中在参数中定义参数类型
  • Leetcode 16 java
  • 发布npmjs组件库
  • 政策技术双轮驱动智慧灯杆市场扩容,塔能科技破解行业痛点
  • 使用AWS S3 + Lambda + MediaConvert 实现上传视频文件并自动转码
  • 像素风球球大作战 HTML 游戏
  • 隐秘参数APP:全面了解手机硬件信息与优化性能
  • 从零开始搭建React+TypeScript+webpack开发环境——多环境配置管理方案
  • WMS仓库管理系统如何远程访问?
  • RAID服务器
  • qsort函数使用及其模拟实现
  • 视觉语言导航(2)——VLN RNN TRANSFORMER 与ATTENTION 2.2+LSTM(单独一节)
  • 分治-归并-493.翻转对-力扣(LeetCode)
  • 艺术品与收藏直播驱动数字化鉴赏与交易
  • 设计模式笔记_行为型_访问者模式
  • 双通道审核智能合约更新路径:基于区块链与AI融合的编程范式分析
  • MATLAB建模与可视化技术文档:从二维到三维
  • snprintf
  • 《Python学习之使用标准库:从入门到实战》
  • 104、【OS】【Nuttx】【周边】文档构建渲染:安装 Sphinx 扩展(上)
  • 从零到一构建企业级GraphRAG系统:GraphRag.Net深度技术解析
  • Python Ovito统计多晶晶粒数量
  • 领域驱动设计(DDD)中的“核心领域逻辑与基础设施分离”原则
  • Maven 生命周期和插件
  • RocketMQ是什么?
  • Day7--滑动窗口与双指针--1695. 删除子数组的最大得分,2958. 最多 K 个重复元素的最长子数组,2024. 考试的最大困扰度
  • 消息队列中的推模式与拉模式
  • C++/Java双平台表单校验实战:合法性+长度+防重复+Tab顺序四重守卫