当前位置: 首页 > news >正文

I/O复用的高级应用之一:非阻塞 connect———使用 select 实现(也可以用 poll 实现)

一、啥是 非阻塞 connect

我们之前唯一使用过的非阻塞socket文件描述符是在服务器中,使用非阻塞的方式可以大大提升服务器程序的效率。

现在试想,我们把客户端程序的 socket 设置为非阻塞的,那么用该 socket 文件描述符进行 connect 连接就很容易报错。为什么呢?因为,非阻塞下,对该文件描述符的调用直接返回结果,而 socket 连接很难这么快完成,所以就很容易调用失败,此时会设置errno的值为:EINPROGRESS。该错误类型发生在,对非阻塞的 socket 调用 connect,而连接又没有建立时。所以当 connect 返回-1时,并不一定发生了错误,可能连接还在进行中!

此事的解决方法是,调用selectpoll等函数监听这个连接失败的 socket 上的可写事件。当selectpoll等函数返回后,再利用getsockopt读取错误码。如果错误码是0,表示刚刚的 connect 确实没发生错误,连接成功建立,若错误码为1,表示确实是发生了错误。

这么麻烦的用非阻塞 connect 有啥好处吗?

通过使用非阻塞connect,我们就能同时发起多个连接并一起等待

2、非阻塞 connect 实例程序

下面是一个客户端程序,它使用一个非阻塞 socket 连接服务器。

#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<time.h>
#include<sys/ioctl.h>

#define BUFFER_SIZE 1023

int setnonblocking(int fd)
{
	int old_option = fcntl(fd, F_GETFL);
	int new_option = old_option | O_NONBLOCK;
	fcntl(fd, F_SETFL, new_option);
	return old_option;
}

/* 超时连接函数,参数分别为IP地址,端口号,和超时时间(毫秒)。成功返回连接状态的 socket,失败返回-1 */
int unblock_connect(const char* ip, int port, int time)
{
	int ret = 0;
	struct sockaddr_in address;
	bzero(&address, sizeof(address));
	address.sin_family = AF_INET;
	address.sin_port = htons(port);
	inet_pton(AF_INET, ip, &address);

	int sockfd = socket(PF_INET, SOCK_STREAM, 0);
	int fdopt = setnonblocking(sockfd);
	ret = connect(sockfd, (struct sockaddr*)&address, sizeof(address));
	if (ret == 0)										/* 连接成功了 */
	{
		/* 如果连接成功,则恢复 sockfd 的属性,并立即返回之 */
		printf("connect whit server immediately\n");
		fcntl(sockfd, F_SETFL, fdopt);
		return sockfd;
	}
	else if (errno != EINPROGRESS)		/* 如果错误类型不是 EINPROGRESS,说明发生了其他的错误,立即返回 */
	{
		/* 如果连接没有立即建立,那么只有当 errno 是 EINPROGRESS 时才表示连接还在进行,否则出错返回 */
		printf("unblock connect not support\n");
		return -1;
	}

	fd_set readfds;
	fd_set writefds;
	struct timeval timeout;

	FD_ZERO(&readfds);
	FD_SET(sockfd, &writefds);

	timeout.tv_sec = time;
	timeout.tv_usec = 0;

	ret = select(sockfd + 1, NULL, &writefds, NULL, &timeout);
	if (ret <= 0)
	{
		/* select 超时或者出错,立即返回 */
		printf("connection time out\n");
		close(sockfd);
		return -1;
	}

	if (!FD_ISSET(sockfd, &writefds))	/* 如果 sockfd 不可写,说明连接失败了,立即返回 */
	{
		printf("no events on sockfd found\n");
		close(sockfd);
		return -1;
	}

	int error = 0;
	socklen_t length = sizeof(error);
	/* 调用 getsockopt 来获取并清除 sockfd 上的错误 */
	if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &length) < 0)
	{
		printf("connection failed after select with the error: %d\n", error);
		close(sockfd);
		return -1;
	}
	/* 错误号不为 0 表示连接出错 */
	if (error != 0)
	{
		printf("connection failed after select with the error: %d\n", error);
		close(sockfd);
		return -1;
	}
	/* 连接成功 */
	printf("connection ready after select with the socket: %d\n", sockfd);
	fcntl(sockfd, F_SETFL, fdopt);
	return sockfd;
}

