网络编程-超时连接(下)

Posted by 周思进 on December 6, 2019

可以通过两种编程方式来修改默认连接超时等待时间

一、通过setsockopt来设置套接字SO_SNDTIMEO属性,部分代码如下:

	timeout.tv_sec = 20;
	timeout.tv_usec = 0;
	ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, len);
	if (-1 == ret)
	{
		printf("[FILE:%s] [FUNC:%s] [Line:%d]    \n", __FILE__, __func__, __LINE__ );
		return -1;
	}

	ret = connect(sockfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));
	if (-1 == ret)
	{
		if (errno == EINPROGRESS)
		{
			printf("[FILE:%s] [FUNC:%s] [Line:%d]  connect timeout  \n", __FILE__, __func__, __LINE__ );
		}

		return ret;
	}

使用上述代码测试后,针对非子网不可访问目标主机进行通信,就不会等待默认2分钟之久了,超时20S就会返回。

该方式对于发送数据接口一样适用,不过对于超时返回的情况下,errno返回值会不一样,像connect则是置为EINPROGRESS,发送操作则是置为EAGIN或EWOULDBLOCK。

二、将套接字设置成非阻塞,由select接口来设置超时等待时间,部分代码如下:

	block_flag = 1;
	if (ioctl(sockfd, FIONBIO, &block_flag) != 0)
	{
		printf("[FILE:%s] [FUNC:%s] [Line:%d]  ioctl failed.  \n", __FILE__, __func__, __LINE__ );
		return -1;
	}

	ret = connect(sockfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));
	if (-1 == ret)
	{
		if (errno == EINPROGRESS)
		{
			FD_ZERO(&set);
			FD_SET(sockfd, &set);

			timeout.tv_sec = 20;
			timeout.tv_usec = 0;

			if (select(sockfd + 1, NULL, &set, NULL, &timeout) > 0)
			{
				(void)getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &err, &len);
				if (0 == err)
				{
					ret = 0;
				}
			}
			else
			{
				printf("[FILE:%s] [FUNC:%s] [Line:%d]  timeout   \n", __FILE__, __func__, __LINE__ );
			}
		}
	}

	block_flag = 0;
	if (ioctl(sockfd, FIONBIO, &block_flag) != 0)
	{
		printf("[FILE:%s] [FUNC:%s] [Line:%d]    \n", __FILE__, __func__, __LINE__ );
	}

connect接口会立即返回EINPROGRESS错误,不过底层会继续进行TCP三次握手连接,应用需要使用select接口来判断连接成功还是失败,同样可以通过select的第三个参数来设置连接超时等待时间。

这里说到非阻塞,有一点需要理解,就是原本套接字是阻塞的,如果connect阻塞住了,CPU就会挂起该线程,只能去处理其他线程;如果设置成非阻塞,但其后select只是处理这一路连接,则仍旧是阻塞等待连接成功或失败为止,和前面的唯一区别是可设置等待时间。

一般设置非阻塞主要用于下面三种场景:
1、设置连接超时,就如前面所说的
2、多路复用,比如要建立多个连接,不用等这一路连接成功,才能建立下一个连接,这里注意是一个线程建立多个连接
3、在连接过程中处理其他事务

设置超时操作还好,这里的主要问题是如何判断连接是否建立成功了呢?这个通过查看《UNIX网络编程》了解,需要考虑两种情况,

1、目标主机是本机地址,则可能connect后已经建立连接,这种情况可以直接返回建立连接后的套接字

2、通过select来判断是否连接建立成功,如果连接建立成功,则套接字可写,不过书中有说道如果发生错误,则套接字可读可写,所以不能单纯的通过可写来判断,那么只能通过判断是否有发生错误来判断连接是否建立成功,如代码中通过getsockopt接口来获取错误信息。

需要注意连接成功后,需要将套接字重新设置为阻塞模式。前面讲的第一种方式其实也需要有类似的操作,否则后面的发送操作也会沿用开始设定的超时机制,除非明确就是要这样的。


1、
编码过程中一开始主机地址和端口使用的变量名分别是server和port,不过看man手册示例代码的时候,基本是命名为host和server,可见对于server我的理解是有偏差的。
想想一个功能一般称为服务,比如FTP服务,对应的端口就是服务端口,就如linux下的服务端口文件/etc/services。

2、
connect接口如果返回出错,想要重新连接,需要使用新的套接字,不能使用原有的套接字继续连接。