网络编程-TCP连接断开检测

Posted by 周思进 on February 27, 2020

客户端需要实时发送报警信息给服务端,以TCP建立连接。因为需要实时发送,如果以短连接的方式实现,每一条数据发送都需要经过3次握手建立连接和4次挥手释放连接,这会大大影响效率,所以考虑以长连接的方式实现,即客户端和服务端建立连接之后,就不关闭连接,后面有报警数据上来,直接通过现有套接字往服务端发送。

虽然客户端不会主动关闭连接,但还是要考虑服务端主动关闭连接的情况,比如服务端如果设置了keepalive属性,如果该长连接客户端长时间没有发送数据给服务端,则服务端就会关闭该连接。

在测试过程中发现,服务端在某一个中间空闲时刻关闭了该长连接,但客户端后面有数据上来时,仍旧使用原先建立的socket套接字进行发送,虽然连接已经断开,但该次发送操作,write接口并没有报错,返回正常发送数据。

实际上服务端已经断开了该连接,在接收到客户端的PSH操作后,对该连接进行了RST回应,这样当客户端下次再尝试通过该套接字发送时才发现该连接已经断开了,但这样就导致前面一条报警信息发送并没有及时得知发送异常了,导致数据丢失。

下面是本地模拟的抓包现象: image

这就需要在发送时确认建立的连接是否已经断开,通过如下代码实现可以很好的检测当前连接是否断开

// 方案一
#include <netinet/tcp.h>

int is_connect_closed(int sockfd)
{
	struct tcp_info info;
	int len = sizeof(info);
	int ret = -1;

	if (sockfd <= 0)
	{
		return 1;
	}
	
	memset(&info, 0, sizeof(info));
	ret = getsockopt(sockfd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
	if (ret != 0)
	{
		return 1;
	}

	if ((info.tcpi_state == TCP_ESTABLISHED))
	{
		return 0;
	}
	else
	{
		return 1;
	}
}

// 方案二

int is_connect_closed(int sockfd)
{
	fd_set rset;
	int ret = 0;
	struct timeval timeout;
	char data;

	FD_ZERO(&rset);
	FD_SET(sockfd, &rset);

	timeout.tv_sec = 0;
	timeout.tv_usec = 100000;

	ret = select(sockfd+1, &rset, NULL, NULL, &timeout);
	if (ret > 0)
	{
		ret = read(sockfd, &data, 1);
		if (ret == 0)	// 正常连接关闭read会返回0
		{
			return 1;
		}
		else if (ret < 0)
		{
			// 暂不确定还有哪些错误码表示连接断开了
		}
	}

	return 0;
}

相对而言方案一比较直接明确。

因为还实现了加密传输方式,该检测接口对于TLS加密传输套接字判断同样适用。

说道TLS加密传输,在实现过程中还遇到另一个问题,就是服务器认证端口需要双向认证,即客户端在连接过程中需要传输自己的证书给服务器,但设备端实际并没有配置客户端证书,然后发现SSL_connect接口却是正常返回,并没有报错。实际抓包是可以看到该连接随后被服务器给断开了,同样可以通过上述方法来确认连接是否建立成功。