int main(int argc, char* argv[])
{
	if (argc <= 2)
	{
		return 1;
	}
	const char* ip = argv[1];
	int port = atoi(argv[2]);

	int sockfd = unblock_connect(ip, port, 10);
	if (sockfd < 0)
	{
		return 1;
	}
	close(sockfd);
	return 0;
}

相关文章:

  • I/O复用的高级应用:同时处理 TCP 和 UDP 服务
  • I/O复用的高级应用:聊天室程序———实例代码
  • select、poll、epoll的使用方法 和 使用场景
  • 使用统一事件源的方式同时处理信号和 I/O
  • 使用SIGURG信号接受带外数据
  • 信号 ——《Linux高性能服务器编程》第10章——读书笔记
  • Linux 文件I/O 及其 多个相关函数
  • python——学校课程预习+复习
  • Linux 进程———详解
  • Linux服务器实例程序———使用定时器列表处理非活动连接
  • 各种最短路问题的常用算法模板
  • Linux高级进程编程———在任意两个进程间传递文件描述符:使用 sendmsg 和 recvmsg 实现
  • Linux 线程———详解
  • Linux 中与字符串相关的函数strpbrk、strcasecmp、strspn(不间断更新)
  • C++ printf族函数
  • CentOS 7 修改主机名
  • create-react-app项目添加less配置
  • emacs初体验
  • express.js的介绍及使用
  • fetch 从初识到应用
  • Java超时控制的实现
  • Meteor的表单提交:Form
  • v-if和v-for连用出现的问题
  • vue数据传递--我有特殊的实现技巧
  • 安装python包到指定虚拟环境
  • 移动端解决方案学习记录
  • 用Python写一份独特的元宵节祝福
  • 3月7日云栖精选夜读 | RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势 ...
  • Java总结 - String - 这篇请使劲喷我
  • 微龛半导体获数千万Pre-A轮融资,投资方为国中创投 ...
  • # Pytorch 中可以直接调用的Loss Functions总结:
  • #include<初见C语言之指针(5)>
  • #QT(TCP网络编程-服务端)
  • $$$$GB2312-80区位编码表$$$$
  • (12)Hive调优——count distinct去重优化
  • (Repost) Getting Genode with TrustZone on the i.MX
  • (阿里云万网)-域名注册购买实名流程
  • (博弈 sg入门)kiki's game -- hdu -- 2147
  • (待修改)PyG安装步骤
  • (全部习题答案)研究生英语读写教程基础级教师用书PDF|| 研究生英语读写教程提高级教师用书PDF
  • (幽默漫画)有个程序员老公,是怎样的体验?
  • (转)创业家杂志:UCWEB天使第一步
  • (转)详解PHP处理密码的几种方式
  • (转载)深入super,看Python如何解决钻石继承难题
  • .NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划
  • .net 生成二级域名
  • .NET/C# 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions)
  • .Net中wcf服务生成及调用
  • .sys文件乱码_python vscode输出乱码
  • @Tag和@Operation标签失效问题。SpringDoc 2.2.0(OpenApi 3)和Spring Boot 3.1.1集成
  • [ABP实战开源项目]---ABP实时服务-通知系统.发布模式
  • [Android Pro] android 混淆文件project.properties和proguard-project.txt
  • [Android]如何调试Native memory crash issue
  • [C++参考]拷贝构造函数的参数必须是引用类型
  • [Java][方法引用]构造方法的引用事例分析