程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 如何喚醒socket被阻塞的函數

如何喚醒socket被阻塞的函數

編輯:關於C語言

    最近項目遇到一個問題,程序退出的時候資源沒有正常釋放。經過調試發現,原來是網絡線程一直阻塞,導致一些必要的資源沒有被釋放,寫了幾個簡單的測試程序調試了一下才明白,原來在Linux下直接close socket的文件描述符,並不會使程序中調用的一些阻塞式的socket函數比如 read、recvfrom 等)退出阻塞,從而導致無法正常釋放資源。簡化示例如下。

    下面是一個簡化的UDP服務程序,首先創建socket對象,然後開啟服務線程,將客戶端發送過來的數據包回發給客戶端。當用戶在shell中敲入兩次回車後,程序退出。我們來觀察一下程序退出後,socket服務線程在怎樣的情況下可以正常退出。

  1. #include <stdio.h>  
  2. #include <sys/types.h>  
  3. #include <sys/socket.h>  
  4. #include <linux/in.h>  
  5. #include <string.h>  
  6. #include <pthread.h>  
  7.   
  8. #define SERVER_PORT 8888  
  9. #define BUFFER_LEN  256  
  10.   
  11. int g_Exit = 0;  
  12.   
  13. void *service( void* arg )  
  14. {  
  15.     char buff[BUFFER_LEN];  
  16.     struct sockaddr clientAddr;  
  17.     int socklen = sizeof(clientAddr);  
  18.     int recvbytes;  
  19.     int socketfd = *((int *)arg);  
  20.   
  21.     printf("OK, Enter Service!\n");  
  22.   
  23.     while(!g_Exit)  
  24.     {  
  25.         recvbytes = recvfrom(socketfd,buff,BUFFER_LEN,0,&clientAddr,&socklen);  
  26.   
  27.         sendto(socketfd,buff,recvbytes,0,&clientAddr,socklen);  
  28.     }  
  29.   
  30.     printf("OK, Service Thread Exit!\n");  
  31.   
  32.     pthread_exit(NULL);;  
  33. }  
  34.   
  35. int main( int argc,char * argv[] )  
  36. {  
  37.     int fd;  
  38.     void *status;  
  39.     struct sockaddr_in serverAddr;  
  40.     pthread_t thr;  
  41.     pthread_attr_t attr;  
  42.   
  43.     fd = socket(AF_INET,SOCK_DGRAM,0);  
  44.   
  45.     memset(&serverAddr,0,sizeof(serverAddr));  
  46.   
  47.     serverAddr.sin_family = AF_INET;  
  48.     serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);  
  49.     serverAddr.sin_port = htons(SERVER_PORT);  
  50.   
  51.     bind(fd,(struct sockaddr *)&serverAddr,sizeof(serverAddr));  
  52.  
  53.     // create service thread  
  54.     pthread_attr_init(&attr);  
  55.     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);  
  56.     if( pthread_create(&thr,&attr,service,(void *)&fd ) )  
  57.     {  
  58.         printf("pthread_create fail!\n");  
  59.         return -1;  
  60.     }  
  61.     // Free attribute  
  62.     pthread_attr_destroy(&attr);  
  63.   
  64.     // wait user control exit 
  65.     getchar();  
  66.     getchar();  
  67.   
  68.     g_Exit = 1;      
  69.   
  70.     printf("OK, Waiting For Thread Exit...!\n");  
  71.   
  72.    close(fd);
  73.   
  74.     // wait for thread exit
  75.   pthread_join(thr, &status);
  76.  
  77. printf("OK, Exit Main Process !\n");
  78.  
  79.     return 0;  
  80. }  

    上述程序,當用戶敲兩次回車後,顯示結果如下:

    

    可以看到,沒有打出主進程和服務線程的退出信息,無論是主進程還是服務線程都沒有正常退出,由此可見,直接close socket句柄,並不能使 recvfrom 函數退出阻塞。

    那麼,如果把 pthread_join 換成 pthread_cancel 呢?結果是一樣的,雖然主進程退出了,但依然無法讓 service 線程正常退出。那麼,該如何才能正常退出 recvfrom 的阻塞呢?

    網上搜了一下,可以考慮使用 shutdown 函數。

  1. //shutdown函數原型為:  
  2. #include <sys/socket.h>  
  3. int shutdown(int s, int how);  
  4.   
  5. //shutdown() 可以對套接字的關閉進行更細致的控制,它允許對套接字進行單向關閉或全部禁止。  
  6. //參數 s 為待關閉的套接字描述符。  
  7. //參數 how 指定了關閉方式,具體取值如下:  
  8. //SHUT_RD : 將連接上的讀通道關閉,此後進程將不能再接收到任何數據,接收緩沖區中還未被讀取的數據也將被丟棄,但仍然可以在該套接字上發送數據。  
  9. //SHUT_WR : 將連接上的寫通道關閉,此後進程將不能再發送任何數據,發送緩沖區中還未被發送的數據也將被丟棄,但仍然可以在該套接字上接收數據。  
  10. //SHUT_RDWR : 讀、寫通道都將被關閉。  
  11. //執行成功返回 0,出錯則返回 -1,錯誤代碼存入 errno 中。  

    可以測試一下,我們在上述代碼的pthread_join前面加上一句:shutdown(fd,SHUT_RDWR); 然後再編譯調試,結果如下: 

    

    可以看到,Service服務線程已經正常退出了。進一步測試,如果只是shutdown寫通道或者只shutdown讀通道呢?

    經過測試可以發現,如果只關閉寫通道 shutdown(fd,SHUT_WR); 服務線程依然無法正常退出,而如果只關閉讀通道 shutdown(fd,SHUT_RD),則服務線程正常退出了。分析如下:因為recvfrom在fd的讀通道等待列表中,因此必須關閉讀通道時才能將recvfrom阻塞喚醒。

    那麼,為啥shutdown就可以使得recvfrom退出阻塞,而close卻不能呢?

    我的理解如下:shutdown破壞了socket連接的讀寫通道,導致讀寫阻塞的socket函數被喚醒,而close函數只是做了關閉連接釋放socket資源的操作,卻並沒有進行讀寫通道的清理工作,從而無法成功喚醒讀寫函數的阻塞。期待高手給出更深層次的解釋)

    進一步,那麼,解決這一問題,還有其他的什麼辦法沒有?

    下面我簡單地羅列一下網上搜到的可行的一些方法,以後有時間再深入研究:

    1.  設置socket發送/接收超時

    2.  使用非阻塞方式,異步socket模型

    3.  其他方式,歡迎大家補充。

    文章就寫到這裡了,歡迎大家來信進一步交流[email protected]

 

本文出自 “對影成三人” 博客,請務必保留此出處http://ticktick.blog.51cto.com/823160/845536

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved