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"查看线程数量,可以看到一共有三个线程,其中有一个是主线程还有两个线程分别接收两个客户端发来的信息。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/104639.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java包的package-info.java文件

Java包的下面可以放一个package-info.java文件&#xff0c;在这个文件中声明包&#xff0c;在注释中增加包的介绍信息。这样javadoc工具就可以优先从这个文件中获取包的介绍信息。 例如Java工程&#xff0c;在包com.thb下面有package-info.java文件&#xff1a; package-inf…

STM32之17.PWM脉冲宽度调制

一LED0脉冲宽度调制在TIM14_CHI&#xff0c;先将LED&#xff08;PF9&#xff09;代码配置为AF推挽输出模式&#xff0c;将PF9引脚连接到TIM14&#xff0c; #include <stm32f4xx.h>static GPIO_InitTypeDef GPIO_InitStruct;void Led_init(void) {//打开端口F的硬件时钟&a…

成都睿趣科技:抖音开网店前期的流程是什么

随着互联网的快速发展&#xff0c;电子商务成为了商业领域中的一大利器&#xff0c;而在电商领域中&#xff0c;抖音作为一个强大的平台&#xff0c;也吸引了众多商家的目光。然而&#xff0c;要在抖音上开设一家成功的网店&#xff0c;并不是一件简单的事情&#xff0c;需要经…

微服务基础知识

文章目录 微服务基础知识一、系统架构的演变1、单体应用架构2、垂直应用架构3、分布式SOA架构&#xff08;1&#xff09;什么是SOA&#xff08;2&#xff09;SOA架构 4、微服务架构5、SOA和微服务的关系&#xff08;1&#xff09;SOA&#xff08;2&#xff09;微服务架构 二、分…

More Effective C++学习笔记(4)

目录 条款16&#xff1a;谨记 80 - 20 法则条款17&#xff1a;考虑使用lazy evaluation&#xff08;缓式评估&#xff09;条款18&#xff1a;分期摊还预期的计算成本条款19&#xff1a;了解临时对象来源条款20&#xff1a;协助完成 “ 返回值优化 ”条款21&#xff1a;利用重载…

JDBC基础

文章目录 创建依赖基本步骤案例1. statement案例&#xff08;不常用&#xff09;&#xff1a;2.prepared 案例&#xff08;常用&#xff09;3.prepared curd 案例&#xff08;获取列信息&#xff09;5.事务一致性&#xff08;转账&#xff09; 创建依赖 各个版本有各个的依赖包…

决策树算法:随机森林民主算法【02/2】

决策树民主&#xff1a;随机森林算法 一、介绍&#xff1a; 记住您在阅读亚马逊上的所有评论后进行的最后一次购买&#xff0c;或者在查看 IMDb 评级后您观看的以前的电影。人类是社会动物&#xff0c;他人的意见和行为自然会影响我们。我们的决定在很大程度上取决于“群体智慧…

Matlab分割彩色图像

彩色图像 彩色图像除有亮度信息外&#xff0c;还包含有颜色信息。以最常见的RGB&#xff08;红绿蓝&#xff09;彩色空间为例来简要说明彩色图像&#xff1a; 彩色图像可按照颜色的数目来划分。例如&#xff0c;256色图像和真彩色图像&#xff08;2的16次方&#xff1d;21677…

智信数科SMS短信平台安装教程

智信SMS客户端下载 使用智信SMS短信平台前&#xff0c;需要前往公司官网或者百度网盘共享地址下载最新版本的短信平台。 下载地址一&#xff1a;公司官网 下载地址二&#xff1a;百度网盘共享&#xff08;推荐&#xff09; 智信SMS客户端安装 安装前&#xff0c;确保您已经正…

【MySQL系列】Select语句单表查询详解(二)ORDERBY排序

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

Oracle的学习心得和知识总结(二十八)|Oracle数据库数据库回放功能之论文二翻译及学习

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《Oracle Database SQL Language Reference》 2、参考书籍&#xff1a;《PostgreSQL中文手册》 3、EDB Postgres Advanced Server User Gui…

私有化部署即时通讯平台,30分钟替换钉钉和企业微信

随着企业对即时通讯和协作工具的需求不断增长&#xff0c;私有化部署的即时通讯平台成为企业的首选。WorkPlus作为有10余年行业深耕经验与技术沉淀品牌&#xff0c;以其安全高效的私有化部署即时通讯解决方案&#xff0c;帮助企业在30分钟内替换钉钉和企业微信。本文将深入探讨…

adb 命令

1.adb shell dumpsys activity top | find "ACTIVITY" 查看当前运行的activity包名 2.adb shell am start -n 包名/页面名 打开应用的页面 3.查看将要启动或退出app的包名 adb shell am monitor 只有在启动或退出的时候才会打印 4.查看当前启动应用的包名 ad…

【动手学深度学习】--20.目标检测和边界框

文章目录 目标检测和边界框1.目标检测2.边界框 目标检测和边界框 学习视频&#xff1a;物体检测和数据集【动手学深度学习v2】 官方笔记&#xff1a;目标检测和边界框 在图像分类任务中&#xff0c;我们假设图像中只有一个主要物体对象&#xff0c;我们只关注如何识别其类别…

用Idea把SpringBoot项目打包镜像上传至docker

1、设置docker把2375端口开起来 命令查看docker装在哪里 vim docker.service 新增 -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock 2、配置Dockerfile 我在跟pom同一层 3、配置docker-maven-plugin <plugin><groupId>com.spotify</groupId><arti…

c语言调用mciSendString播放音乐

如下所示&#xff0c;这是一个使用c语言调用系统方法mciSendString()&#xff0c;让系统播放音乐的示例&#xff1a; baihuaxiang 代码&#xff1a; #include <graphics.h> #include <Windows.h> #include <mmsystem.h>#pragma comment(lib,"WINMM.LIB…

element-ui中的el-table合并单元格

描述&#xff1a; 在写项目的时候有时候会经常遇到把行和列合并起来的情况&#xff0c;因为有些数据是重复渲染的&#xff0c;不合并行列会使表格看起来非常的混乱&#xff0c;如下&#xff1a; 而我们想要的数据是下面这种情况&#xff0c;将重复的行进行合并&#xff0c;使表…

STM32设置为I2C从机模式(HAL库版本)

STM32设置为I2C从机模式&#xff08;HAL库版本&#xff09; 目录 STM32设置为I2C从机模式&#xff08;HAL库版本&#xff09;前言1 硬件连接2 软件编程2.1 步骤分解2.2 测试用例 3 运行测试3.1 I2C连续写入3.2 I2C连续读取3.3 I2C单次读写测试 4 总结 前言 我之前出过一篇关于…

XXX程序 详细说明

用于记录理解PC程序的程序逻辑 1、程序的作用 根据原作者的说明&#xff08;文件说明.txt&#xff09;&#xff0c;该程序 (PC.py) 的主要作用是提取某一个文件夹中的某个设备 (通过config中的信息看出来是Ag_T_8) 产生的日志文件&#xff0c;然后提取其中某些需要的数据&…

内网渗透神器CobaltStrike之权限提升(七)

Uac绕过 常见uac攻击模块 UAC-DLL UAC-DLL攻击模块允许攻击者从低权限的本地管理员账户获得更高的权限。这种攻击利用UAC的漏洞&#xff0c;将ArtifactKit生成的恶意DLL复制到需要特权的位置。 适用于Windows7和Windows8及更高版本的未修补版本 Uac-token-duplication 此攻…