幾種TCP鏈接中出現RST的狀況

應該沒有人會質疑,如今是一個網絡時代了。應該很多程序員在編程中須要考慮多機、局域網、廣域網的各類問題。因此網絡知識也是避免不了學習的。並且筆者一直以爲TCP/IP網絡知識在一個程序員知識體系中必需佔有一席之地的。 程序員

在TCP協議中RST表示復位,用來異常的關閉鏈接,在TCP的設計中它是不可或缺的。發送RST包關閉鏈接時,沒必要等緩衝區的包都發出去,直接就丟棄緩存區的包發送RST包。而接收端收到RST包後,也沒必要發送ACK包來確認。 編程

其實在網絡編程過程當中,各類RST錯誤實際上是比較難排查和找到緣由的。下面我列出幾種會出現RST的狀況。 緩存

1 端口未打開

服務器程序端口未打開而客戶端來鏈接。這種狀況是最爲常見和好理解的一種了。去telnet一個未打開的TCP的端口可能會出現這種錯誤。這個和操做系統的實現有關。在某些狀況下,操做系統也會徹底不理會這些發到未打開端口請求。 服務器

好比在下面這種狀況下,主機241向主機114發送一個SYN請求,表示想要鏈接主機114的40000端口,可是主機114上根本沒有打開40000這個端口,因而就向主機241發送了一個RST。這種狀況很常見。特別是服務器程序core dump以後重啓以前連續出現RST的狀況會常常發生。 網絡

固然在某些操做系統的主機上,未必是這樣的表現。好比向一臺WINDOWS7的主機發送一個鏈接不存在的端口的請求,這臺主機就不會迴應。 併發

2 請求超時

曾經遇到過這樣一個狀況:一個客戶端鏈接服務器,connect返回-1而且error=EINPROGRESS。 直接telnet發現網絡鏈接沒有問題。ping沒有出現丟包。用抓包工具查看,客戶端是在收到服務器發出的SYN以後就莫名其妙的發送了RST。 socket

好比像下面這樣: 工具

有8九、27兩臺主機。主機89向主機27發送了一個SYN,表示但願鏈接8888端口,主機27迴應了主機89一個SYN表示能夠鏈接。可是主機27卻很不友好,莫名其妙的發送了一個RST表示我不想鏈接你了。 學習

後來通過排查發現,在主機89上的程序在創建了socket以後,用setsockopt的SO_RCVTIMEO選項設置了recv的超時時間爲100ms。而咱們看上面的抓包結果表示,從主機89發出SYN到接收SYN的時間多達110ms。(從15:01:27.799961到15:01:27.961886, 小數點以後的單位是微秒)。所以主機89上的程序認爲接收超時,因此發送了RST拒絕進一步發送數據。 spa

3 提早關閉

關於TCP,我想咱們在教科書裏都讀到過一句話,'TCP是一種可靠的鏈接'。 而這可靠有這樣一種含義,那就是操做系統接收到的來自TCP鏈接中的每個字節,我都會讓應用程序接收到。若是應用程序不接收怎麼辦?你猜對了,RST。

看兩段程序:


//server.c

int main(int argc, char** argv)  
{  
    int listen_fd, real_fd;  
    struct sockaddr_in listen_addr, client_addr;  
    socklen_t len = sizeof(struct sockaddr_in);  
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);  
    if(listen_fd == -1)  
    {  
        perror("socket failed   ");  
        return -1;  
    }  
    bzero(&listen_addr,sizeof(listen_addr));  
    listen_addr.sin_family = AF_INET;  
    listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
    listen_addr.sin_port = htons(SERV_PORT);  
    bind(listen_fd,(struct sockaddr *)&listen_addr, len);  
    listen(listen_fd, WAIT_COUNT);  
    while(1)  
    {  
        real_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &len);  
        if(real_fd == -1)  
        {  
            perror("accpet fail  ");  
            return -1;  
        }  
        if(fork() == 0)  
        {  
            close(listen_fd);  
            char pcContent[4096];
            read(real_fd,pcContent,4096);
            close(real_fd);  
            exit(0);              
        }  
        close(real_fd);  
    }     
    return 0;  
}
這一段是server的最簡單的代碼。邏輯很簡單,監聽一個TCP端口而後當有客戶端來鏈接的時候fork一個子進程來處理。注意看的是這一段fork裏面的處理:



