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…...
C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
从面试角度回答Android中ContentProvider启动原理
Android中ContentProvider原理的面试角度解析,分为已启动和未启动两种场景: 一、ContentProvider已启动的情况 1. 核心流程 触发条件:当其他组件(如Activity、Service)通过ContentR…...
面试高频问题
文章目录 🚀 消息队列核心技术揭秘:从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"?性能背后的秘密1.1 顺序写入与零拷贝:性能的双引擎1.2 分区并行:数据的"八车道高速公路"1.3 页缓存与批量处理…...
[拓扑优化] 1.概述
常见的拓扑优化方法有:均匀化法、变密度法、渐进结构优化法、水平集法、移动可变形组件法等。 常见的数值计算方法有:有限元法、有限差分法、边界元法、离散元法、无网格法、扩展有限元法、等几何分析等。 将上述数值计算方法与拓扑优化方法结合&#…...
内窥镜检查中基于提示的息肉分割|文献速递-深度学习医疗AI最新文献
Title 题目 Prompt-based polyp segmentation during endoscopy 内窥镜检查中基于提示的息肉分割 01 文献速递介绍 以下是对这段英文内容的中文翻译: ### 胃肠道癌症的发病率呈上升趋势,且有年轻化倾向(Bray等人,2018&#x…...
CMS内容管理系统的设计与实现:多站点模式的实现
在一套内容管理系统中,其实有很多站点,比如企业门户网站,产品手册,知识帮助手册等,因此会需要多个站点,甚至PC、mobile、ipad各有一个站点。 每个站点关联的有站点所在目录及所属的域名。 一、站点表设计…...
