Linux下的TCP通信(二)—— 多进程模式、多线程模式

上一次尝试了一个最简单的TCP通信,但是一般来说,服务器可以和多个主机同时进行通信,所以这次我们要实现的是多客户端和服务器进行通信。

客户端和上次一样,不清楚的可以看看我之前写的客户端和服务端。服务端的变化主要是在接受连接之后,下面就依次来介绍有哪些地方是需要变动的。

Linux下的TCP通信(一)—— 单进程模式_abs(ln(1+NaN))的博客-优快云博客https://blog.youkuaiyun.com/challenglistic/article/details/125831489?spm=1001.2014.3001.5502


目录

一、包装"读写套接字"的功能

二、多进程模式

1、创建子进程

2、回收子进程

3、打印连接到服务端的客户端IP和端口号(非必要)

4、测试结果

三、多线程模式

1、创建新线程

2、线程执行函数

3、测试


一、包装"读写套接字"的功能

因为现在有多个进程或者线程,为了方便了解整体是如何运作的,个人建议把读取套接字、向套接字写内容等功能放到一个函数里。这里我起名为ServiceIO,内容和之前完全一样,只是全都放到ServiceIO这个函数里了。

void ServiceIO(int sock)
{
  while (1)
  {
    char buffer[1024];
    memset(buffer, 0, sizeof(buffer));
    int s = read(sock, buffer, sizeof(buffer) - 1);
    if (s > 0)
    {
      buffer[s] = 0;
      std::cout << "client["<<getpid()<<"]# " << buffer << std::endl;

      std::string msg = "服务端收到了客户端的消息: ";
      msg.append(buffer);
      write(sock, msg.c_str(), msg.size());
    }
    else if (s == 0)
    {
      std::cout << "客户端已退出..." << std::endl;
    }
    else
    {
      std::cerr << "读取出错..." << std::endl;
      break;
    }
  }
}

二、多进程模式

1、创建子进程

创建子进程使用的是fork函数,返回值为子进程的pid,如果pid>0,代表当前进程是父进程;如果pid = 0,代表当前进程是子进程(子进程自己不会创建新的进程,所以pid = 0)

父进程:把提供服务的任务全部交给子进程,自己呢,就去继续接受下一个连接。由于父进程不需要提供服务,用于提供服务的套接字 new_sock 也就用不上了。

子进程:子进程继承父进程的文件描述符,也就是说子进程会有 监听套接字、用于提供服务的套接字。但是子进程无需去接收新的连接,所以一开始就可以把监听套接字 server 关闭,然后提供服务,提供完服务以后,把 用于提供服务的套接字 new_sock也关闭。

//这里列举出之写的部分内容
for (;;)
{
    struct sockaddr_in peer;
    socklen_t len = sizeof(peer);
    int new_sock = accept(server, (struct sockaddr *)&peer, &len);
    if (new_sock < 0)
    {
      continue;
    }


    //从这里开始是新的内容
    int pid = fork();
    if (pid == 0)
    {
      close(server);            //server是监听套接字
      //子进程
      ServiceIO(new_sock);
      close(new_sock);         //new_sock是用于提供服务的套接字
    }
    
    //父进程
    close(new_sock);    //父进程无需提供服务,关闭new_sock,回到accpet函数处,继续接收下一个连接
}

2、回收子进程

说到子进程,那就需要回收子进程,否则会出现僵尸进程,从而造成内存泄漏。回收子进程的方式有两种,一种是通过wait函数,一种是通过信号。

解决僵尸进程的两种方式(重温waitpid函数、了解17号信号SIGCHLD)_abs(ln(1+NaN))的博客-优快云博客https://blog.youkuaiyun.com/challenglistic/article/details/124627448当子进程退出的时候,会向父进程发送17号信号,我们对17号信号的处理设置为忽视,这样就无需主动回收子进程。

//无视17号信号,无需回收子进程
signal(17,SIG_IGN); 

for(;;){
    //accept接收客户端连接


    //创建子进程,让子进程提供服务

} 

3、打印连接到服务端的客户端IP和端口号(非必要)

为了方便看效果,这里打印出连接的客户端IP和端口号。

for(;;){
    //accept接收客户端连接


    //打印客户端的IP和端口号
    const char *client_ip = inet_ntoa(peer.sin_addr);
    uint16_t client_port = ntohs(peer.sin_port);
    std::cout << "客户端:[" << client_ip << ":" << client_port << "]已连接...." << std::endl;

    //创建子进程,让子进程提供服务

}    

4、测试结果

测试结果如下

=========================服务端=========================

 =========================客户端=========================

三、多线程模式

进程承担的是系统资源,创建的子进程太多,会给系统造成较重的负担。我们可以采用轻量级进程 —— 线程来实现多个客户端和一个服务端的TCP通信。

1、创建新线程

每当我们接收到一个连接,我们就创建一个新线程来提供服务

//这里列举出之写的部分内容
for (;;)
{
    struct sockaddr_in peer;
    socklen_t len = sizeof(peer);
    int new_sock = accept(server, (struct sockaddr *)&peer, &len);
    if (new_sock < 0)
    {
      continue;
    }

    const char *client_ip = inet_ntoa(peer.sin_addr);
    uint16_t client_port = ntohs(peer.sin_port);
    std::cout << "客户端:[" << client_ip << ":" << client_port << "]已连接...." << std::endl;


    //从这里开始是新的内容
    pthread_t tid;
    pthread_create(&tid,nullptr,ServiceRoutine,(void*)&new_sock);
}

2、线程执行函数

如果你不想写pthread_join来回收子线程,可以采用线程分离的方式。

void* ServiceRoutine(void* args){
  pthread_detach(pthread_self());    //分离子线程

  int sock = *(int*)args;
  ServiceIO(sock);
  close(sock);
}

3、测试

测试结果和多进程的测试结果差不太多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值