歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux下的socket編程實踐(四)TCP服務端優化和常見函數

Linux下的socket編程實踐(四)TCP服務端優化和常見函數

日期:2017/3/1 12:23:58   编辑:關於Linux

並發下的僵屍進程處理

只有一個進程連接的時候,我們可以使用以下兩種方法處理僵屍進程:

1)通過忽略SIGCHLD信號,避免僵屍進程

在server端代碼中添加

signal(SIGCHLD, SIG_IGN);

2)通過wait/waitpid方法,解決僵屍進程

signal(SIGCHLD,onSignalCatch);  
  
void onSignalCatch(int signalNumber)  
{  
    wait(NULL);  
}  
那麼如果是多進程狀態下多個客戶端同時關閉呢?

\\

我們可以用下面的客戶端代碼測試:

/** 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);  
}  
此時由於信號的同時到達,並且SIGCHLD又是不可靠信號,不支持排隊,會留下相當部分的僵屍進程

\
解決方法:

使用循環的 waitpid函數就可以將所有的子進程留下的僵屍進程處理掉

void sigHandler(int signo)  
{  
    while (waitpid(-1, NULL, WNOHANG) > 0)  
        ;  
} 
//pid=-1 等待任何子進程,相當於 wait()。
//WNOHANG 若pid指定的子進程沒有結束,則waitpid()函數返回0,不予以等待。若結束,則返回該子進程的ID。
地址查詢的API

#include   
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   
extern int h_errno;  
struct hostent *gethostbyname(const char *name);  
  
#include        /* 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 */ 

這兩個函數調用的時機很重要,否則不能得到正確的地址和端口:

TCP

對於服務器來說,在bind以後就可以調用getsockname來獲取本地地址和端口,雖然這沒有什麼太多的意義。getpeername只有在鏈接建立以後才調用,否則不能正確獲得對方地址和端口,所以他的參數描述字一般是鏈接描述字而非監聽套接口描述字。

對於客戶端來說,在調用socket時候內核還不會分配IP和端口,此時調用getsockname不會獲得正確的端口和地址(當然鏈接沒建立更不可能調用getpeername),當然如果調用了bind 以後可以使用getsockname。想要正確的到對方地址(一般客戶端不需要這個功能),則必須在鏈接建立以後,同樣鏈接建立以後,此時客戶端地址和端口就已經被指定,此時是調用getsockname的時機。

UDP

UDP分為鏈接和沒有鏈接2種(這個到UDP與connect可以找到相關內容)

沒有鏈接的UDP不能調用getpeername,但是可以調用getsockname,和TCP一樣,他的地址和端口不是在調用socket就指定了,而是在第一次調用sendto函數以後

已經鏈接的UDP,在調用connect以後,這2個函數都是可以用的(同樣,getpeername也沒太大意義。如果你不知道對方的地址和端口,不可能會調用connect)。

獲取本機所有IP:

/**獲取本機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.客戶端和服務器連接建立的時候,雙方處於ESTABLISHED(建立)狀態

\
2.關於TIME_WAIT狀態 詳見 http://www.mamicode.com/info-detail-190400.html

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

產生CLOSING狀態的原因:

Server端與Client端同時關閉(同時調用close,此時兩端同時給對端發送FIN包),將產生closing狀態,最後雙方都進入TIME_WAIT狀態。(因為主動關閉的一方會進入TIME_WAIT狀態,雙方同時主動關閉,則都進入)

SIGPIPE信號

往一個已經接收FIN的套接中寫是允許的,接收到FIN僅僅代表對方不再發送數據;但是在收到RST段之後,如果還繼續寫,調用write就會產生SIGPIPE信號,對於這個信號的處理我們通常忽略即可。

signal(SIGPIPE, SIG_IGN);

SIGPIPE,雖然已經接受到FIN,但是我還可以發送數據給對方;如果對方已經不存在了,那麼TCP會進行重置,TCP協議棧發送RST段,收到RST後,再進行write會產生SIGPIPE信號。

其實很好理解:TCP可以看作是一個全雙工的管道,讀端信號不存在了,如果再對管道進行寫的話會導致SIGIPPE信號的產生,處理的時候是忽略這個信號就可以了,其實就是按照管道的規則。

我們測試的時候在Client發送每條信息都發送兩次,當Server端關閉之後Server端會發送一個FIN分節給Client端, 第一次消息發送之後, Server端會發送一個RST分節給Client端;第二次消息發送(調用write)時, 會產生SIGPIPE信號。

close和shutdown函數的區別

#include   
int close(int fd);  
  
#include   
int shutdown(int sockfd, int how);  

shutdown的how參數

SHUT_RD

關閉讀端

SHUT_WR

關閉寫端

SHUT_RDWR

讀寫均關閉

close終止了數據傳送的兩個方向,shutdown可以有選擇的終止某個方向的數據傳送或者終止數據傳送的兩個方向

shutdowm how=1就可以保證對等方接收到一個EOF字符,而不管其他進程是否已經打開可套接字。

而close不能保證,直到套接字引用計數減為0的時候才發送。也就是說知道所有進程都關閉了套接字。

調用close函數,可能導致全雙工的管道還沒有回射給客戶端時,產生丟失數據現象;例如

FIN D C B A

A B C D ----》丟失,已經關閉close

Close准確的含義是 套接字引用計數減為0 的時候,才發送FIN

int conn;
conn=accept(sock,NULL,NULL);
pid_t pid=fork();
if(pid==-1)
   ERR_EXIT("fork"); 
if(pid==0)
{
	close(sock);
	//通信
	close(conn); //這時才會向對方發送FIN段(因為這個時候conn引用計數減為0) 
}
else if(pid>0)
   close (conn); //不會向客戶端發送FIN段,僅僅只是將套接字的引用計數減1 

Copyright © Linux教程網 All Rights Reserved