• socket中的函数遇见EINTR的处理【转】


    转自:http://blog.chinaunix.net/uid-21501855-id-4490453.html

    这几天,写服务器代码过程当中,遇见EINRT信号的问题,我是借鉴 《unp 》,采用continue或者goto again循环解决的。但是感觉这个还是很有必要记录一下。网络上查找到的信息很多。下面是我查找到的和EINTR有关的介绍:
    1  http://blog.csdn.net/yanook/article/details/7226019  慢系统调用函数如何处理中断信号EINTR
    2  http://blog.csdn.net/benkaoya/article/details/17262053 信号中断 与 慢系统调用
    3  http://1.guotie.sinaapp.com/?p=235    socket,accept,connect出现EINTR错误的解决方法
    个人认为2说的比较明确,建议大家多看看。
    我的记录基本上是对2的抄袭:

    慢系统调用:可能永远阻塞的系统调用这很关键,不适用于非诸塞的情况。永远阻塞的系统调用是指调用永远无法返回,多数网络支持函数都属于这一类。如:若没有客户连接到服务器上,那么服务器的accept调用就会一直阻塞。
    (以下为抄袭2原文)
    EINTR说明:如果进程在一个慢系统调用(slow system call)中阻塞时,当捕获到某个信号且相应信号处理函数返回时,这个系统调用被中断,调用返回错误,设置errno为EINTR(相应的错误描述为“Interrupted system call”)。

    怎么看哪些系统条用会产生EINTR错误呢?man 7 signal,在ubuntu 10.04上可以查看,哪些系统调用会产生 EINTR错误。

    如何处理被中断的系统调用

    既然系统调用会被中断,那么别忘了要处理被中断的系统调用。有三种处理方式:

    ◆ 人为重启被中断的系统调用

    ◆ 安装信号时设置 SA_RESTART属性(该方法对有的系统调用无效)

    ◆  忽略信号(让系统不产生信号中断)

    人为重启被中断的系统调用

    人为当碰到EINTR错误的时候,有一些可以重启的系统调用要进行重启,而对于有一些系统调用是不能够重启的。例如:accept、read、write、select、和open之类的函数来说,是可以进行重启的。不过对于套接字编程中的connect函数我们是不能重启的,若connect函数返回一个EINTR错误的时候,我们不能再次调用它,否则将立即返回一个错误。针对connect不能重启的处理方法是,必须调用select来等待连接完成。

    这里的“重启”怎么理解?

    一些IO系统调用执行时,如 read 等待输入期间,如果收到一个信号,系统将中断read, 转而执行信号处理函数. 当信号处理返回后, 系统遇到了一个问题: 是重新开始这个系统调用, 还是让系统调用失败?早期UNIX系统的做法是, 中断系统调用,并让系统调用失败, 比如read返回 -1, 同时设置 errno 为EINTR中断了的系统调用是没有完成的调用,它的失败是临时性的,如果再次调用则可能成功,这并不是真正的失败,所以要对这种情况进行处理, 典型的方式为:

    另外,原文建议上去github上看看别人怎么处理EINTR错误的,下面2个处理方面均是截图过来的:

    connect处理方式,抄袭3原文,没有测试过,处理方法是对的。
    connect的问题,当connect遇到EINTR错误时,不能向上面那样重新进入循环处理,原因是,connect的请求已经发送向对方,正在等待对方回应,这是如果重新调用connect,而对方已经接受了上次的connect请求,这一次的connect就会被拒绝,因此,需要使用select或poll调用来检查socket的状态,如果socket的状态就绪,则connect已经成功,否则,视错误原因,做对应的处理。

    #include poll.h
    
    int check_conn_is_ok(socket_t sock) {
    	struct pollfd fd;
    	int ret = 0;
    	socklen_t len = 0;
    
    	fd.fd = sock;
    	fd.events = POLLOUT;
    
    	while ( poll (&fd, 1, -1) == -1 ) {
    		if( errno != EINTR ){
    			perror("poll");
    			return -1;
    		}
    	}
    
    	len = sizeof(ret);
    	if ( getsockopt (sock, SOL_SOCKET, SO_ERROR,
                         &ret,
                         &len) == -1 ) {
        	        perror("getsockopt");
    		return -1;
    	}
    
    	if(ret != 0) {
    		fprintf (stderr, "socket %d connect failed: %s
    ",
                     sock, strerror (ret));
    		return -1;
    	}
    
    	return 0;
    }

    在调用connect时,这样使用:

    #include erron.h
    
    ....
    if(connnect()) {
        if(errno == EINTR) {
            if(check_conn_is_ok() < 0) {
                  perror();
                  return -1;
            }
            else {
                 printf("connect is success!
    ");
            }
        }
        else {
             perror("connect");
             return -1;
        }
    }

    我一般使用continue或者goto来处理。

    安装信号时设置 SA_RESTART属性

    我们还可以从信号的角度来解决这个问题,  安装信号的时候, 设置 SA_RESTART属性,那么当信号处理函数返回后, 不会让系统调用返回失败,而是让被该信号中断的系统调用将自动恢复。

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. struct sigaction action;  
    2.    
    3. action.sa_handler = handler_func;  
    4. sigemptyset(&action.sa_mask);  
    5. action.sa_flags = 0;  
    6. /* 设置SA_RESTART属性 */  
    7. action.sa_flags |= SA_RESTART;  
    8.    
    9. sigaction(SIGALRM, &action, NULL);  

    但注意,并不是所有的系统调用都可以自动恢复。如msgsnd喝msgrcv就是典型的例子,msgsnd/msgrcv以block方式发送/接收消息时,会因为进程收到了信号而中断。此时msgsnd/msgrcv将返回-1,errno被设置为EINTR。且即使在插入信号时设置了SA_RESTART,也无效。在man msgrcv中就有提到这点:

    msgsnd and msgrcv are never automatically restarted after being interrupted by a signal handler, regardless of the setting  of the SA_RESTART flag when establishing a signal  handler.

     

    忽略信号

    当然最简单的方法是忽略信号,在安装信号时,明确告诉系统不会产生该信号的中断。

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. struct sigaction action;  
    2.    
    3. action.sa_handler = SIG_IGN;  
    4. sigemptyset(&action.sa_mask);  
    5.    
    6. sigaction(SIGALRM, &action, NULL);  

    测试代码1

    1. #include <signal.h>  
    2. #include <stdio.h>  
    3. #include <stdlib.h>  
    4. #include <error.h>  
    5. #include <string.h>  
    6. #include <unistd.h>  
    7.    
    8. void sig_handler(int signum)  
    9. {  
    10.     printf("in handler ");  
    11.     sleep(1);  
    12.     printf("handler return ");  
    13. }  
    14.    
    15. int main(int argc, char **argv)  
    16. {  
    17.     char buf[100];  
    18.     int ret;  
    19.     struct sigaction action, old_action;  
    20.    
    21.     action.sa_handler = sig_handler;  
    22.     sigemptyset(&action.sa_mask);  
    23.     action.sa_flags = 0;  
    24.     /* 版本1:不设置SA_RESTART属性 
    25.      * 版本2:设置SA_RESTART属性 */  
    26.     //action.sa_flags |= SA_RESTART;  
    27.    
    28.     sigaction(SIGALRM, NULL, &old_action);  
    29.     if (old_action.sa_handler != SIG_IGN) {  
    30.         sigaction(SIGALRM, &action, NULL);  
    31.     }  
    32.     alarm(3);  
    33.      
    34.     bzero(buf, 100);  
    35.    
    36.     ret = read(0, buf, 100);  
    37.     if (ret == -1) {  
    38.         perror("read");  
    39.     }  
    40.    
    41.     printf("read %d bytes: ", ret);  
    42.     printf("%s ", buf);  
    43.    
    44.     return 0;  
    45. }  

    在ubuntu 10.04 上测试结果:
    不设置SA_RESTART,执行结果如下:
    说明接受信号处理完成以后,主函数收到EINTR信号,read函数返回-1,退出
    设置SA_RESTART,执行结果如下:

    说明设置SA_RESTART参数以后,自动重新调用read函数,没有体现在应用层代码中,在应用层看来,这个EINTR没有造成任何影响。

    个人认为下面的总结很重要:

    慢系统调用(slow system call)会被信号中断,系统调用函数返回失败,并且errno被置为EINTR(错误描述为“Interrupted system call”)。

    处理方法有以下三种:①人为重启被中断的系统调用;②安装信号时设置 SA_RESTART属性;③忽略信号(让系统不产生信号中断)。

    有时我们需要捕获信号,但又考虑到第②种方法的局限性(设置 SA_RESTART属性对有的系统无效,如msgrcv),所以在编写代码时,一定要“人为重启被中断的系统调用”

  • 相关阅读:
    LeetCode 230. 二叉搜索树中第K小的元素(Kth Smallest Element in a BST)
    LeetCode 216. 组合总和 III(Combination Sum III)
    LeetCode 179. 最大数(Largest Number)
    LeetCode 199. 二叉树的右视图(Binary Tree Right Side View)
    LeetCode 114. 二叉树展开为链表(Flatten Binary Tree to Linked List)
    LeetCode 106. 从中序与后序遍历序列构造二叉树(Construct Binary Tree from Inorder and Postorder Traversal)
    指针变量、普通变量、内存和地址的全面对比
    MiZ702学习笔记8——让MiZ702变身PC的方法
    你可能不知道的,定义,声明,初始化
    原创zynq文章整理(MiZ702教程+例程)
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/7115161.html
Copyright © 2020-2023  润新知