5 发送数据与错误处理

本文介绍了FastDFS中的tcpsenddata_nb函数在非阻塞模式下发送数据,以及在遇到EAGAIN、EWOULDBLOCK和EINTR错误时的处理方式。特别讨论了EINTR错误的产生原因,并探讨了为何需要处理errno=0的情况,可能是由于信号中断导致的errno被修改。同时提到了服务器崩溃时,客户端的重传机制和超时错误ETIMEOUT。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、tcpsenddata_nb

tcpsenddata_nb()是fastdfs中主要的用于发送数据的函数。注意:本例sock的非阻塞的。

int tcpsenddata_nb(int sock, void* data, const int size, const int timeout)
{
	int left_bytes;
	int write_bytes;
	int result;
	unsigned char* p;
#ifdef USE_SELECT
	fd_set write_set;
	struct timeval t;
#else
	struct pollfd pollfds;
#endif

#ifdef USE_SELECT
	FD_ZERO(&write_set);
	FD_SET(sock, &write_set);
#else
	pollfds.fd = sock;
	pollfds.events = POLLOUT;
#endif

	p = (unsigned char*)data;
	left_bytes = size;
	while (left_bytes > 0)
	{
		write_bytes = send(sock, p, left_bytes, 0);
		if (write_bytes < 0)
		{
			if (!(errno == EAGAIN || errno == EWOULDBLOCK))  //如果是这两个错误,则问题不太,重新调用send()即可。
			{
				return errno != 0 ? errno : EINTR;
			}
		}
		else
		{
			left_bytes -= write_bytes;
			p += write_bytes;
			continue;
		}
//以下在write_bytes<0时执行。
#ifdef USE_SELECT
		if (timeout <= 0)
		{
			result = select(sock+1, NULL, &write_set, NULL, NULL);
		}
		else
		{
			t.tv_usec = 0;
			t.tv_sec = timeout;
			result = select(sock+1, NULL, &write_set, NULL, &t);
		}
#else
		result = poll(&pollfds, 1, 1000 * timeout);
		if (pollfds.revents & POLLHUP)
		{
			return ENOTCONN;
		}
#endif

		if (result < 0)  //select的返回结果<0,说明出了严重问题。
		{
			return errno != 0 ? errno : EINTR;
		}
		else if (result == 0)  //返回结果=0,表示超时。
		{
			return ETIMEDOUT;
		}
	}

	return 0;
}


二、关于EAGAIN、EWOULDBLOCK、EINTR

在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。一般出现在非阻塞调用。

1.EINTR错误产生的原因:(出现在阻塞调用)

注意:非阻塞调用并不会被信号中断,但是在非阻塞调用和判断erron之间(属于用户态,信号的处理是在用户态下进行的)可能会有信号中断。 http://willko.iteye.com/blog/1691741

当阻塞于某个慢系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可能会返回一个EINTR错误。例如:在socket服务器端,设置了信号捕获机制,有子进程当在父进程阻塞于慢系统调用时由父进程捕获到了一个有效信号时,内核会致使accept返回一个EINTR错误(被中断的系统调用)。

2.如何解决:
当碰到EINTR错误的时候,可以采取有一些可以重启的系统调用进行重启,而对于有一些系统调用是不能够重启的。例如:accept、read、wiret、seletc、ioctl和open之类的函数来说是可以进行重启的。不过对于套接字编程中的connect函数是不能进行重启的(每次connect失败后,套接口就不可再用,必须关闭,重新调用socket())。若connetct函数返回一个EINTR错误的时候,则不能再次调用它,否则将返回一个错误。针对connect不能重启的特性,必须调用select函数来等待连接完成。
补充:慢系统调用(slow system call)
该术语适用于那些可能永远阻塞的系统调用。永远阻塞的系统调用是指调用永远无法返回,多数网络支持函数都属于这一类。


三、errno != 0 ? errno : EINTR处理的原因猜测

问题得根本在于:我们为什么需要处理errno=0,?或者说errno=0在什么情况下会出现?

首先,是没有函数将erron设置为0的(不过errno是可以手动修改的,这么做并无意义,只有在出错时检查errno才是有意义的)。其次,在正常情况下,应当是一个线程一个局部的errno变量,从而保证安全性。最后,非阻塞调用并不会被信号中断,但是在非阻塞调用和判断erron之间可能会有信号中断。

send和判断errno之间可能会发生信号中断(因此对于信号处理函数,应当保持errno值,退出时恢复errno值),因此有errno != 0 ? errno : EINTR(理论上讲,在send出错时,errno应当不为0,;但有可能发生信号处理函数手动修改了errno,且没有恢复errno值)。

另一个可能性:在如Solaris系统中,如果没有正确的编译(在这些系统中,不可以cc -c foo.c && cc foo.o -lpthread这样编译),可能会使用全局的errno变量。参考http://fixunix.com/unix/245807-errno%3D0-how-debug-avoid-problem.html。

综上所述,errno = 0很可能由于信号中断并被修改造成的。我觉得如果没有做过手动设置errno,完全没有必要检查errno是否为0。希望有人指正。


四、关于ETIMEDOUT

如果服务器崩溃,客户端持续重传数据分节,试图从服务器上接收一个ACK。源自Berkeley的实现重传该数据分节12次,共约9分钟才放弃重传。当客户TCP最终放弃时,会返回客户进程一个错误,即ETIMEOUT。详见《Unix网络编程》第三版5.14节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值