Linux 多线程解决客户端与服务器端通信
一、一个服务器端只能和一个客户端进行通信(单线程模式)
客户端代码ser.c如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>int main()
{//1.创建套接字int sockfd=socket(AF_INET,SOCK_STREAM,0);//监听套接字if(sockfd==-1){printf("创建失败\n");exit(1);}//定义套接字地址struct sockaddr_in saddr,caddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//2.指定套接字ip地址int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(res==-1){printf("绑定失败\n");exit(1);}//3.创建监听队列res=listen(sockfd,5);if(res==-1){printf("监听队列创建失败\n");exit(1);}while(1){int len = sizeof(caddr);//4.接收客户端的连接int c =accept(sockfd,(struct sockaddr*)&caddr,&len);//连接套接字if(c<0){continue;}printf("c=%d\n",c);while(1){char buff[128]={0};//5.接收客户端的消息int num=recv(c,buff,127,0);//recv的返回值是实际收到的字节数 //可能阻塞if(num<=0){break;}printf("buff=%s\n",buff);//6.向客户端回复消息send(c,"ok",2,0);}printf("关闭客户端\n");//7.关闭客户端close(c);}
}
客户端代码cli.c如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>int main()
{//1.创建套接字int sockfd=socket(AF_INET,SOCK_STREAM,0);if(sockfd==-1){printf("创建失败\n");}//定义套接字地址struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//2.连接服务器端int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(res==-1){printf("连接失败\n");exit(1);}while(1){printf("输入:");char buff[128]={0};fgets(buff,128,stdin);if(strncmp(buff,"end",3)==0){break;}//3.向服务器端发送消息send(sockfd,buff,strlen(buff),0);memset(buff,0,128);//4.接收服务器端回复的消息recv(sockfd,buff,127,0);printf("buff=%s\n",buff);}//5.关闭服务器端close(sockfd);exit(0);}
运行结果:
先在一个终端启动服务器端,然后在另一个终端启动客户端,此时,客户端就可以与服务器端进行通信:

二、接收缓冲区和发送缓冲区
每一个套接字都有两个缓冲区,即发送缓冲区和接收缓冲区。也就是说服务器端和客户端两端都有自己的发送缓冲区和接收缓冲区。
比如说客户端通过send向服务器端发送数据,那么这个数据就会被写入发送缓冲区中,只要send返回成功就说明所要发送的数据已经成功写入发送缓冲区中。发送缓冲区要通过底层网络协议把数据通过网络发送到服务器端的接收缓冲区中,此时服务器端的接收缓冲区中就放着从客户端发送来的数据,当服务器端执行recv的时候,服务器端会将客户端所发送来的数据接收出来,如果接收缓冲区是空的,那么recv就接受不到数据。反之,也一样。
套接字是一个全双工的通信方式。
将上述服务器端代码ser.c中的int num=recv(c,buff,127,0);这一行代码修改为int num=recv(c,buff,1,0);,此时启动服务器端和客户端:

通过命令netstat -natp来查看接收缓冲区和发送缓冲区还有没有数据。
三、两个客户端要与一个服务器端进行通信(多线程模式)
1.代码还是上述代码,但是运行结果失败
代码如上,运行结果如下:

从结果可以看出,第二个客户端的printf("输入:");这一行代码已经执行过了,说明此时connect已经执行成功,说明三次握手已经完成,建立了TCP连接,将这个建立好的连接放到了已完成三次握手的监听队列中,等待服务器端执行accept进行接收。第二个客户端send(sockfd,buff,strlen(buff),0);这一行代码也已经执行过了,但是send执行成功并不意味之已经把消息发送给了服务器端,而是说明所发送的数据现在已经存到了该客户端的发送缓冲区中,而此时服务器端此时也发送阻塞,阻塞在int num=recv(c,buff,127,0);这一行代码的位置,因为此时第一个客户端没有发送消息,所以客户端执行recv的时候发送阻塞,所以服务器端就没有机会执行accept去接收已完成三次握手的监听队列中与第二个客户端建立的连接,因此服务器端的接收缓冲区此时也接收不到第二个客户端从发送缓冲区发送来的数据,所以第二个客户端在执行recv的时候接收不到服务器端给它回复的消息而被阻塞。
当我们关闭第一个客户端之后,服务器端代码就退出小while循环,执行printf("关闭客户端\n");关闭与第一个客户端的连接,此时服务器端就可以执行accept接收已完成三次握手的监听队列中与第二个客户端建立的连接,然后接收到第二个客户端发送到服务器端的信息并输出,客户端也会得到服务器端的回复:

2.同时让两个客户端与服务器端进行通信成功
方法:再创建一个线程与第二个客户端连接。服务端接受一个客户端的连接后,创建
一个线程,然后在新创建的线程中循环处理数据。主线程只负责监听客户端的连接,并使用accept()接受连接,不进行数据的处理。如下图所示:

服务器端代码ser.c如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>//创建一个结构体,把想要传给线程函数的参数全部放到这个结构体中
struct Node_Arg
{int c;//连接套接字的描述符
};void* fun(void* arg)
{struct Node_Arg* p=(struct Node_Arg*)arg;int c=p->c;while(1){char buff[128]={0};//5.接收客户端发送来的信息int num=recv(c,buff,127,0);if(num<=0){break;}printf("buff(c=%d)=%s\n",c,buff);//6.向客户端回复信息send(c,"ok",2,0);}printf("关闭客户端\n");//7.关闭客户端close(c);free(p);//释放堆区空间}int main()
{//1.创建套接字int sockfd=socket(AF_INET,SOCK_STREAM,0);//监听套接字if(sockfd==-1){printf("创建失败\n");exit(1);}//定义套接字地址struct sockaddr_in saddr,caddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//2.指定套接字ip地址int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(res==-1){printf("绑定失败\n");exit(1);}//3.创建监听队列res=listen(sockfd,5);if(res==-1){printf("监听队列创建失败\n");exit(1);}while(1){int len = sizeof(caddr);//4.接收客户端的连接int c =accept(sockfd,(struct sockaddr*)&caddr,&len);//连接套接字if(c<0){continue;}printf("c=%d\n",c);pthread_t id;//在堆区为结构体struct Node_Arg申请一块空间struct Node_Arg*ptr=(struct Node_Arg*)malloc(sizeof(struct Node_Arg));ptr->c=c;pthread_create(&id,NULL,fun,ptr);}}
客户端代码没有改变cli.c如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>int main()
{//1.创建套接字int sockfd=socket(AF_INET,SOCK_STREAM,0);if(sockfd==-1){printf("创建失败\n");}//定义套接字地址struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//2.连接服务器端int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(res==-1){printf("连接失败\n");exit(1);}while(1){printf("输入:");char buff[128]={0};fgets(buff,128,stdin);if(strncmp(buff,"end",3)==0){break;}//3.向服务器端发送消息send(sockfd,buff,strlen(buff),0);memset(buff,0,128);//4.接收服务器端回复的消息recv(sockfd,buff,127,0);printf("buff=%s\n",buff);}//5.关闭服务器端close(sockfd);exit(0);}
运行结果:

根据结果可以看出,在启动服务器端之后,可以启动两个客户端与服务器端进行通信。
查看客户端与服务器端通信时的线程的数量:
通过命令ps -eLf | grep "ser"查看线程数量,可以看到一共有三个线程,其中有一个是主线程还有两个线程分别接收两个客户端发来的信息。

相关文章:
Linux 多线程解决客户端与服务器端通信
一、一个服务器端只能和一个客户端进行通信(单线程模式) 客户端代码ser.c如下: #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<sys/socket.h> #include<netinet…...
FMX的TListBox单选列表框
TListBox功能比较全,对于选择项,有“两种”模式,一种就是ListItem选中(界面上就是焦点和颜色变化),可以无,单选和多选。另一种是通过CheckBox来选择ListItem的选中。默认下,ShowChec…...
prompt工程(持续更新ing...)
诸神缄默不语-个人CSDN博文目录 我准备想办法把这些东西整合到我的ScholarEase项目里。到时候按照分类、按照prompt生成方法列一堆选项,用户自己生成prompt后可以选择在ScholarEase里面聊天,也可以复制到别的地方(比如ChatGPT网页版之类的&a…...
win11 docker-desktop安装记录
win11安装Docker踩坑实录 马上开始正式工作了,需要用到docker,以前在win10上安装过,新电脑是win11,心想肯定会遇到坑,就浅浅记录一下 首先看一下安装要求 需要wsl2 那么就先进行 wsl的更新 wsl --update注意这里网络…...
opencv特征提取、梯度计算
...
AI绘画工具MJ新功能有点东西,小白也能轻松一键换装
先看最终做出来的效果 直接来干货吧。Midjourney,下面简称MJ 1.局部重绘功能来袭 就在前两天,MJ悄咪咪上线了这个被众人期待的新功能:局部重绘。 对于那些追求创新和个性化的设计师来说,局部绘制不仅是一个实用的功能ÿ…...
java springboot sql防注入的6种方式
在Spring Boot中,可以通过使用参数绑定、预处理语句和使用ORM框架等方式来防止SQL注入。以下是几种常见的方式: 1. 参数绑定:通过使用参数绑定,将用户输入的数据作为参数传递给SQL语句,而不是将其直接拼接到SQL语句中…...
深度学习实战49-基于卷积神经网络和注意力机制的汽车品牌与型号分类识别的应用
大家好,我是微学AI,今天给大家介绍一下深度学习实战49-基于卷积神经网络和注意力机制的汽车品牌与型号分类识别的应用,该项目就像是一只智慧而敏锐的眼睛,专注地凝视着汽车世界。这个项目使用PyTorch作为强有力的工具,提供了一个深度学习的舞台,让我们能够设计和训练一个…...
Open3D(C++) 可视化(3)——批量动态可视化点云
目录 一、概述二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、概述 拿到一个新的点云数据集,想要快速查看数据集内点云的形状特征。然而,对于动辄几千个点云的数据集而言,逐个将点云拖入…...
opencv 文档识别+UI界面识别系统
目录 一、实现和完整UI视频效果展示 主界面: 识别结果界面: 查看处理图片过程: 查看历史记录界面: 二、原理介绍: 将图像变换大小->灰度化->高斯滤波->边缘检测 轮廓提取 筛选第三步中的轮廓…...
下|税收大数据应用研究
上文呢,对于税收大数据我们已经对它有了一定程度的认知。下篇呢,就研究一下应用方面有哪些优势和存在的不足之处。 一、税收大数据应用的优势 1.提升征管效率和预测准确率 税收部门通过收集、分析海量数据。并建立数据分析模型来提升效率和准确率。税…...
数据库连接池druid 的jar包官网下载-最新版下载
进入官网Central Repository: com/alibaba/druid 往下滑 找到最新版点击进入 找到该jar包 点击即可下载...
2023河南萌新联赛第(六)场:河南理工大学 C - 旅游
2023河南萌新联赛第(六)场:河南理工大学 C - 旅游 时间限制:C/C 1秒,其他语言2秒 空间限制:C/C 262144K,其他语言524288K Special Judge, 64bit IO Format: %lld 题目描述 小C喜欢旅游…...
Java | IDEA中Netty运行多个client的方法
想要运行多个client但出现这种提示: 解决方法 1、打开IDEA,右上角找到下图,并点击 2、勾选...
【蓝桥杯】 [蓝桥杯 2015 省 A] 饮料换购
原题链接:https://www.luogu.com.cn/problem/P8627 1. 题目描述 2. 思路分析 小伙伴们如果没有思路可以看看这篇文章~(这里很详细讲解了三种方法!) https://blog.csdn.net/m0_62531913/article/details/132385341?spm1001.2014…...
操作系统-笔记-第三章-内存管理
🌸章节汇总 一、第一章——操作系统的概念 二、第二章——【进程】 二、第二章——【线程】编辑 二、第二章——【进程调度】 二、第二章——【进程同步与互斥】 二、第二章——【锁】 三、第三章——内存管理 四、第四章——文件管理 五、第五章——输入输出管理…...
详解单体架构和微服务(概念,优缺点和区别)
单体架构和微服务 单体架构和微服务架构区别?为什么要用微服务架构? 单体架构的整个系统是一个War包,即war包走天下。微服务架构的项目是很多个war包(一个子系统一个)。 单体架构的优点: 架构简单开发测试部署简单…...
储能运行约束的Matlab建模方法
最近一段时间有很多人问我最优潮流计算中储能系统的建模方法。部分朋友的问题我回复了,有些没有回消息的,我就不再一一回复了,在这里我写一篇博客统一介绍一下。 1.储能系统介绍 首先,让【GPT】简单介绍一下储能系统:…...
微信小程序 车牌号输入组件
概述 一个小组件,用于方便用户输入车牌号码 详细 概述 有时候我们开发过程中会遇到需要用户输入车牌号的情况,让客户通过自带键盘输入,体验不好且容易出错,例如车牌号是不能输入O和I的,因此需要有一个自定义的键盘…...
Bootstrap Blazor 实战动态表单组件
1.新建工程 源码 新建工程b18ValidateForm,使用 nuget.org 进行 BootstrapBlazor 组件安装, Chart 库,字体. 将项目添加到解决方案中 dotnet new blazorserver -o b18ValidateForm dotnet add b06chart package BootstrapBlazor dotnet add b06chart package BootstrapBlazo…...
AI智能体工作完整源码大公开!企业级多Agent框架,一键私有化部署
温馨提示:文末有资源获取方式最近“龙虾AI”的热度席卷技术圈,大家都在讨论如何“养殖”自己的智能体。但真正落地时,技术门槛、Token消耗与复杂的协同问题,往往让普通用户和企业望而却步。今天我们不谈概念,直接分享一…...
告别单打独斗!Apipost 8协作版数据迁移保姆级教程(含团队项目处理)
Apipost 8协作版数据迁移实战:从个人到团队的无缝衔接 第一次打开Apipost 8协作版时,我盯着那个"迁入项目"按钮犹豫了整整十分钟——作为独立开发者,我的旧版本里积累了237个接口文档和56个测试集合,它们就像我精心搭建…...
K8s Ingress实战:如何为静态资源开启Gzip压缩和Cache Control(附完整ConfigMap配置)
Kubernetes Ingress高级配置:静态资源Gzip压缩与缓存策略实战指南 在当今快节奏的数字化体验中,网页加载速度直接影响用户留存率和转化率。根据行业研究,页面加载时间每增加1秒,可能导致转化率下降7%。作为Kubernetes运维专家&…...
深度学习 三次浪潮、三大驱动力与神经科学的恩怨(二)
1. 一个领域,多个名字 很多人以为"深度学习"是一个全新的领域。事实上,它的历史可以追溯到 20 世纪 40 年代——只不过在不同时期,它被叫过完全不同的名字: 1940s-1960s:被称为控制论(Cybernetic…...
新手指南:掌握3MF格式实现Blender高效3D打印工作流
新手指南:掌握3MF格式实现Blender高效3D打印工作流 【免费下载链接】Blender3mfFormat Blender add-on to import/export 3MF files 项目地址: https://gitcode.com/gh_mirrors/bl/Blender3mfFormat 副标题:从格式解析到自动化处理的完整应用方案…...
Windows 10/11 下用 Anaconda 和 Hadoop 3.3.6 搞定 PySpark 环境,附赠 Winutils 下载避坑指南
Windows 10/11 下用 Anaconda 和 Hadoop 3.3.6 搞定 PySpark 环境,附赠 Winutils 下载避坑指南 在 Windows 系统上搭建 PySpark 开发环境,对于数据科学家和开发者来说既是一个必经之路,也是一场充满挑战的冒险。不同于 Linux 或 macOS 系统&a…...
OpCore-Simplify:智能配置驱动的OpenCore EFI自动化构建工具
OpCore-Simplify:智能配置驱动的OpenCore EFI自动化构建工具 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify 🤔 配置黑苹果的痛…...
OpenClaw资源监控方案:Qwen3-32B镜像驱动服务器健康巡检
OpenClaw资源监控方案:Qwen3-32B镜像驱动服务器健康巡检 1. 为什么需要AI驱动的资源监控? 去年我的个人开发服务器连续宕机三次,每次都是因为磁盘写满导致服务崩溃。传统监控工具虽然能发出警报,但往往在问题发生后才会触发&…...
告别序列‘拉直’的暴力美学:手把手复现MaIR,体验保持图像局部与连续性的Mamba新玩法
告别序列“拉直”的暴力美学:手把手复现MaIR,体验保持图像局部与连续性的Mamba新玩法 在计算机视觉领域,图像修复任务(如去噪、超分、去模糊)一直是研究热点。传统方法往往将2D图像“拉直”为1D序列进行处理࿰…...
EVA-01场景应用:电商商品分析、文档信息提取,真实工作流分享
EVA-01场景应用:电商商品分析、文档信息提取,真实工作流分享 1. 从科幻到现实:EVA-01的商业价值 在电商运营和文档处理的日常工作中,我们常常面临这样的挑战:海量商品图片需要人工标注关键信息,繁杂的合同…...
