Socket編程實踐(6) --TCP服務端注意事項

殭屍進程處理

1)經過忽略SIGCHLD信號,避免殭屍進程編程

    在server端代碼中添加服務器

    signal(SIGCHLD, SIG_IGN);socket

 

2)經過wait/waitpid方法,解決殭屍進程函數

signal(SIGCHLD,onSignalCatch);

void onSignalCatch(int signalNumber)
{
    wait(NULL);
}

3) 若是多個客戶端同時關閉, 問題描述以下面兩幅圖所示:測試


/** client端實現的測試代碼**/
int main()
{
    int sockfd[50];
    for (int i = 0; i < 50; ++i)
    {
        if ((sockfd[i] = socket(AF_INET, SOCK_STREAM, 0)) == -1)
            err_exit("socket error");

        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[i], (const struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1)
            err_exit("connect error");
    }
    sleep(20);
}

在客戶運行過程當中按下Ctrl+C,則能夠看到在server端啓動50個子進程,而且全部的客戶端所有一塊兒斷開的狀況下,產生的殭屍進程數是驚人的(此時也證實了SIGCHLD信號是不可靠的)!spa


解決方法-將server端信號捕捉函數改造以下:.net

void sigHandler(int signo)
{
    while (waitpid(-1, NULL, WNOHANG) > 0)
        ;
}

waitpid返回值解釋:code

  on  success,  returns the process ID of the child whose state has changed(返回已經結束運行server

的子進程的PID); if WNOHANG was specified and one or more child(ren) specified by pid exist, blog

but have not yet changed state, then 0 is returned(若是此時尚有好多被pid參數標識的子進程存在

且沒有結束的跡象, 返回0).  On error, -1 is returned.

 

地址查詢API

#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);	//獲取本地addr結構
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);	//獲取對方addr結構

int gethostname(char *name, size_t len);
int sethostname(const char *name, size_t len);

#include <netdb.h>
extern int h_errno;
struct hostent *gethostbyname(const char *name);

#include <sys/socket.h>       /* for AF_INET */
struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);
struct hostent *gethostent(void);
//hostent結構體
struct hostent
{
    char  *h_name;            /* official name of host */
    char **h_aliases;         /* alias list */
    int    h_addrtype;        /* host address type */
    int    h_length;          /* length of address */
    char **h_addr_list;       /* list of addresses */
}
#define h_addr h_addr_list[0] /* for backward compatibility */
/**獲取本機IP列表**/
int gethostip(char *ip)
{
    struct hostent *hp = gethostent();
    if (hp == NULL)
        return -1;

    strcpy(ip, inet_ntoa(*(struct in_addr*)hp->h_addr));
    return 0;
}

int main()
{
    char host[128] = {0};
    if (gethostname(host, sizeof(host)) == -1)
        err_exit("gethostname error");

    cout << "host-name: " << host << endl;
    struct hostent *hp = gethostbyname(host);
    if (hp == NULL)
        err_exit("gethostbyname error");

    cout << "ip list: " << endl;
    for (int i = 0; hp->h_addr_list[i] != NULL; ++i)
    {
        cout << '\t'
             << inet_ntoa(*(struct in_addr*)hp->h_addr_list[i]) << endl;
    }

    char ip[33] = {0};
    gethostip(ip);
    cout << "local-ip: " << ip << endl;
}

TCP協議的11種狀態 


1.以下圖(客戶端與服務器都在本機:雙方(server的子進程,與client)連接已經創建(ESTABLISHED),等待通訊)

 

2.最早close的一端,會進入TIME_WAIT狀態; 而被動關閉的一端能夠進入CLOSE_WAIT狀態 (下圖,server端首先關閉)

 

3.TIME_WAIT 時間是2MSL(報文的最長存活週期的2倍) 

  緣由:(ACK y+1)若是發送失敗能夠重發, 所以若是server端不設置地址重複利用的話, 服務器在短期內就沒法重啓;

    服務器端處於closed狀態,不等於客戶端也處於closed狀態。

(下圖, client先close, client出現TIME_WAIT狀態)

 

4.TCP/IP協議的第1種狀態:圖上只包含10種狀態,還有一種CLOSING狀態

產生CLOSING狀態的緣由:

Server端與Client端同時關閉(同時調用close,此時兩端同時給對端發送FIN包),將產生closing狀態,最後雙方都進入TIME_WAIT狀態(以下圖)。


SIGPIPE信號

往一個已經接收FIN的套接中寫是容許的,接收到FIN僅僅表明對方再也不發送數據;可是在收到RST段以後,若是還繼續寫,調用write就會產生SIGPIPE信號,對於這個信號的處理咱們一般忽略便可。

    signal(SIGPIPE, SIG_IGN); 

/** 測試: 在Client發送每條信息都發送兩次
當Server端關閉以後Server端會發送一個FIN分節給Client端,
第一次消息發送以後, Server端會發送一個RST分節給Client端, 
第二次消息發送(調用write)時, 會產生SIGPIPE信號;
注意: Client端測試代碼使用的是下節將要介紹的Socket庫
**/
void sigHandler(int signo)
{
    if (SIGPIPE == signo)
    {
        cout << "receive SIGPIPE = " << SIGPIPE << endl;
        exit(EXIT_FAILURE);
    }
}
int main()
{
    signal(SIGPIPE, sigHandler);
    TCPClient client(8001, "127.0.0.1");
    try
    {
        std::string msg;
        while (getline(cin, msg))
        {
            client.send(msg);
            client.send(msg);   //第二次發送
            msg.clear();
            client.receive(msg);
            client.receive(msg);
            cout << msg << endl;
            msg.clear();
        }
    }
    catch (const SocketException &e)
    {
        cerr << e.what() << endl;
    }
}

close與shutdown的區別

#include <unistd.h>
int close(int fd);

#include <sys/socket.h>
int shutdown(int sockfd, int how);

shutdown的how參數

SHUT_RD

關閉讀端

SHUT_WR

關閉寫端

SHUT_RDWR

讀寫均關閉

1.close終止了數據傳送的兩個方向;

  而shutdown能夠有選擇的終止某個方向的數據傳送或者終止數據傳送的兩個方向。

2.shutdown how=SHUT_WR(關閉寫端)能夠保證對等方接收到一個EOF字符(FIN段),而無論是否有其餘進程已經打開了套接字(shutdown並沒採用引用計數)。

  而close須要等待套接字引用計數減爲0時才發送FIN段。也就是說直到全部的進程都關閉了該套接字。

 

示例分析:

   客戶端向服務器按照順序發送:FIN E D C B A, 若是FIN是當client還沒有接收到ABCDE以前就調用close發送的, 那麼client端將永遠接收不到ABCDE了, 而經過shutdown函數, 則能夠有選擇的只關閉client的發送端而不關閉接收端, 則client端還能夠接收到ABCDE的信息;


/**測試: 實現與上面相似的代碼(使用close/shutdown)兩種方式實現 **/

完整源代碼請參照:

http://download.csdn.net/detail/hanqing280441589/8486517

注意: 最好讀者須要有select的基礎, 沒有select基礎的讀者能夠參考個人博客<Socket編程實踐(8)>相關部分

相關文章
相關標籤/搜索