Unix/Linux網路編程中關於網路異常、伺服器Down掉、異常訊號時的處理方案【學習總結,請勿吐槽】

來源:互聯網
上載者:User

  先描述一下整體的流程及思路:

  用戶端從標準輸入讀取一行文本,發送給伺服器,伺服器收到文本後,將文本直接返回給用戶端,即回顯。整體採用TCP協議完成。

 

  用戶端大致代碼:

socket,connect函數略去

char sendline[1024],recvline[1024];

while( fgets(sendline, 1024, stdin) != NULL){  //從標準輸入讀取

  writen(sockfd,sendline,strlen(sendline));  //發送給伺服器,Sockfd就是與伺服器聯通的Socket

  if(readline(sockfd, recvline, 1024) == 0)  //從伺服器接收

    err_quit("Server terminated!");

  fputs(recvline, stdout);  //顯示在螢幕上

 

  伺服器端大致代碼:

socket,bind,listen函數略去

while(1){

  socklen_t clilen = sizeof(cliaddr);

  connfd = Accept(listenfd, (const SocketAddr_in *)&cliaddr, &clilen);

  if((childpid = fork()) == 0){  //伺服器端採用多進程來處理每一個客戶串連

    Close(listenfd);  //對於子進程來說,Listenfd無用,可以直接關閉

    str_server(connfd); //處理客戶請求,詳見下面

    exit(0);

  }

  close(connfd);

}

void str_server(int sockfd){

  ssize_t n;

  char buf[1024];

again:

  while( (n = read(sockfd, buf, 1024)) > 0)

    writen(sockfd, buf, n);

  if( n < 0 && errno == EINTR)  //之後會說明為什麼在這裡要處理EINTR

    goto again;

  else if( n < 0)

    err_sys("read error");

}

 

一、 伺服器端用於與用戶端通訊的子進程終止

  可以啟動客戶/伺服器,然後使用Kill命令殺死伺服器子進程。這時根據TCP串連中止的四步(不明白的先百度一下,稍候的部落格中我會對TCP四步中斷連線進行詳細講述),子進程所有開啟的描述符都被關閉,這就導致向客戶發送一個FIN,同時客戶TCP則響應一個ACK。對於伺服器端,SIGCHLD訊號會發送到伺服器父進程中,由於代碼中並未寫處理SIGCHLD的功能,所以預設被忽略掉。

  這裡要注意,用戶端並未發生任何特殊之事,它並沒有立刻得到伺服器子進程崩潰的訊息,而是阻塞在fgets(sendline, 1024, stdin)系統調用上,這時我們在用戶端繼續鍵入文本並發送給伺服器,由於處理與客戶通訊的子進程已經關閉,所以會響應一個RST。但是客戶卻看不到這個RST,因為它在調用writen後立即調用readline,由於之前已經收到FIN,所以readline立即返回0,顯示server terminate.也就是說,用戶端並未獲得RST所告知的錯誤資訊。另外,大家注意到,在伺服器子進程終止時,用戶端並未立刻得到通知,因為它阻塞在Fgets調用上,雖然用戶端已經收到FIN。

  如果伺服器子進程終止,用戶端不通過發送資料也能知道的話,就必須不能讓自己阻塞在Fgets上,這裡需要用到I/O多工技術,即select和poll函數。

二、通訊過程中網路斷開,或者說伺服器主機崩潰

  可以在通訊過程中拔下網線。。。。和(一)中所講述的一樣,當用戶端發送給伺服器資料時,由於網路已經斷開,因此writen(其實與send相似)發給伺服器資料後,客戶TCP會持續重傳資料分節,以試圖從伺服器獲得一個ACK,但是,源自Berkeley的實現重傳大約等待9分鐘才放棄重傳,9分鐘後才給客戶進程返回一個錯誤,多數是ETIMEDOUT,即逾時錯誤。而在實際應用中,9分鐘顯然太長了,因此需要對readline調用設定一個逾時。

  但問題仍然存在,因為我們仍然向伺服器發送了資料後才檢測出伺服器主機崩潰或者網路斷開了,如果在網路斷開時客戶就想知道,需要使用SO_KEEPALIVE通訊端選項,即一直保持聯通,當斷開網路時,他會導致用戶端直接返回錯誤訊號,從而捕獲它並進行處理。

三、通訊過程中伺服器主機崩潰,但之後重啟

  這與(二)所不同的地方是:當伺服器重啟後,網路仍然是聯通的,只是與客戶通訊的進程消失了。這樣以來,當重啟後的伺服器收到客戶訊息後,由於之前的通訊進程已經終止,所以會響應一個RST給用戶端,並導致用戶端readline返回ECONNREST錯誤。假如一個應用對於檢測伺服器是否崩潰很重要,即和(二)所說一樣,也就需要SO_KEEPALIVE通訊端選項,另外一種技術是採用客戶/伺服器心搏函數,這個我不太瞭解。

四、通訊過程中伺服器關機

  系統關機時,init進程通常先給所有進程發送SIGTERM訊號,各進程需要捕獲該訊號並做好善後工作。等待一段時間後(5~20秒,這也就是為什麼當我們關機時顯示正在關機中的原因),再發送SIGKILL訊號給所有進程。這時伺服器處理序終止。同(一)中所述一樣,用戶端也需要再發資料才能得到該資訊。同樣,用戶端需要IO多工才能獲得伺服器的這種狀態。

五、 阻塞在慢系統調用上時捕獲訊號,也就是處理被中斷的系統調用

  所謂慢系統調用,我們可以用Accept函數來描述它,當伺服器端Listen後,就會阻塞在Accept函數上等待到來的客戶,當三向交握後,Accept返回。此處需要注意的時,有可能一直沒有客戶串連,Accept有可能永遠阻塞下去,永遠無法返回,這類系統調用就可以認為是慢系統調用。包括fgets, readline等。

  現有如此情境:有5個用戶端與伺服器保持串連,也就是伺服器端有父進程+5個子進程,現在5個子進程同時終止,顯然伺服器端的5個子進程會終止返回,發送SIGCHLD訊號給父進程,此時父進程阻塞在Accept慢系統調用上,這時核心會導致Accept返回一個EINTR錯誤(被中斷的系統能夠調用),如果沒有if( n < 0 && errno == EINTR),則伺服器會直接終止,當然,此處是將EINTR寫在了伺服器子進程中,更常用的方法是將if( errno == EINTR)的處理寫在Accept函數之後。也就是說,不能講EINTR視為一個硬性錯誤,他只是阻塞在慢系統調用上的一個中斷,我們需要捕獲它並Continue我們的程式。

 

綜述:

  在本篇文章中,我沒有寫具體的代碼,只是講述了大概的工作及通訊思路,只有這些思路原理都明白了,看他人的代碼或者自己寫才能有方案。

  對於文章中所講述的IO多工、TCP中斷連線的詳細過程,有時間的話在之後的部落格中會進行介紹。。。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.