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…...
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果