Linux下TCP多進程/多線程套接字通信

上篇文章簡單的介紹了一下Linux下套接字通信的相關知識:
 http://blog.csdn.net/qq_29503203/article/details/60961537

但是存在一個缺陷就是隻能進行單進程通信,我們都知道實際上不可能一個服務器一次只能有一個客戶端,所以在這裏對其進行一個改進。

多進程套接字TCP通信
我們通過fork出子進程去完成客戶端發來的請求,而父進程只需用去accpet連接請求。這裏還需注意的是:既然創建出子進程,那麼就得考慮它的回收。先前學習回收子進程的方法很多,比如:通過發信號,通過函數(一般是父進程對子進程的一個等待)

今天將介紹一種新的方式,即在子進程中繼續創建子進程,然後通過init系統對其進行回收,下來看着代碼繼續詳說

server.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>

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>函數原型,所在頭文件及其返回值


(2)回收子進程
這裏是這樣一個情況,原本fork()出一個子進程,用它來執行回寫等操作,而父進程裏去等待子進程以致回收,但是考慮到性能問題,時間資源消耗問題,我們採取在這個子進程裏再fork一次,如果子進程又創建成功,那麼我們就可以使類似於它的父進程的這個說法的進程(簡單來說就是剛開始父進程創建出來的那個進程)退出,然後該子進程就成爲了孤兒進程,我們又知道孤兒進程一般是通過1號進程init系統回收的,所以這樣將節約不少時間(不讓兒子進程等待孫子進程太久而消耗太多的系統資源)。如下圖分析:



多線程套接字TCP通信

         在前邊的系統編程的學習中,我們知道線程是進程內部的一個執行流,由於同一進程的多個線程共享同一地址空間,如果可以把進程做的事交給多個線程去做就會節約不少資源,而我們又知道進程是程序的一次動態的執行過程,系統中的進程數過於多的話,會增加系統的負擔,所以這裏採用多線程實現通信對上面兩種進行優化。 
實現方法: 
(1)主線程中創建出一個新線程,新線程的執行函數是讀取信息,類似於上邊的多進程間的通信。
(2)這裏需要注意的就是線程等待和回收的問題。我們知道默認情況下,線程被創建成可結合的,如果我們通過pthread_join()的話,這裏主線程阻塞式的去等待新線程會耗費很多的時間,所以這裏我們可以將新的線程進行分離,分離之後的線程就不需要主線程去等待,而是由操作系統區回收。

代碼實現:

tserver.c
#include<stdio.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#include<stdlib.h>

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不用改變,有關進程,線程套接字的通信就介紹到此。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章