父子进程和线程解决TCP多个端口通信的问题

在这里插入图片描述
接上一篇TCP协议的通信小程序与函数接口

先来复习一下TCP通信的规则,客户端和服务端先建立一个套接字并绑定了地址信息,然后服务端去侦听客户端发送的连接请求(历经三次握手),请求成功后就拷贝信息到新的套接字结构体中,让他和客户端进行无缝连接,完成通信。
在这里插入图片描述

多个端口通信的问题所在

当我们只有一个客户端与服务端发送通信的时候,我们可以看到通信是正常进行的。问题就在于我们再加了一个客户端的时候,就出现了这种情况:
在这里插入图片描述
在服务端与第一个客户端完成连接并正常通信的时候,第二个客户端发起连接后,虽然可以正常连接,但是好像信息没有发生到服务端。

  • 查看客户端2
ps aux | grep ./client
pstack [pid]

在这里插入图片描述

  • 查看服务端
ps aux | grep ./server
pstack [pid]
sudo netstat -anp | grep 19999 //查看端口连接状态

在这里插入图片描述
我们可以看到,虽然服务端与两个客户端都建立了连接,但是单进程的服务端好像无法完成和两个客户端的通信,服务端只能和一个客户端建立稳定安全的通信,其他客户端就会陷入接收阻塞中。

那么可以有的解决方法就是,创建多个进程,让每一个进程都当做一个套接字,单独为客户端服务,有两种方式

  • 父子进程:父进程执行监听的逻辑,子进程执行通信功能
  • 多线程:主线程进行监听的逻辑,其他线程进行通信的功能

进程版本

在这里插入图片描述

任务分配

  • 父进程:接收新的连接,完成listen()任务;创建子进程,这时子进程中的信息和父进程的信息是相同的。
  • 子进程:和客户端进行TCP通信

再接收新的连接之后,不能让父进程带着新创建的套接字和客户端进行通信。

  • 因为文件描述符只有1024个位置,如果让父进程进行通信的话,那么文件描述符被用完之后就又只能陷入阻塞的状态了。
    所以说父进程还需要关闭套接字,防止文件描述符被占满。(不考虑高并发的情况)

修改后的服务端程序

#include "tcpser.hpp"
#include <signal.h>
#include <sys/wait.h>

//信号触发的回调函数
void sigcallback(int signo)
{
    (void)signo;
    while(1)
    {
        //-1 等待任何子进程退出,没有限制,waitpid 和 pid的作用一样
        //NULL 表示父进程不关心子进程的终止状态
        //WHONANG 表示子进程如果没有改变,就不阻塞,直接返回0
        waitpid(-1,NULL,WNOHANG);
    }
}


int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        cout<<"请输入正确的参数 [./ser] [ip] [port]"<<endl;
        return 0;
    }
    
    signal(SIGCHLD,sigcallback);

    string ip = argv[1];
    uint16_t port = atoi(argv[2]);

    TcpClass ts;

    //创建套接字
    if(!ts.CreateSocket())
    {
        return 0;
    }

    //绑定地址信息
    if(!ts.Bind(ip,port))
    {
        return 0;
    }

    //监听
    if(!ts.Listen(10))
    {
        return 0;
    }

    while(1)
    {
        TcpClass new_ts;
        struct sockaddr_in new_addr;//将新建的套接字存放的结构体
        //等待连接
        if(!ts.Accept(new_ts,&new_addr))
        {
            continue;
        }

        //成功建立连接
        cout<<"有一个新的连接 ["<<inet_ntoa(new_addr.sin_addr)<<"] ["<<ntohs(new_addr.sin_port)<<"] "<<endl;

        //创建子进程,让子进程进行通信
        int pid = fork();
        if(pid < 0)
        {
            perror("fork");
            exit(1);
        }
        else if(pid == 0)
        {
            //child
            while(1)
            {
                //接收客户端的信息
                string buf;
                new_ts.Recv(buf);
                cout<<"客户端说 : "<<buf<<endl;

                cout<<"给客户端回话 : ";
                fflush(stdout);
                cin>>buf;
                new_ts.Send(buf);
            }

            new_ts.Close();
            exit(1);//退出当前进程,
        }
        else 
        {
            //father
			//删除父进程的文件描述符
            new_ts.Close();
        }
    }
    return 0;
}
  • 运行结果

在这里插入图片描述

多线程版本

在这里插入图片描述

  • 进程当中多个线程共享着同一个文件描述符,多线程了解

任务分配

  • 主线程:负责监听listen(),并创造工作线程,给工作线程分配通信的任务
  • 工作线程:和客户端进行通信功能

程序实现

#include "tcpser.hpp"
#include <pthread.h>

void* ThreadStart(void* arg)
{
    pthread_detach(pthread_self());
    tcpClass* ts = (tcpClass*)arg;

    while(1)
    {
        string buf;
        ts->Recv(buf);
        cout<<"客户端发来请求,说 :"<<buf<<endl;

        cout<<"给客户端回话 : ";
        fflush(stdout);
        cin>>buf;
        ts->Send(buf);
    }
    ts->Close();
    delete ts;

    return NULL;
}

int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        cout<<"请输入正确的参数 [./ser] [ip] [port]"<<endl;
        return 0;
    }

    string ip = argv[1];
    uint16_t port =  atoi(argv[2]);

    tcpClass ts;

    //创建套接字
    if(!ts.CreateSocket())
    {
        return 0;
    }

    //绑定地址信息
    if(!ts.Bind(ip,port))
    {
        return 0;
    }

    //建立监听
    if(!ts.Listen(10))
    {
        return 0;
    }

    while(1)
    {
        tcpClass* new_ts = new tcpClass();
        struct sockaddr_in peeraddr;
        if(!ts.Accept(new_ts,peeraddr))
        {
            continue;
        }

        cout<<"有一个新的连接 "<<inet_ntoa(peeraddr.sin_addr)<<' '<<ntohs(peeraddr.sin_port)<<endl;

        pthread_t tid;
        int ret = pthread_create(&tid,NULL,ThreadStart,(void*)new_ts);
        if(ret < 0)
        {
            perror("pthread_create");
            return 0;
        }
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值