如何寫一個完美的socket多線程通訊程式 ?(持續更新中...)

來源:互聯網
上載者:User

/*
* 由socket多線程通訊中一些常見問題與技巧引發的一系列程式開發秘籍,(個人經驗,僅供參考)

* 請結合http://blog.csdn.net/ipromiseu/archive/2009/05/12/4169589.aspx 《Linux系統下的多線程編程全面入門 》閱讀本文
*          by . Gray Luo(guohui.great@gmail.com)
*/



1.socket多線程通訊中一些常見問題與技巧:

1>socket考慮到效率,一般採用nonblock方式。

2>socket通訊或者其它讀寫操作,使用select進行可讀寫的檢測,一方面可以防止阻塞佔用CPU,另一方面可以安全地進行資料讀寫。

3>read/write 或 recv/send 操作之前都需要使用FD_ISSET()檢測控制代碼是否可讀寫。

4>read/write 或 recv/send 都需要重新封裝以達到對資料的完整收發與出錯處理。 請參考我的另一篇文章《 通訊中如何一次性完整地接收資料》 http://blog.csdn.net/ipromiseu/archive/2010/01/05/5138760.aspx

5>對出錯的socket,如果無法恢複,則需要銷毀,而不能繼續進行讀寫。

6>一個多次迴圈的操作內,如while(),for()之中的運算式,一定要在每次迴圈後加上usleep()讓出CPU,usleep的長短需要調節以適應程式的各個模組的效率。在這種無限迴圈中,迴圈條件需要是一個變數,可不能寫成while(1),for(;;)之類的,因為如果寫成這樣,那當程式出現異常時,可能程式就無法正常退出,對程式資源造成混亂。

7>socket的listen位置,來一個新的串連,就開一個新的線程去處理,這裡的線程ID也需要新的ID。這裡容易出現bug.

8>socket通訊中不能嚴格按照收發順序來進行收發資料,即一個socket的通訊流程要簡潔,彼此之間不能有太多的依賴性。

