C语言TCP服务器模型 : select + 多线程与双循环单线程阻塞服务器的比较
观察到的实验现象:
启动三个客户端:
使用双循环阻塞服务器:只能accept后等待收发,同时只能与一个客户端建立连接,必须等已连接的客户端多次收发 明确断开后才能与下个客户端连接

使用IO多路复用select:可以同时接收所有的连接请求,并且连接状态一直是存活的,直到客户端关闭连接

select + 多线程服务器创作灵感:
本来是想 接收,发送 全用select
但是如果每个连接都要求处理大量数据,则响应时间不确定
最重要的,select判断依据是内核缓存是否有足够空间可写,而不是数据是否准备好
所以为了数据准备好再发送
我使用了 接收多路复用+分线程处理数据+处理完毕在线程内直接发送 的模型
什么样的场景收发都适合用select?
IO密集型转发服务器
用于对比的双循环阻塞服务器工作原理:
进入外循环, accept后 再进入内循环 收 发 ,当客户端结束连接时 内层循环结束(使用break)
代码走完 重新进入外层循环 accept阻塞等待一个新连接
注意事项: ip地址修改为符合 你网络规范的ip 运行环境:unix-like系统 gnu_c库
select + 多线程服务器,欢迎指正:
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <time.h>#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50012
// 此结构体用于线程参数
struct t_args
{int fd;char data[1024];
};
// 用于accept返回的fd的容器
int client_sockfds[1024] = {0};
// 计数器可以理解为指针,每次用完向后挪1位
int count = 0;
// 线程执行函数
void *start_routine(void *p)
{// 解析参数struct t_args ta = *((struct t_args *)p);// fd后面要用int fd = ta.fd;// 数据打印出来表示已经获取,可以进行后续处理printf("%s\n", ta.data);// 模拟数据处理sleep((rand() % 3) + 1);// 这是处理完的结果char res_data[128] = "yes yes done done done";ssize_t send_bytes;// 声明写监控集fd_set writefds;// 清空重置FD_ZERO(&writefds);// 将这个fd加入写监控FD_SET(fd, &writefds);// 如果select返回,说明此fd写就绪int r = select(fd + 1, NULL, &writefds, NULL, NULL);if (r == -1){perror("select");}if (r > 0){// 如果写就绪if (FD_ISSET(fd, &writefds)){// 就把处理好的数据发送回去send_bytes = send(fd, res_data, strlen(res_data), 0);if (send_bytes == -1){perror("send");}if (send_bytes > 0){printf("%s\n", res_data);}}}free(p);pthread_exit(NULL);
}
void handler(void *p)
{pthread_t tid;// 创建线程,传参fdif (pthread_create(&tid, NULL, start_routine, p)){perror("pthread_create");}// 分离if (pthread_detach(tid)){perror("pthread_detach");}
}int main()
{int server_sockfd, client_sockfd;struct sockaddr_in server_sockaddr, client_sockaddr;memset(&server_sockaddr, 0, sizeof(server_sockaddr));memset(&client_sockaddr, 0, sizeof(client_sockaddr));socklen_t client_sockaddr_len = sizeof(client_sockaddr);socklen_t server_sockaddr_len = sizeof(server_sockaddr);ssize_t recv_bytes;char recv_buf[1024] = {0};fd_set readfds;// 随机数种子srand(time(NULL));// 创建socketserver_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (server_sockfd == -1){perror("socket");}// 端口复用int optval = 1;if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1){perror("setsockopt");}// 绑定地址端口inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);server_sockaddr.sin_port = htons(SERVER_PORT);server_sockaddr.sin_family = AF_INET;if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, server_sockaddr_len) == -1){perror("bind");}// 监听if (listen(server_sockfd, 16) == -1){perror("listen");}printf("server start...\n");// 服务器主循环while (1){// 清空重置读集FD_ZERO(&readfds);// 将server_sockfd加入读集FD_SET(server_sockfd, &readfds);// 假设最大的fd是server_sockfdint fd_max = server_sockfd;int i;// count总是指向当前已填充fd的下一个位置for (i = 0; i < count; i++){// client_sockfds[i]数组储存accept返回的fd ,> 0表示存在fdif (client_sockfds[i] > 0){// 存在fd就加入读监控FD_SET(client_sockfds[i], &readfds);// 更新最大fd的值fd_max = fd_max > client_sockfds[i] ? fd_max : client_sockfds[i];}}// 此处select作用:从读集中选择读就绪int r = select(fd_max + 1, &readfds, NULL, NULL, NULL);if (r > 0){// 如果server_sockfd是读就绪的if (FD_ISSET(server_sockfd, &readfds)){// 说明已经有连接在等待,则accept不会阻塞client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);if (client_sockfd == -1){perror("accept");}// count++先读取count的值 后++,把返回的client_sockfd存到数组client_sockfds[count++] = client_sockfd;// 当连接数达到1024时,变得无法处理且有重大安全漏洞if (count == 1024){kill(getpid(), SIGKILL);}}// 此循环用于检查client_sockfds数组已填充部分for (i = 0; i < count; i++){// 检查fd是否读就绪if (FD_ISSET(client_sockfds[i], &readfds)){// 接收消息recv_bytes = recv(client_sockfds[i], recv_buf, sizeof(recv_buf), 0);if (recv_bytes < 0){perror("recv");}else if (recv_bytes == 0){printf("close by peer\n");// 对面关我也关close(client_sockfds[i]);// 将数组上的fd清空client_sockfds[i] = 0;}else{// 向线程传参struct t_args ta;ta.fd = client_sockfds[i];strncpy(ta.data, recv_buf, strlen(recv_buf));// 为每个线程参数动态分配内存空间struct t_args *p = (struct t_args *)malloc(sizeof(ta));if (p == NULL){return -1;}// 赋值*p = ta;// 传入处理函数handler((void *)p);}}}}else if (r == -1){perror("select");}}close(server_sockfd);return 0;
}
双循环阻塞服务器:
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50012int main()
{int server_sockfd, client_sockfd;struct sockaddr_in server_sockaddr, client_sockaddr;memset(&server_sockaddr, 0, sizeof(server_sockaddr));memset(&client_sockaddr, 0, sizeof(client_sockaddr));socklen_t client_sockaddr_len = sizeof(client_sockaddr);ssize_t send_bytes, recv_bytes;char send_buf[1024] = "How can I help you today ?";char recv_buf[1024] = {0};server_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (server_sockfd == -1){perror("socket");}int optval = 1;setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));server_sockaddr.sin_family = AF_INET;inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);server_sockaddr.sin_port = htons(SERVER_PORT);if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1){perror("bind");}if (listen(server_sockfd, 16) == -1){perror("listen");}printf("server start...\n");while (1){client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);if (client_sockfd == -1){perror("accept");}while (1){recv_bytes = recv(client_sockfd, recv_buf, sizeof(recv_buf), 0);if (recv_bytes == -1){perror("recv");}else if (recv_bytes == 0){printf("closed by peer\n");break;}else{printf("%s\n", recv_buf);}send_bytes = send(client_sockfd, send_buf, strlen(send_buf), 0);if (send_bytes == -1){perror("send");}}}close(server_sockfd);return 0;
}
赠送客户端:
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50012int main()
{int client_sockfd;struct sockaddr_in server_sockaddr, client_sockaddr;memset(&server_sockaddr, 0, sizeof(server_sockaddr));memset(&client_sockaddr, 0, sizeof(client_sockaddr));socklen_t client_sockaddr_len = sizeof(client_sockaddr);ssize_t send_bytes, recv_bytes;char send_buf[1024] = {0};char recv_buf[1024] = {0};srand(time(NULL));client_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (client_sockfd == -1){perror("socket");}inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);server_sockaddr.sin_port = htons(SERVER_PORT);server_sockaddr.sin_family = AF_INET;if (connect(client_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1){perror("connect");}getsockname(client_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);snprintf(send_buf, sizeof(send_buf), "%u:he###llo s???ver !!!",ntohs(client_sockaddr.sin_port));while (1){send_bytes = send(client_sockfd, send_buf, strlen(send_buf), 0);if (send_bytes == -1){perror("send");}recv_bytes = recv(client_sockfd, recv_buf, sizeof(recv_buf), 0);if (recv_bytes == -1){perror("recv");}printf("%s\n", recv_buf);sleep(1);}close(client_sockfd);return 0;
}
相关文章:
C语言TCP服务器模型 : select + 多线程与双循环单线程阻塞服务器的比较
观察到的实验现象: 启动三个客户端: 使用双循环阻塞服务器:只能accept后等待收发,同时只能与一个客户端建立连接,必须等已连接的客户端多次收发 明确断开后才能与下个客户端连接 使用IO多路复用select:可以同时接收所有的连接请求,并且连接状态一直是存活的,直到客户端关闭连…...
【数字IC/FPGA】手撕代码:模3检测器(判断输入序列能否被3整除)
今天我们来手撕一个常见的笔试题,使用的方法是三段式Moore状态机。 题目描述: 输入端口是串行的1bit数据,每个时钟周期进来一位新数据后,实时检查当前序列是否能整除3,若能则输出1,否则输出0。 例如&#…...
最小可行产品需要最小可行架构——可持续架构(三)
前言 最小可行产品(MVP)的概念可以帮助团队专注于尽快交付他们认为对客户最有价值的东西,以便在投入大量时间和资源之前迅速、廉价地评估产品的市场规模。MVP不仅需要考虑产品的市场可行性,还需要考虑其技术可行性,以…...
笔记: 数据结构与算法--时间复杂度二分查找数组
算法复杂度 不依赖于环境因素事前分析法 计算最坏情况的时间复杂度每一条语句的执行时间都按照t来计算 时间复杂度 大O表示法 n 数据量 ; f(n) 实际的执行条数当存在一个n0 , 使得 n > n0,并且 c * g(n) 恒> f(n) : 渐进上界(算法最坏的情况)那么f(n)的时间复杂度 …...
AI绘画教程:Midjourney使用方法与技巧从入门到精通
文章目录 一、《AI绘画教程:Midjourney使用方法与技巧从入门到精通》二、内容介绍三、作者介绍🌤️粉丝福利 一、《AI绘画教程:Midjourney使用方法与技巧从入门到精通》 一本书读懂Midjourney绘画,让创意更简单,让设计…...
Spring-事务管理
1、事务管理 1.1、回滚方式 默认回滚方式:发生运行异常时异常和error时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式 1.2、事务定义接口 1.2.1、事务隔离级别常量 这些常量…...
MySql实战--为什么我的MySQL会“抖”一下
时的工作中,不知道你有没有遇到过这样的场景,一条SQL语句,正常执行的时候特别快,但是有时也不知道怎么回事,它就会变得特别慢,并且这样的场景很难复现,它不只随机,而且持续时间还很短…...
【蓝桥杯第十三届省赛B】(部分详解)
九进制转十进制 #include <iostream> #include<math.h> using namespace std; int main() {cout << 2*pow(9,3)0*pow(9,2)2*pow(9,1)2*pow(9,0) << endl;return 0; }顺子日期 #include <iostream> using namespace std; int main() {// 请在此…...
[linux初阶][vim-gcc-gdb] OneCharter: vim编辑器
一.vim编辑器基础 目录 一.vim编辑器基础 ①.vim的语法 ②vim的三种模式 ③三种模式的基本切换 ④各个模式下的一些操作 二.配置vim环境 ①手动配置(不推荐) ②自动配置(推荐) vim是vi的升级版,包含了更加丰富的功能. ①.vim的语法 vim [文件名] ②vim的三种模式 命令…...
【Lazy ORM 框架学习】
Gitee 点赞关注不迷路 项目地址 快速入门 模块所属层级描述快照版本正式版本wu-database-lazy-lambdalambda针对不同数据源wu-database-lazy-orm-coreorm 核心orm核心处理wu-database-lazy-sqlsql核心处理成处理sql解析、sql执行、sql映射wu-elasticsearch-starterESESwu-hb…...
安科瑞路灯安全用电云平台解决方案【电不起火、电不伤人】
背景介绍 近年来 ,随着城市规模的不断扩大 ,路灯事业蓬勃发展。但有的地方因为观念、技术、管理等方面不完善 ,由此引发了一系列安全问题。路灯点多面广 ,一旦漏电就极容易造成严重的人身安全事故。不仅给受害者家庭带来痛苦 &am…...
MYSQL——索引概念索引结构
索引 索引是帮助数据库高效获取数据的排好序的数据结构。 有无索引时,查询的区别 主要区别在于查询速度和系统资源的消耗。 查询速度: 在没有索引的情况下,数据库需要对表中的所有记录进行扫描,以找到符合查询条件的记录&#…...
Linux(CentOS7)配置系统服务以及开机自启动
目录 前言 两种方式 /etc/systemd/system/ 进入 /etc/systemd/system/ 文件夹 创建 nginx.service 文件 重新加载 systemd 配置文件 编辑 配置开机自启 /etc/init.d/ 进入 /etc/init.d/ 文件夹 创建 mysql 文件 编写脚本内容 添加/删除系统服务 配置开机自启 …...
0 决策树基础
目录 1 绪论 2 模型 3 决策树面试总结 1 绪论 决策树算法包括ID3、C4.5以及C5.0等,这些算法容易理解,适用各种数据,在解决各种问题时都有良好表现,尤其是以树模型为核心的各种集成算法,在各个行业和领域都有广泛的…...
Linux速览(2)——环境基础开发工具篇(其一)
本章我们来介绍一些linux的常用工具 目录 一. Linux 软件包管理器 yum 1.什么是软件包? 2. 查看软件包 3. 如何安装软件 4. 如何卸载软件 5.yum补充 6. 关于 rzsz 二. Linux编辑器-vim使用 1. vim的基本概念 2. vim的基本操作 3. vim正常模式命令集 4. vim末行模式…...
AWS SES发送邮件时常见的错误及解决方法?
AWS SES发送邮件如何做配置?使用AWS SES发信的限制? 在使用AWS SES发送邮件时,可能会遇到一些常见的错误。AokSend将介绍一些常见的AWS SES发送邮件错误及其相应的解决方法,帮助用户更好地利用AWS SES进行邮件发送。 AWS SES发送…...
视频基础学习三——视频帧率、码率与分辨率
文章目录 前言一、介绍1.定义2.三者之间的关系 总结 前言 在之前的文章中详细介绍了一些关于图像的色彩与格式,而视频其实就是由一张张图片进行展示呈现出来的。 我们会经常说一段视频的质量好不好,而什么是视频的质量呢?博主的个人理解就是…...
Spring(详细介绍)
目录 一、简介 1、什么是Spring? 2、Spring框架的核心特性 3、优点 二、IOC容器 介绍 1、获取资源的传统方式 2、控制反转方式获取资源 3、DI 4、IOC容器在Spring中的实现 入门案例 1、创建Maven Module 2、引入依赖 3、创建HelloWorld类 4、在Spring的配…...
Kettle使用
1.准备工作 KETTLE-5.4.zip HANA环境192.168.xx.xx 用户名:system 密码:****** 端口号:30015 Oracle环境 192.168.xx.xx 用户名 HANA_TEST 密码 ****** 端口号:31001 配置java环境变量 因为本次数据转换测试为将HANA数据转换到Or…...
互联网摸鱼日报(2024-04-01)
互联网摸鱼日报(2024-04-01) 36氪新闻 「矽迪半导体」获数千万天使轮融资,提供高效功率半导体方案|硬氪首发 本周双碳大事:国资委即将发布央企ESG指导意见;上海发文推动建立产品碳足迹管理体系;隆基新硅片面世 数字…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...