char pcContent[4096];
read(real_fd,pcContent,4096);
close(real_fd);
每次只是讀socket的前4096個字節,而後就關閉掉鏈接。


而後再看一下client的代碼:


//client.c
int main(int argc, char** argv)  
{  
    int send_sk;  
    struct sockaddr_in s_addr;  
    socklen_t len = sizeof(s_addr);  
    send_sk = socket(AF_INET, SOCK_STREAM, 0);  
    if(send_sk == -1)  
    {  
        perror("socket failed  ");  
        return -1;  
    }  
    bzero(&s_addr, sizeof(s_addr));  
    s_addr.sin_family = AF_INET;  

    inet_pton(AF_INET,SER_IP,&s_addr.sin_addr);  
    s_addr.sin_port = htons(SER_PORT);  
    if(connect(send_sk,(struct sockaddr*)&s_addr,len) == -1)  
    {  
        perror("connect fail  ");  
        return -1;  
    }  
    char pcContent[5000]={0};
    write(send_sk,pcContent,5000);
    sleep(1);
    close(send_sk);
}
這段代碼更簡單,就是打開一個socket而後鏈接一個服務器併發送5000個字節。剛纔咱們看服務器的代碼,每次只接收4096個字節,那麼就是說客戶端發送的剩下的4個字節服務端的應用程序沒有接收到,服務器端的socket就被關閉掉,這種狀況下會發生什麼情況呢,仍是抓包看一看。


前三行就是TCP的3次握手,從第四行開始看,客戶端的49660端口向服務器的9877端口發送了5000個字節的數據,而後服務器端發送了一個ACK進行了確認,緊接着服務器向客戶端發送了一個RST斷開了鏈接。和咱們的預期一致。

4 在一個已關閉的socket上收到數據

若是某個socket已經關閉,但依然收到數據也會產生RST。

代碼以下:

客戶端:

int main(int argc, char** argv)  
{  
    int send_sk;  
    struct sockaddr_in s_addr;  
    socklen_t len = sizeof(s_addr);  
    send_sk = socket(AF_INET, SOCK_STREAM, 0);  
    if(send_sk == -1)  
    {  
        perror("socket failed  ");  
        return -1;  
    }  
    bzero(&s_addr, sizeof(s_addr));  
    s_addr.sin_family = AF_INET;  

    inet_pton(AF_INET,SER_IP,&s_addr.sin_addr);  
    s_addr.sin_port = htons(SER_PORT);  
    if(connect(send_sk,(struct sockaddr*)&s_addr,len) == -1)  
    {  
        perror("connect fail  ");  
        return -1;  
    }  
    char pcContent[4096]={0};
    write(send_sk,pcContent,4096);
    sleep(1);
    write(send_sk,pcContent,4096);
    close(send_sk);
} 
服務端:

int main(int argc, char** argv)  
{  
    int listen_fd, real_fd;  
    struct sockaddr_in listen_addr, client_addr;  
    socklen_t len = sizeof(struct sockaddr_in);  
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);  
    if(listen_fd == -1)  
    {  
        perror("socket failed   ");  
        return -1;  
    }  
    bzero(&listen_addr,sizeof(listen_addr));  
    listen_addr.sin_family = AF_INET;  
    listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
    listen_addr.sin_port = htons(SERV_PORT);  
    bind(listen_fd,(struct sockaddr *)&listen_addr, len);  
    listen(listen_fd, WAIT_COUNT);  
    while(1)  
    {  
        real_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &len);  
        if(real_fd == -1)  
        {  
            perror("accpet fail  ");  
            return -1;  
        }  
        if(fork() == 0)  
        {  
            close(listen_fd);  
            char pcContent[4096];
            read(real_fd,pcContent,4096);
            close(real_fd);  
            exit(0);              
        }  
        close(real_fd);  
    }     
    return 0;  
} 
客戶端在服務端已經關閉掉socket以後,仍然在發送數據。這時服務端會產生RST。

總結

總結,本文講了幾種TCP鏈接中出現RST的狀況。實際上確定還有無數種的RST發生,我之後會慢慢收集把更多的例子加入這篇文章。

參考文獻:

1 從TCP協議的原理來談談RST攻擊 http://russelltao.iteye.com/blog/1405349

2 TCP客戶-服務器程序例子http://blog.csdn.net/youkuxiaobin/article/details/6917880  

相關文章
相關標籤/搜索