9>收發資料需要有magic_num/checksum,對於視頻音頻流可能還需要sequence num,收到header後首先需要判斷magic_num/checksum是否正確,
以確定這個packet是否合法。如果不合法,就以max_buff的大小來收,由於是nonblock,所以以最大的包來收,沒資料會立即返回,
然後continue. 對於sequence num discontiguous packets,do some process.
如:
/*------------------------------------------------------------------------*/ if((message->magic_num != MAGIC_NUMBER ) || (message->header_len != sizeof(PHS))){ MY_ERROR("-----------------wrong message header ,drop it ! ------------------------/n"); ret = read(sock,sendbuf,256*1024);//nonblock ,return as soon as possible ret = 0; usleep(10); continue; } /*------------------------------------------------------------------------*/
10>嚴格判斷函數的傳回值,以確定下一步的操作。

11>如果程式出現問題,需要從最開始的源頭尋找問題的來源,而不要盲目的腳痛醫腳。

12> 使用signal(SIGPIPE, SIG_IGN); 忽略掉socket中斷產生的SIGPIPE訊號,以致於程式不被異常中斷。

13>在write/send的時候,也需要先FD_ISSET() -> rc = read()/recv -> if(rc == 0)則說明收到FIN/RST,socket中斷,無法恢複,做清理工作 。
if(rc < 0)且EAGAIN == errno,則socket出錯,無法恢複,做清理工作。如 <19>執行個體所示.


14>適當設定線程的優先順序,以達到最重要的線程可以順暢運行,而不至於時常被打斷。

15>不要出現無允出準則的while(1),for(;;)之類的,因為任何一個程式,都有可能被中斷,
一旦收到中斷訊號,線程就需要一個允出準則,while(!flag),當收到某個中斷訊號後,就需要執行某個函數,在這個函數中做一些清理工作,包括flag = 1;
如:
/*------------------------------------------------------------------------*/ signal(SIGINT, sigchld_handler); signal(SIGTERM, sigchld_handler); signal(SIGPIPE, SIG_IGN); // ignore SIGPIPE void sigchld_handler(void *s) { printf("___Interrupt,Clean Up done Quit.../n"); flag = 1; } /*------------------------------------------------------------------------*/


16>在使用signal(SIGPIPE, SIG_IGN);後程式還是會收到Interrupted system call訊號,所以在select處,需要catch這個訊號,然後做完一些清理工作再退出。
如下:
/*------------------------------------------------------------------------*/ for(;;) { //the following codes will break out,only just when (errno == EINTR) ,loop will re-run,so it doesn't matter. rc = select(sockfd+1, &rset, (fd_set *)NULL, (fd_set *)NULL, &Timeout); if(rc < 0) { if(errno == EINTR){ printf("catch a Interrupted system call,Insist on cleaning work .../n"); continue; } fprintf(stderr, "select() error: %s/n", strerror(errno)); return FALSE; //or goto _EXIT_CLEANWORK_; } else if(rc == 0) { printf("select() timeout .../n"); return FALSE; //or continue; } } /*-------------------------------------------------------------------------*/
17>盡量使函數的功能簡單,一個函數只幹一件事,對於功能類似的操作,盡量封裝到同一函數中。

18>在寫socket操作前,先判斷該socket是否可讀,以確認該socket是否已被關閉。

19>我自己一般讀socket方法如下:
eg:
/*-------------------------------------------------------------------------*/ int read_sock (int sockhandle, unsigned char *buf, int length) { int byte_read = -1; unsigned char *ptbuf =buf; int mlength = 0; int i = 0; fd_set rset; struct timeval timeout; int rc; int retrytime = 2; if(length > 1000){ retrytime = 10; } do { if((byte_read <= 0) && (i++ > retrytime )) return mlength; FD_ZERO(&rset); FD_SET(sockhandle,&rset); timeout.tv_sec = 1; timeout.tv_usec = 0; byte_read = 0; rc = select(sockhandle+1,&rset,NULL,NULL,&timeout); if(rc < 0){ if(errno == EINTR){ printf("catch a Interrupted system call,Insist on cleaning work .../n"); continue; } perror("select() error"); return -1; } else if(rc == 0){ //MY_DEBUG("select timeout/n"); usleep(100); continue; } rc = 0; if(FD_ISSET(sockhandle,&rset)){ byte_read = read (sockhandle, ptbuf,length-mlength); if(byte_read < 0){ if(errno == EAGAIN){ usleep(10); continue; } perror("socket recv error"); return -1; } else if(byte_read == 0){ printf("socket recv FIN/RST /n"); return -1; } else{ ptbuf = ptbuf+byte_read; mlength = mlength+byte_read; //printf("reste to read %d /n",mlength); } } } while (mlength < length); return (mlength); } /*-------------------------------------------------------------------------*/
20>函數的傳回值,如果定義為unsigned 類型的,對這個函數的傳回值判斷一定要謹慎,不能以 <0 或者 >0為比較條件,因為它是unsigned,必然大於0。
如:
/*--return value test--*/ unsigned long function_test(...){ //waiting for your performace //eg: if(x){ return 1; } else if(y){ return -1; } else{ return 0; } } /*--caller--*/ int ret ; ret = function_test(...); //if(ret < 0){ //wrong ! if(ret == -1){ ... } else if(ret == 0 ){ ... } else{ ... }
21> 盡量少用malloc,因為一旦使用了malloc,就註定著你要為它安排一個同伴free(),而有時候你收到了malloc的“好處”就忘記了給它安排完整的free(),
程式任何一個走向都將要給它安排同伴,你逃是逃不掉的。
一旦使用了malloc,也註定了你必然要判斷一下它是否是活的,誰都不願意為死人效勞,因為死人無法給自己帶來好處。
eg:

unsigned char * ptr = NULL; ptr = (unsigned char *)malloc(sizeof(PHS)); if(ptr == NULL){ return _ERROR_MALLOC_; } //your performace place ... free(ptr); ptr = NULL;//some people don't like give NULL to the pointer,but some other people like this.
22>當一個listen主進程或者主線程得到一個串連就開一個線程處理時,這個新開的線程中的while迴圈條件與前面的線程ID一樣,
都需要是這個線程專屬的,不會影響到其它並發線程,否則,所有線程都將會因為一個線程的壞掉而都死掉。

 

23>write/recv之前,對方socket中斷,write/recv會先調用SIGPIPE響應函數,由於將SIGPIPE交給了系統,則write/recv會返回-1,errno號為EPIPE(32).

  socket write/recv過程中,對方socket中斷,write/recv會先返回已經發送的位元組數,再次write時返回-1,errno號為ECONNRESET(104). 即:write/recv 一個已收到RST的socket,系統會發SIGPIPE訊號給該進程,如果將這個訊號交給系統處理或者直接忽略掉了,write/recv都返回EPIPE錯誤. 因此對於socket通訊一定要捕獲此訊號,進行適當處理 ,否則程式的異常退出將會給你帶來災難。 參考: The client's call to readline may happen before the server's RST is received by the client, or it may happen after.  If the readline happens before the RST is received, as we've shown in our example, the result is an unexpected EOF in the client.  But if the RST arrives first, the result is an ECONNRESET ("Connection reset by peer") error return from readline. What happens if the client ignores the error return from readline and writes more data to the server?  This can happen, for example, if the client needs to perform two writes to the server before reading anything back,  with the first write eliciting the RST. The rule that applies is: When a process writes to a socket that has received an RST, the SIGPIPE signal is sent to the process.  The default action of this signal is to terminate the process, so the process must catch the signal to avoid being involuntarily terminated. If the process either catches the signal and returns from the signal handler, or ignores the signal, the write operation returns EPIPE. 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.