Linux下TCP多进程/多线程套接字通信

上篇文章简单的介绍了一下Linux下套接字通信的相关知识:
 http://blog.csdn.net/qq_29503203/article/details/60961537

但是存在一个缺陷就是只能进行单进程通信,我们都知道实际上不可能一个服务器一次只能有一个客户端,所以在这里对其进行一个改进。

多进程套接字TCP通信
我们通过fork出子进程去完成客户端发来的请求,而父进程只需用去accpet连接请求。这里还需注意的是:既然创建出子进程,那么就得考虑它的回收。先前学习回收子进程的方法很多,比如:通过发信号,通过函数(一般是父进程对子进程的一个等待)
Linux下TCP多进程/多线程套接字通信_第1张图片
今天将介绍一种新的方式,即在子进程中继续创建子进程,然后通过init系统对其进行回收,下来看着代码继续详说

server.c
#include
#include
#include
#include
#include
#include
#include
#include

int startup(int _port,const char* _ip)
{
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        exit(1);
    }

     int opt=1;
     if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))<0)
    {
        perror("setsockopt");
        exit(2);
    }


    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_port=htons(_port);
    local.sin_addr.s_addr=inet_addr(_ip);

    socklen_t len=sizeof(local);

   if(bind(sock,(struct sockaddr*)&local,len)<0)
   {
     perror("bind");
     exit(2);
    }

    if(listen(sock,5)<0)
    {
        perror("listen");
        exit(3);
    }
   return sock;
}
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        printf("Usage: [local_ip] [local_port]",argv[0]);
        return 3;
    }
    int listen_socket=startup(atoi(argv[2]),argv[1]);

    struct sockaddr_in remote;
    socklen_t len=sizeof(struct sockaddr_in);

    while(1)
    {
        int socket=accept(listen_socket,(struct sockaddr*)&remote,&len);
        if(socket<0)
        {
            perror("accept");
            continue;
        }

        pid_t pid=fork();
        if(pid==0)
        {
            if(fork()>0)
            {
                exit(1);
            }
            char buf[1024];
            while(1)
            {
               ssize_t _s=read(socket,buf,sizeof(buf)-1);
               if(_s>0)
               {
                 buf[_s]=0;
                 printf("client# %s\n",buf);   
               }
               else
               {
                 printf("client is quit!\n");
                 break;
               }
               printf("client,ip:%s,port:%d\n",inet_ntoa(remote.sin_addr)\
               ,ntohs(remote.sin_port)); 
            } 
        }
        else if(pid>0)
        {

           close(socket);
           waitpid(-1,NULL,WNOHANG);         
        }
        else
        {
            perror("fork");
            exit(2);
        }
    }    
    return 0;
}

这份代码修改了两个地方:
(1)在socket创建成功后,使用了setsockopt函数,下面将从两方面对其进行一个解释:
  • 它是用来解决什么问题的?
  • 不这样做会产生怎样的后果?
  • 函数原型的说明。
1>之前在测试的时候,当我们启动server,然后启动client,再用Ctrl+C使server终止,这时马上再运行server,结果是:
bind error:Address already in use   这时因为虽然server的应用程序终止了,但TCP协议的连接并没有完全断开,回忆一下TCP四次挥手的过程,因此不能再监听同样的server端口。
在server的TCP没有连接没有完全断开之前不允许重新监听是不合理的,TCP没有完全断开是指connfd(127.0.0.1:8080)没有完全断开,而我们重新监听的是listenfd(0.0.0.0:8080),虽然占用同一个端口,但IP地址不同,connfd对应的是与某个客户端通讯的一个具体的IP地址,而Listenfd对应的是wildcard address(通配符地址)。

解决这一办法就是使用setsockopt函数,设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符,通常在socket()和bind()调用之间调用该函数。

