Linux 多线程解决客户端与服务器端通信

news2024/11/25 6:36:06

一、一个服务器端只能和一个客户端进行通信(单线程模式)

客户端代码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.coloradmin.cn/o/922947.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

美国商务签证拒签了怎么办?

当面临美国商务签证被拒签的情况时&#xff0c;许多申请人可能会感到失望和困惑。然而&#xff0c;拒签并不意味着绝望&#xff0c;您仍然有一些选项可以考虑。以下是知识人网小编的一些建议&#xff0c;希望能对您有所帮助&#xff1a; 1.查明拒签原因&#xff1a;首先&#x…

vue中使用echarts三维的项目

需要安装 echarts 同时引入 echarts-gl 我安装的版本&#xff1a; "echarts": "^5.3.2", "echarts-gl": "^2.0.9", 效果 &#xff1a; 安装后main.js引入 import Vue from "vue"; import * as echarts from "echart…

八月更新 | CI 构建计划触发机制升级、制品扫描 SBOM 分析功能上线!

点击链接了解详情 这个八月&#xff0c;腾讯云 CODING DevOps 对持续集成、制品管理、项目协同、平台权限等多个产品模块进行了升级改进&#xff0c;为用户提供更灵活便捷的使用体验。以下是 CODING 新功能速递&#xff0c;快来看看是否有您期待已久的功能特性&#xff1a; 01…

人工智能与机器学习Pytorch手写数字识别-MINIST数据集识别篇

上期文章,我们分享了Pytorch手写数字的训练,当pytorch训练完成后,保存了训练的参数,方便本期使用预训练参数,进行手写数字的识别,我们准备一个手写数字的图片,可以自己在画图软件中,直接写个数字 手写数字 1、导入第三方库 导入第三方库 2、建立神经网络 神经网络的…

如何保护自己知识产权,建立代码护城河——建立自己的静态库,x86和arm平台的实例讲解

前言 &#xff08;1&#xff09;想象一下&#xff0c;假如我们幸幸苦苦写了一个封装库代码&#xff0c;为了建立护城河&#xff0c;我们企业不愿意把真实的代码提供给用户。怕客户拿了代码&#xff0c;这个合同结束&#xff0c;稍微改一点点&#xff0c;就盗用我们的技术&#…

四信桥梁监测解决方案

方案背景 随着我国经济水平的快速发展,桥梁作为交通运输的重要组成节点&#xff0c;其设计结构、耐久性和使用年限以及维护管理等安全状况一直是公众关心的问题。由于对桥梁运营状态下产生结构问题不能及时发现&#xff0c;近年来桥梁事故屡见不鲜&#xff0c;传播迅速&#x…

MinIO【部署 01】MinIO安装及SpringBoot集成简单测试

MinIO安装及SpringBoot集成测试 1.下载安装1.1 Install the MinIO Server1.2 Launch the MinIO Server1.3 Connect Your Browser to the MinIO Server 2.SpringBoot集成2.1 依赖及配置2.2 代码2.3 测试结果 1.下载安装 下载 https://min.io/download#/linux&#xff1b; 安装文…

Map和Set—数据结构

文章目录 1.搜索1.1常见搜索方式1.2模型 2.map2.1介绍2.2 Map.Entry<K, V>2.3map的使用2.4遍历map2.5TreeMap和HashMap的区别 3.set3.1介绍3.2set的使用3.3遍历set3.4 TreeSet和HashSet的不同 4.搜索树4.1概念4.2实现4.3性能分析 5.哈希表5.1查找数据5.2冲突的概念5.3冲突…

一个新的品牌如何快速做好品牌宣传?媒介盒子有绝招

互联网快速发展的今天&#xff0c;大量信息进入人们的生活&#xff0c;只要有流量就将成为广告的渠道。今天这里提到的是新品牌&#xff0c;相比较而言又具有一定的特殊性。 新品牌可能是一个创业公司&#xff0c;刚刚研发出来的品牌&#xff0c;想要冲进这个信息化的市场&…

libjpeg实践1:源码编译和MJPG转BMP测试:

