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

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值