2>想想一个场景,当server服务器异常退出时,这会儿它处于TIME_WAIT状态(等待2msl长的时间),主动关闭的一方处于TIME_WAIT状态,而ip和端口号依然被占用,此时客户端无法连接到服务器,而服务器又无法重新启动,会造成网络数据的流失异常等多种情况,解决办法就是采用setsockopt函数。

3>函数原型,所在头文件及其返回值
Linux下TCP多进程/多线程套接字通信_第2张图片

(2)回收子进程
这里是这样一个情况,原本fork()出一个子进程,用它来执行回写等操作,而父进程里去等待子进程以致回收,但是考虑到性能问题,时间资源消耗问题,我们采取在这个子进程里再fork一次,如果子进程又创建成功,那么我们就可以使类似于它的父进程的这个说法的进程(简单来说就是刚开始父进程创建出来的那个进程)退出,然后该子进程就成为了孤儿进程,我们又知道孤儿进程一般是通过1号进程init系统回收的,所以这样将节约不少时间(不让儿子进程等待孙子进程太久而消耗太多的系统资源)。如下图分析:
Linux下TCP多进程/多线程套接字通信_第3张图片


多线程套接字TCP通信

         在前边的系统编程的学习中,我们知道线程是进程内部的一个执行流,由于同一进程的多个线程共享同一地址空间,如果可以把进程做的事交给多个线程去做就会节约不少资源,而我们又知道进程是程序的一次动态的执行过程,系统中的进程数过于多的话,会增加系统的负担,所以这里采用多线程实现通信对上面两种进行优化。 
实现方法: 
(1)主线程中创建出一个新线程,新线程的执行函数是读取信息,类似于上边的多进程间的通信。
(2)这里需要注意的就是线程等待和回收的问题。我们知道默认情况下,线程被创建成可结合的,如果我们通过pthread_join()的话,这里主线程阻塞式的去等待新线程会耗费很多的时间,所以这里我们可以将新的线程进行分离,分离之后的线程就不需要主线程去等待,而是由操作系统区回收。

代码实现:

tserver.c
#include
#include
#include
#include
#include
#include
#include
#include

int startup(int _port,char* _ip)
{
    assert(_ip);

    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        exit(2);
    }

    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_port=htons(_port);
    local.sin_addr.s_addr=inet_addr(_ip);
    socklen_t len=sizeof(local);
      
    int opt = 1;
    int stat= setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    if(bind(sock,(struct sockaddr*)&local,len)<0)
    {
        perror("bind");
        exit(3);
    }
    if(listen(sock,5)<0)
    {
        perror("listen");
        exit(4);
    }
    return sock;
}

void* handler_fd(void* arg)
{
    int sock=*((int*)arg);
    pthread_detach(pthread_self());
    char buf[1024];
    while(1)
    {
        ssize_t _s=read(sock,buf,sizeof(buf)-1);
        if(_s>0)
        {
          buf[_s-1]=0;
          printf("Client# %s\n",buf);
        }
        else 
        {
           printf("Client quit!");
           close(sock);
           break;
        }  
}

int main(int argc,char*argv[])
{
    if(argc!=3)
    {
        printf("Usage:%s,[local_ip] [local_port]\n",argv[0]);
        exit(1);
    }
    int listen_sock=startup(atoi(argv[2]),argv[1]);

    struct sockaddr_in peer;
    socklen_t len=sizeof(peer);

    while(1)
    {
        int sock=accept(listen_sock,(struct sockaddr*)&peer,&len);
        if(sock<0)
        {
            perror("accept");
            continue;
        }

        pthread_t tid;
        int ret=pthread_create(&tid,NULL,handler_fd,&sock);
        if(ret<0)
        {
            printf("pthread create failed!\n");
            exit(5);
        }
        pthread_detach(tid);
    }
    return 0;
}

client.c不用改变,有关进程,线程套接字的通信就介绍到此。



你可能感兴趣的:(网络,c/c++/数据结构,socket,通信,多进程,多线程)