网络编程0x03 EAGAIN错误和EINTR错误

网络编程0x03 EAGAIN错误和EINTR错误

1. 慢系统调用

该术语适用于那些可能永远阻塞的系统调用。永远阻塞的系统调用是指调用永远无法返回,多数网络支持函数都属于这一类。如:若没有客户连接到服务器上,那么服务器的accept调用就会一直阻塞。

慢系统调用可以被永久阻塞,包括以下几个类别:

(1)读写‘慢’设备(包括pipe,终端设备,网络连接等)。读时,数据不存在,需要等待;写时,缓冲区满或其他原因,需要等待。读写磁盘文件一般不会阻塞。
(2)当打开某些特殊文件时,需要等待某些条件,才能打开。例如:打开中断设备时,需要等到连接设备的modem响应才能完成。
(3)pause和wait函数。pause函数使调用进程睡眠,直到捕获到一个信号。wait等待子进程终止。
(4)某些ioctl操作。
(5)某些IPC操作。

2. EAGAIN错误

在Linux环境下开发经常会碰到很多错误(设置errno),其中EAGAIN是其中比较常见的一个错误(比如用在非阻塞操作中). 从字面上来看,是提示再试一次。这个错误经常出现在当应用程序进行一些非阻塞(non-blocking)操作(对文件或socket)的时候。例如,以O_NONBLOCK的标志打开文件/socket/FIFO,如果你连续做read操作而没有数据可读。此时程序不会阻塞起来等待数据准备就绪返回,read函数会返回一个错误EAGAIN,提示你的应用程序现在没有数据可读请稍后再试。又例如,当一个系统调用(比如fork)因为没有足够的资源(比如虚拟内存)而执行失败,返回EAGAIN提示其再调用一次(也许下次就能成功)。

2.1 非阻塞socket编程处理EAGAIN错误

在linux进行非阻塞的socket接收数据时经常出现Resource temporarily unavailable,errno代码为11(EAGAIN),这说明在非阻塞模式下调用了阻塞操作,在该操作没有完成就返回这个错误,这个错误不会破坏socket的同步,不用管它,下次循环接着recv就可以。对非阻塞socket而言,EAGAIN不是一种错误。在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。


size_t socket_send(int sockfd, const char* buffer, size_t buflen)
{
	size_t tmp;
	size_t total = buflen;
	const char *p = buffer;
 
	while(1)
	{
		tmp = send(sockfd, p, total, 0);
 
		if(tmp < 0)
		{
			// 当send收到信号时,可以继续写,但这里返回-1.
			if(errno == EINTR)
			{
				return -1;
			}
 
			// 当socket是非阻塞时,如返回此错误,表示写缓冲队列已满,
			// 在这里做延时后再重试.
			if(errno == EAGAIN)
			{
				usleep(1000);
				continue;
			}
 
			return -1;
		}
 
		if((size_t)tmp == total)
		{
			return buflen;
		}
 
		total -= tmp;
		p += tmp;
	}
 
	return tmp;
}

3. EINTR错误产生的原因

阻塞的系统调用、或者非阻塞的系统调用.
如果进程在一个慢系统调用(slow system call)中阻塞时,当捕获到某个信号且相应信号处理函数返回时,这个系统调用不再阻塞而是被中断,就会调用返回错误(一般为-1)且设置errno为EINTR(相应的错误描述为“Interrupted system call”)。

如下表所示的系统调用就会产生EINTR错误,当然不同的函数意义也不同。

系统调用函数errno为EINTR表征的意义
write由于信号中断,没写成功任何数据。The call was interrupted by a signal before any data was written.
open由于信号中断,没读到任何数据。The call was interrupted by a signal before any data was read
recv由于信号中断返回,没有任何数据可用。 The receive was interrupted by delivery of a signal before any data were available
sem_wait函数调用被信号处理函数中断。The call was interrupted by a signal handler.

4. EINTER解决办法

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

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

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

理解“重启”?

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

    //1.创建一个socket
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd == -1)
    {
        std::cout << "create clientfd error...\n";
        return -1;
    }
	
	//将 clientfd 设置成非阻塞模式
	int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);
	int newSocketFlag = oldSocketFlag | O_NONBLOCK;
	if (fcntl(clientfd, F_SETFL,  newSocketFlag) == -1)
	{
		close(clientfd);
		std::cout << "set nonblock error...\n";
		return -1;
	}

    //2.连接服务器
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);
    serveraddr.sin_port = htons(SERVER_PORT);
	for (;;)
	{
		int ret = connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
		if (ret == 0)
		{
			std::cout << "connect to server successfully." << std::endl;
		} 
		else if (ret == -1) 
		{
			if (errno == EINTR)
			{
				//connect 动作被信号中断,重试connect
				continue;
			} else if (errno == EINPROGRESS)
			{
				//连接正在尝试中
				break;
			} else {
				//真的出错了,
				close(clientfd);
				return -1;
			}
		}
	}

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

struct sigaction action;             
action.sa_handler = handler_func;      
sigemptyset(&action.sa_mask);      
action.sa_flags = 0;      /* 设置SA_RESTART属性 */      
action.sa_flags |= SA_RESTART;            
sigaction(SIGALRM, &action, NULL);  

并不是所有的系统调用都有效

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

struct sigaction action;            
action.sa_handler = SIG_IGN;      
sigemptyset(&action.sa_mask);             
sigaction(SIGALRM, &action, NULL);  
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在 ALSA 的非阻塞模式下,`snd_pcm_writei()` 或 `snd_pcm_writen()` 函数可能会返回 `-EAGAIN` 错误,表示缓冲区已满或无法写入数据。解决这个问题的关键是要等待一段时间后再尝试写入数据。以下是一个简单的示例,展示如何处理 `-EAGAIN` 错误: ``` ssize_t write_frames(snd_pcm_t *pcm, const void *buffer, size_t frames) { ssize_t written = 0; while (written < frames) { ssize_t n = snd_pcm_writei(pcm, buffer + written * frame_size, frames - written); if (n < 0) { if (n == -EAGAIN) { // 等待一段时间后再尝试写入数据 snd_pcm_wait(pcm, 100); // 100 毫秒 } else { // 其他错误 return n; } } else { // 成功写入 n 帧数据 written += n; } } return written; } ``` 在这个示例中,`write_frames()` 函数将数据写入 PCM 流,并返回写入的帧数。如果 `snd_pcm_writei()` 函数返回 `-EAGAIN` 错误,就等待一段时间后再尝试写入数据。在这里,使用 `snd_pcm_wait()` 函数等待 100 毫秒后再尝试写入数据。可以根据实际情况调整等待时间。如果 `snd_pcm_writei()` 函数返回其他错误,就直接返回该错误码。如果成功写入了一些数据,就继续写入剩余的数据,直到所有数据都写入完成。 需要注意的是,等待时间不能太长,否则会导致音频输出延迟。另外,在非阻塞模式下,需要及时检查缓冲区状态,以便及时写入数据。如果缓冲区已满,就需要等待一段时间后再尝试写入数据。如果缓冲区中的数据已经被播放完毕,就需要及时写入数据,以避免音频中断。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Erice_s

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值