编译源码 下载源码 http://www.ijg.org/files/ wget http://www.ijg.org/files/jpegsrc.v9b.tar.gz解压&#xff1a; tar zxvf jpegsrc.v9b.tar.gz 开始配置和编译&#xff0c;因为是在ubuntu中测试。所以配置很简单 ./configure --prefix/home/lkmao/linux/3588-linux/…

SpringBoot 2.7 集成 Netty 4 解决粘包半包问题

文章目录 1 摘要2 核心代码2.1 Netty 服务端连接器2.2 Netty 客户端连接器2.3 Netty 服务端 Handler2.4 Netty 客户端 Handler 3 推荐参考资料4 Github 源码 1 摘要 Netty 的粘包和半包问题是由于 Netty 在接收消息时无法判断消息是否发送完毕&#xff0c;只能靠读取消息时是否…

每天一分享#读up有感#

不知道开头怎么写&#xff0c;想了一下&#xff0c;要不&#xff0c;就这样吧&#xff0c;开头也就写完 今日分享 分享一博主的分享——https://blog.csdn.net/zhangay1998/article/details/121736687 全程高能&#xff0c;大佬就diao&#xff0c;一鸣惊人、才能卓越、名扬四…

算法通关村十二关 | 字符串前缀问题

1. 最长公共前缀 题目&#xff1a;LeetCode14&#xff0c;14. 最长公共前缀 - 力扣&#xff08;LeetCode&#xff09; 思路一 我们先看公共前缀有什么特点。 第一种方式&#xff0c;竖着比较&#xff0c;如图左边所示&#xff0c;选取数组中第一个字符串的位置&#xff0c;每…

platform相关资料

Step 1: Hardware Settings for Vitis Platform — Vitis™ Tutorials 2021.2 documentationhttps://xilinx.github.io/Vitis-Tutorials/2021-2/build/html/docs/Vitis_Platform_Creation/Introduction/03_Edge_VCK190/step1.html https://www.cnblogs.com/VagueCheung/p/1313…

Tensoeboard的一些坑与技巧

安装 pip install tensorboard 安装过程中遇到tensoeboard.exe找不到&#xff0c;参考解决&#xff1a; https://blog.csdn.net/weixin_44532467/article/details/123525891 3.启动tensorboard 默认路径 &#xff08;&#xff09; tensorboard --logdir logstensorboard -…

淘宝API技术解析,实现获得淘宝APP商品详情原数据

淘宝API&#xff08;Application Programming Interface&#xff09;是为开发者提供的一组接口&#xff0c;用于与淘宝平台进行数据交互。通过使用淘宝API&#xff0c;开发者可以获得淘宝平台上商品、店铺、订单等各种数据&#xff0c;并进行相应的业务操作。 要实现获取淘宝A…

利用Kettle进行SQLServer与Oracle之间的数据迁移实践

待更新 https://it.cha138.com/tech/show-1275283.html

基于spring boot校园疫情信息管理系统/疫情管理系统

摘要 随着计算机技术&#xff0c;网络技术的迅猛发展&#xff0c;Internet 的不断普及&#xff0c;网络在各个领域里发挥了越来越重要的作用。特别是随着近年人民生活水平不断提高&#xff0c;校园疫情信息管理系统给学校带来了更大的帮助。 由于当前疫情防控形势复杂&#xff…

史上最全软件测试入门到精通【测试+测开】

测试学习大纲梳理 根据本人过往学习经验与理解&#xff0c;整理了一些关于测试学习内容与顺序&#xff0c;涵盖了基本软件测试工程师需要掌握的所有技能&#xff0c;希望可以给想了解的小伙伴们一些指引与帮助&#xff0c;有错误或需求的欢迎留言指出~ 一、web开发者模式 这…

[LeetCode周赛复盘] 第 359 场周赛20230820

[LeetCode周赛复盘] 第 359 场周赛20230820 一、本周周赛总结2828. 判别首字母缩略词1. 题目描述2. 思路分析3. 代码实现 2829. k-avoiding 数组的最小总和1. 题目描述2. 思路分析3. 代码实现 2830. 销售利润最大化1. 题目描述2. 思路分析3. 代码实现 2831. 找出最长等值子数组…