1 背景

写一个c++的TCP通信程序,使用了recv接口。因为该接口默认为阻塞,因此除非检测到连接断开,否则线程会一直阻塞在那里。

现在需要将recv接口增加一个超时,即在n秒内未能recv任何数据,则跳出阻塞。

2 解决方法

解决方法可以有两种:

  1. 设置socket接受超时法:recv本身可以设置超时(推荐)。
  2. 信号法:recv可以通过信号的方式解除阻塞。

2.1 设置socket超时

在建立连接前增加超时设置:

auto fd = socket(PF_INET, SOCKET_STREAM, 0);
if(-1 == fd){
	return "error";
}
 
// 设置超时
struct timeval timeout = {3, 0}; // 这里设置3秒超时时间
setsocketopt(fd, SOL_SOCKET, SO_RCVTIMEO, (void*)&timeout, sizeof(struct timeval));
 
if (0 > connect(fd, (struct sockaddr*)&addr_in, sizeof(struct sockaddr))){
	return "error";
}

如此就可以在接收不到任何数据后的3秒时间断开阻塞。

2.2 信号法

我们可以利用信号可以解除阻塞的机制进行,就比如我们利用SIGALRM信号,可以使用alarm函数在n秒后触发信号:

#include <signal.h>
 
void handleSigAlarm(int sign_no){ }
 
int main(){
	struct sigaction alarm_act;
	bzero(&alarm_act,sizeof(sigaction));
	alarm_act.sa_handler = handleSigAlarm;
	alarm_act.sa_flags = SA_NOMASK; // 这里一定是NOMASK
	sigaction(SIGALRM,&alarm_act,NULL);
	//...
	while(true){
		alarm(3); // 这里设置3秒超时
		auto ret = recv(....)
		alarm(0); // 收到后复位
	}
}
 

需要注意的是其中的sa_flags

  • 如果其值为SA_NOMASK,则会中断已阻塞的函数,使程序继续往下执行,即略过阻塞。
  • 如果其值为SA_RESTART,则会重启函数,也就是会复位继续在recv上阻塞。