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

news/2024/5/17 16:16:51 标签: linux, 服务器, 运维, tcp

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

客户端代码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.niftyadmin.cn/n/4966910.html

相关文章

【golang】for语句和switch语句

使用携带range子句的for语句时需要注意哪些细节&#xff1f; numbers1 : []int{1, 2, 3, 4, 5, 6} for i : range numbers1 {if i 3 {numbers1[i] | i} } fmt.Println(numbers1)这段代码执行后会打印出什么内容&#xff1f; 答案&#xff1a;[1 2 3 7 5 6] 当for语句被执行…

【javaweb】学习日记Day6 - Mysql 数据库 DDL DML DQL

之前学习过的SQL语句笔记总结戳这里→【数据库原理与应用 - 第六章】T-SQL 在SQL Server的使用_Roye_ack的博客-CSDN博客 目录 一、概述 1、如何安装及配置路径Mysql&#xff1f; 2、SQL分类 二、DDL 数据定义 1、数据库操作 2、IDEA内置数据库使用 &#xff08;1&…

CentOS KVM虚拟安装和开机启动

1. 配置系统 关闭SELinux setenforce 0持久化关闭配置 vi /etc/selinux/config2. 安装虚拟化软件 安装 KVM、QEMU等虚拟化软件。 yum install qemu-kvm qemu-img virt-manager libvirt virt-install virt-viewer 检查LVM模块是否已经加载 lsmod |grep kvm设置开机启动 s…

【redis问题】Caused by: io.netty.channel

遇到的问题&#xff1a; 在使用 RedisTemplate 连接 Redis 进行操作的时候&#xff0c;发生了如下报错&#xff1a; 测试代码为&#xff1a; 配置文件&#xff1a; 问题根源&#xff1a; redis没有添加端口映射解决方案&#xff1a; 删除原来的redis容器&#xff0c;添加新…

HTML番外篇(四)-HTML5新增元素-CSS常见函数-理解浏览器前缀-BFC

一、HTML5新增元素 1.HTML5语义化元素 在HMTL5之前&#xff0c;我们的网站分布层级通常包括哪些部分呢&#xff1f; header、nav、main、footer ◼ 但是这样做有一个弊端&#xff1a; 我们往往过多的使用div, 通过id或class来区分元素&#xff1b;对于浏览器来说这些元素不…

2023年高教社杯数学建模思路 - 复盘:光照强度计算的优化模型

文章目录 0 赛题思路1 问题要求2 假设约定3 符号约定4 建立模型5 模型求解6 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 问题要求 现在已知一个教室长为15米&#xff0c;宽为12米&…

VScode 编辑器报错: ‘HelloWorld‘ is declared but its value is never read.

.vue文件被标识红色波浪线&#xff1b;提示&#xff1a; HelloWorld is declared but its value is never read. 问题原因&#xff1a; 因为vue3已经不支持vetur插件。 1、在扩展里面进行搜索Vetur插件&#xff0c;进行禁用或卸载&#xff1b; 2、在 VScode扩展里面搜索并下载…

LVS DR模式搭建

目录 一、DR模式概述 一、与NET模式的区别 二、操作命令图 三、搭建流程 一、首先配置三台虚拟机并配置环境&#xff08;关闭防火墙&#xff0c;宽容模式&#xff09; 二、ping通百度 三、在115.3的&#xff08;lvs&#xff09;虚拟机上安装 ipvsadm 四、调整ARP参数 五…