歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux下的socket編程實踐(三)端口復用和P2P多進程服務器

Linux下的socket編程實踐(三)端口復用和P2P多進程服務器

日期:2017/3/1 12:24:12   编辑:關於Linux
Socket端口復用 先說為什麼要使用socket端口復用?如果你遇到過這樣的問題:server程序重啟之後,無法連接,需要過一段時間才能連接上? 1.一個監聽(listen)server已經啟動 2.當有client有連接請求的時候,server產生一個子進程去處理該client的事物. 3.server主進程終止了,但是子進程還在占用該連接處理client的事情.雖然子進程終止了,但是由於子進程沒有終止,該socket的引用計數不會為0,所以該socket不會被關閉. 4.server程序重啟。 這個時候由於端口已經被占用,所以無法重新再bind,這也使得TIME_WAIT狀態設計的原因之一。 int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen); int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); SO_REUSEADDR允許同一個端口上綁定多個IP,只要這些IP不同。服務端盡可能使用SO_REUSEADDR,在綁定之前盡可能調用setsockopt來設置SO_REUSEADDR套接字選項。該選項可以使得server不必等待TIME_WAIT狀態消失。
int setsockopt(  
    SOCKET s,  
    int level,  
    int optname,  
    const char* optval,  
    int optlen  
);  

s(套接字): 指向一個打開的套接口描述字 level:(級別): 指定選項代碼的類型。 SOL_SOCKET: 基本套接口 IPPROTO_IP: IPv4套接口 IPPROTO_IPV6: IPv6套接口 IPPROTO_TCP: TCP套接口 optname(選項名): 選項名稱 optval(選項值): 是一個指向變量的指針 類型:整形,套接口結構, 其他結構類型:linger{}, timeval{ } optlen(選項長度) :optval 的大小 在bind之前添加源碼,支持端口復用:
       
int on = 1;    
if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,    
               &on,sizeof(on)) == -1)    
    err_exit("setsockopt SO_REUSEADDR error");    

另外,我想到一個問題:因為SO_REUSEADDR是為了應對重啟server使用的,那麼未使用選項的服務端在accept之後產生一個新的socket連接,那麼這個連接真的會分配一個新的端口嗎?當我實踐之後卻發現這些新的socket連接和bind的端口是一致的!!難道一個端口可以綁定多個套接字嗎,當然不是,下面是分析: 首先,一個端口肯定只能綁定一個socket。我認為,服務器端的端口在bind的時候已經綁定到了監聽套接字socetfd所描述的對象上,accept函數新創建的socket對象其實並沒有進行端口的占有,而是復制了socetfd的本地IP和端口號,並且記錄了連接過來的客戶端的IP和端口號。 那麼,當客戶端發送數據過來的時候,究竟是與哪一個socket對象通信呢? 客戶端發送過來的數據可以分為2種,一種是連接請求,一種是已經建立好連接後的數據傳輸。 由於TCP/IP協議棧是維護著一個接收和發送緩沖區的。在接收到來自客戶端的數據包後,服務器端的TCP/IP協議棧應該會做如下處理:如果收到的是請求連接的數據包,則傳給監聽著連接請求端口的socetfd套接字,進行accept處理;如果是已經建立過連接後的客戶端數據包,則將數據放入接收緩沖區。這樣,當服務器端需要讀取指定客戶端的數據時,則可以利用socketfd_new 套接字通過recv或者read函數到緩沖區裡面去取指定的數據(因為socketfd_new代表的socket對象記錄了客戶端IP和端口,因此可以鑒別)。 在解決這個問題的時候,參考了博客 http://ticktick.blog.51cto.com/823160/779866 處理多客戶連接:
       
void echo(int clientfd);    
int main()    
{    
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);    
    if (listenfd == -1)    
        ERR_EXIT("socket error");    
    int on = 1;    
    if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,    
                   &on,sizeof(on)) == -1)  //應對重啟server,端口復用   
        ERR_EXIT("setsockopt SO_REUSEADDR error");    
    
    struct sockaddr_in addr;    
    addr.sin_family = AF_INET;    
    addr.sin_port = htons(8001);    
    addr.sin_addr.s_addr = htonl(INADDR_ANY);    
    if (bind(listenfd, (const struct sockaddr *)&addr, sizeof(addr)) == -1)    
        ERR_EXIT("bind error");    
    if (listen(listenfd, SOMAXCONN) == -1)    
        ERR_EXIT("listen error");    
    
    struct sockaddr_in clientAddr;    
     
    socklen_t addrLen = sizeof(clientAddr);    
    while (true)    
    {    
        int clientfd = accept(listenfd, (struct sockaddr *)&clientAddr, &addrLen);    
        if (clientfd == -1)    
            ERR_EXIT("accept error");    
        //打印客戶IP地址與端口號    
        cout << "Client information: " << inet_ntoa(clientAddr.sin_addr)    
             << ", " << ntohs(clientAddr.sin_port) << endl;    
    
        pid_t pid = fork();    
        if (pid == -1)    
            ERR_EXIT("fork error");    
        else if (pid > 0)    
            close(clientfd);    
        //子進程處理鏈接    
        else if (pid == 0)    
        {    
            close(listenfd);    
            echo(clientfd);    
            //子進程一定要exit, 否則的話, 該子進程也會回到accept處    
            exit(EXIT_SUCCESS);    
        }    
    }    
    close(listenfd);    
}    
void echo(int clientfd)    
{    
    char buf[512] = {0};    
    int readBytes;    
    while ((readBytes = read(clientfd, buf, sizeof(buf))) > 0)    
    {    
        cout << buf;    
        if (write(clientfd, buf, readBytes) == -1)    
            ERR_EXIT("write socket error");    
        memset(buf, 0, sizeof(buf));    
    }    
    if (readBytes == 0)    
    {    
        cerr << "client connect closed..." << endl;    
        close(clientfd);    
    }    
    else if (readBytes == -1)    
        ERR_EXIT("read socket error");    
}    

