zoukankan      html  css  js  c++  java
  • 如何唤醒socket被阻塞的函数

      最近项目遇到一个问题,程序退出的时候资源没有正常释放。经过调试发现,原来是网络线程一直阻塞,导致一些必要的资源没有被释放,写了几个简单的测试程序调试了一下才明白,原来在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! ");  
    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! ");  
    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! ");  
    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...! ");  
    71.   
    72.    close(fd);
    73.   
    74.     // wait for thread exit
    75.   pthread_join(thr, &status);
    76.  
    77. printf("OK, Exit Main Process ! ");
    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.  其他方式,欢迎大家补充。

  • 相关阅读:
    vue中根据手机类型判断是安卓、微信或IOS跳转相应下载页面
    冒泡排序、数组去重
    vue2中component父子组件传递数据props的使用
    filter 在CSS用的效果
    纪念一下——做事要踏实
    要先学会做人,再做事
    2014再见,2013你好!
    --initialize specified but the data directory has files in it. Aborting. 解决
    Unity 发生 System.TypeInitializationException: “”的类型初始值设定项引发异常。的错误
    C#和delphi交互传结构体的delphi接收不到问题
  • 原文地址:https://www.cnblogs.com/xiangcunjiaoshi/p/12598304.html
Copyright © 2011-2022 走看看