Windows上如何玩非阻塞的connect?---让程序员自定义connect函数的超时时间

972 篇文章 327 订阅
147 篇文章 46 订阅

        我们知道, 对于阻塞的socket而言, connect函数也是阻塞的, 我在Windows上测试过, 对于阻塞的socket而言, connect的阻塞时间约为25s(linux上是75s吧, 各个平台都不一样).  也就是说, 很多时候, 客户端需要等25s才继续往下执行。 我们想象一下, 用户肯定会不满意啊, 得罪了用户, 那就糟糕了。 那能不能搞个自己设置超时时间的connect函数呢? 完全可以! 在本文中, 我们来学习一下非阻塞connect函数的实现---让程序员自定义connect函数的超时时间。

       说明: 两年后, 当我再次审视这些程序的时候, 我发现, select后, 强烈建议做FD_ISSET检查。

 

       直接上客户端的代码:

 

#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")

int main()
{
	// 网络初始化
	WORD wVersionRequested;
	WSADATA wsaData;
	wVersionRequested = MAKEWORD(2, 2);
	WSAStartup( wVersionRequested, &wsaData );


	// 创建客户端socket(默认为是阻塞socket)
	SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);


	// 设置为非阻塞的socket
	int iMode = 1;
	ioctlsocket(sockClient, FIONBIO, (u_long FAR*)&iMode); 


	// 定义服务端
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(8888);


	// 超时时间
	struct timeval tm;
	tm.tv_sec  = 5;
	tm.tv_usec = 0;
	int ret = -1;
	

	// 尝试去连接服务端
	if (-1 != connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
		ret = 1; // 连接成功
	}
	else
	{
		fd_set set;
		FD_ZERO(&set);
		FD_SET(sockClient, &set);

		if (select(-1, NULL, &set, NULL, &tm) <= 0)
		{
			ret = -1; // 有错误(select错误或者超时)
		}
		else
		{
			int error = -1;
			int optLen = sizeof(int);
			getsockopt(sockClient, SOL_SOCKET, SO_ERROR, (char*)&error, &optLen); 
			
			// 之所以下面的程序不写成三目运算符的形式, 是为了更直观, 便于注释
			if (0 != error)
			{
				ret = -1; // 有错误
			}
			else
			{
				ret = 1;  // 无错误
			}
		}
	}


	// 设回为阻塞socket
	iMode = 0;
	ioctlsocket(sockClient, FIONBIO, (u_long FAR*)&iMode); //设置为阻塞模式


	// connect状态
	printf("ret is %d\n", ret);


	// 发送数据到服务端测试以下
	if(1 == ret)
	{
		send(sockClient, "hello world", strlen("hello world") + 1, 0);
	}


	// 释放网络连接
	closesocket(sockClient);
	WSACleanup();

	return 0;
}

 

 

 

 

 

 

      我们先不管服务端, 直接运行上面的程序, 过5s后, 程序结果为:ret is -1

 

 

      好, 我们关掉当面的客户端。 并启用下面的服务端:

 

#include <stdio.h>
#include <winsock2.h> // winsock接口
#pragma comment(lib, "ws2_32.lib") // winsock实现

int main()
{
	WORD wVersionRequested;  // 双字节,winsock库的版本
	WSADATA wsaData;         // winsock库版本的相关信息
	
	wVersionRequested = MAKEWORD(1, 1); // 0x0101 即:257
	

	// 加载winsock库并确定winsock版本,系统会把数据填入wsaData中
	WSAStartup( wVersionRequested, &wsaData );
	

	// AF_INET 表示采用TCP/IP协议族
	// SOCK_STREAM 表示采用TCP协议
	// 0是通常的默认情况
	unsigned int sockSrv = socket(AF_INET, SOCK_STREAM, 0);

	SOCKADDR_IN addrSrv;

	addrSrv.sin_family = AF_INET; // TCP/IP协议族
	addrSrv.sin_addr.S_un.S_addr = inet_addr("0.0.0.0"); // socket对应的IP地址
	addrSrv.sin_port = htons(8888); // socket对应的端口

	// 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程)
	bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

	// 将socket设置为监听模式,5表示等待连接队列的最大长度
	listen(sockSrv, 5);

	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);
	unsigned int sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len);

	printf("To receive...\n");
	char recvBuf[100] = {0};
	recv(sockConn, recvBuf, 100, 0); // 接收客户端数据,最后一个参数一般设置为0
	printf("recv is %s\n", recvBuf);

	while(1);

	closesocket(sockConn);	
	closesocket(sockSrv);
	WSACleanup();
	
	return 0;
}

 

 

     然后呢, 我们再启动客户端, 发现客户端立即出现:ret is 1, 服务端对应的结果为:

To receive...
recv is hello world

 

       由此可见, 上面的客户端程序实现了非阻塞的connect, 也就是用, 程序员可以自定义超时时间。 ok, 先这样。

 

  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
在 ESP32-C3 上使用 esp-idf 4.3.5 开发 TCP client,可以通过以下步骤设置非阻塞式的超时时间: 1. 创建一个 TCP socket,可以参考以下代码: ``` int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (sock < 0) { ESP_LOGE(TAG, "Failed to create socket"); return; } ``` 2. 将 socket 设置为非阻塞模式,可以参考以下代码: ``` int flags = fcntl(sock, F_GETFL, 0); fcntl(sock, F_SETFL, flags | O_NONBLOCK); ``` 3. 设置超时时间,可以使用 setsockopt 函数,可以参考以下代码: ``` struct timeval timeout = {5, 0}; // 超时时间为5秒 setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); ``` 4. 连接到服务器,可以使用 connect 函数,可以参考以下代码: ``` struct sockaddr_in server_addr; server_addr.sin_addr.s_addr = inet_addr(server_ip); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(server_port); int ret = connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)); if (ret < 0 && errno != EINPROGRESS) { ESP_LOGE(TAG, "Failed to connect to server"); close(sock); return; } ``` 在以上代码中,connect 函数可能会返回 EINPROGRESS 错误码,表示连接仍在进行中。如果连接成功,则 connect 函数会立即返回。如果连接超时,则会返回 ETIMEDOUT 错误码。 5. 发送数据和接收数据时,可以使用非阻塞式的方式,可以参考以下代码: ``` // 发送数据 int ret = send(sock, data, len, 0); if (ret < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { ESP_LOGE(TAG, "Failed to send data"); close(sock); return; } // 接收数据 char buffer[1024]; ret = recv(sock, buffer, sizeof(buffer), 0); if (ret < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { ESP_LOGE(TAG, "Failed to receive data"); close(sock); return; } ``` 在以上代码中,send 和 recv 函数可能会返回 EAGAIN 或者 EWOULDBLOCK 错误码,表示操作仍在进行中。如果超时,则会返回 ETIMEDOUT 错误码。 需要注意的是,在使用非阻塞式的方式进行网络通信时,需要处理 EAGAIN、EWOULDBLOCK 和 ETIMEDOUT 错误码。同时,还需要使用 select 或者 epoll 等函数,来检测网络 socket 是否可读或可写。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值