簡單的P2P聊天程序的實現 server端與client都有兩個進程: (1)父進程負責從socket中讀取數據將其寫至終端, 由於父進程使用的是read系統調用的阻塞版本, 因此如果socket中沒有數據的話, 父進程會一直阻塞; 如果read返回0, 表示對端連接關閉, 則父進程會發送SIGUSR1信號給子進程, 通知其退出; (2)子進程負責從鍵盤讀取數據將其寫入socket, 如果鍵盤沒有數據的話, 則fgets調用會一直阻塞;
//server端代碼  
void sigHandler(int signo)  
{  
    cout << "recv a signal = " << signo << endl;  
    exit(EXIT_SUCCESS);  
}  
int main()    
{    
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);    
    if (listenfd == -1)    
        ERR_EXIT("socket error");    
    int on = 1;    
    if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,    
                   &on,sizeof(on)) == -1)    
        ERR_EXIT("setsockopt SO_REUSEADDR error");    
    
    struct sockaddr_in addr;    
    addr.sin_family = AF_INET;    
    addr.sin_port = htons(8001);    
    addr.sin_addr.s_addr = htonl(INADDR_ANY);    
    if (bind(listenfd, (const struct sockaddr *)&addr, sizeof(addr)) == -1)    
        ERR_EXIT("bind error");    
    if (listen(listenfd, SOMAXCONN) == -1)    
        ERR_EXIT("listen error");    
    
    struct sockaddr_in clientAddr;    
    socklen_t addrLen = sizeof(clientAddr);    
    int clientfd = accept(listenfd, (struct sockaddr *)&clientAddr, &addrLen);    
    if (clientfd == -1)    
        ERR_EXIT("accept error");    
    close(listenfd);    
    //打印客戶IP地址與端口號    
    cout << "Client information: " << inet_ntoa(clientAddr.sin_addr)    
         << ", " << ntohs(clientAddr.sin_port) << endl;    
    
    char buf[512] = {0};    
    pid_t pid = fork();    
    if (pid == -1)    
        ERR_EXIT("fork error");    
    //父進程: socket -> terminal    
    else if (pid > 0)    
    {    
        int readBytes;    
        while ((readBytes = read(clientfd, buf, sizeof(buf))) > 0)    
        {    
            cout << buf;    
            memset(buf, 0, sizeof(buf));    
        }    
        if (readBytes == 0)    
            cout << "client connect closed...\nserver exiting..." << endl;    
        else if (readBytes == -1)    
            ERR_EXIT("read socket error");    
        //通知子進程退出    
        kill(pid, SIGUSR1);    
    }    
    //子進程: keyboard -> socket    
    else if (pid == 0)    
    {    
        signal(SIGUSR1, sigHandler);    
        while (fgets(buf, sizeof(buf), stdin) != NULL)    
        {    
            if (write(clientfd, buf, strlen(buf)) == -1)    
                err_exit("write socket error");    
            memset(buf, 0, sizeof(buf));    
        }    
    }    
    close(clientfd);    
    exit(EXIT_SUCCESS);    
}    

       
//client端代碼與說明    
int main()    
{    
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);    
    if (sockfd == -1)    
        ERR_EXIT("socket error");    
    
    //填寫服務器端口號與IP地址    
    struct sockaddr_in serverAddr;    
    serverAddr.sin_family = AF_INET;    
    serverAddr.sin_port = htons(8001);    
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");    
    if (connect(sockfd, (const struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1)    
        ERR_EXIT("connect error");    
    
    char buf[512] = {0};    
    pid_t pid = fork();    
    if (pid == -1)    
        ERR_EXIT("fork error");    
    //父進程: socket -> terminal    
    else if (pid > 0)    
    {    
        int readBytes;    
        while ((readBytes = read(sockfd, buf, sizeof(buf))) > 0)    
        {    
            cout << buf;    
            memset(buf, 0, sizeof(buf));    
        }    
        if (readBytes == 0)    
            cout << "server connect closed...\nclient exiting..." << endl;    
        else if (readBytes == -1)    
            ERR_EXIT("read socket error");    
        kill(pid, SIGUSR1);    
    }    
    //子進程: keyboard -> socket    
    else if (pid == 0)    
    {    
        signal(SIGUSR1, sigHandler);    
        while (fgets(buf, sizeof(buf), stdin) != NULL)    
        {    
            if (write(sockfd, buf, strlen(buf)) == -1)    
                ERR_EXIT("write socket error");    
            memset(buf, 0, sizeof(buf));    
        }    
    }    
    close(sockfd);    
    exit(EXIT_SUCCESS);    
}    


Copyright © Linux教程網 All Rights Reserved