• Linux下connect函数 阻塞 与 非阻塞 问题


    一、概述

    linux系统下,connect函数是阻塞的,阻塞时间的长度与系统相关。而如果把套接字设置成非阻塞,调用connect函数时会报错Operation now in progress,且errno被设置为EINPROGRESS。下面将分析非阻塞时调用connect报错的原因,以及提供两个方法解决connet函数阻塞太久的问题。

    二、connect函数报Operation now in progress的原因

    connect函数报错Operation now in progress,且errno被设置为EINPROGRESS的原因是套接字被设置为非阻塞了。建立TCP连接会涉及到三次握手的过程,connect函数会一直等到收到自己的SYN的ACK为止,所以会阻塞一段时间;如果套接字设置成非阻塞,connect函数会立即返回,但此时已经发起的TCP三次握手仍在进行,所以connect会返回一个EINPROGRESS错误,表示操作正在进行中(Operation now in progress)。

    三、解决connet函数阻塞太久的问题

    三次握手的过程就决定了connect函数是需要阻塞一段时间的,而我们是向避免它阻塞太久了,影响程序执行。下面提供2个可行的方法,仅供参考。

    3.1 设置成非阻塞,且使用select等到连接的建立

    // 接服务器并返回套接字,timeoutMs 毫秒
    int tcpClientConnect(unsigned int u32SerIp, unsigned short port, int timeoutMs)
    {
    	// 1、创建tcp套接字
    	int sockFd = -1;
    	if ((sockFd = socket(PF_INET, SOCK_STREAM, 0)) == -1) 
    	{ 
    		printf("[%s %d] socket failed\n",__FILE__,__LINE__); 
    		return -1;
    	} 
    
    	// 2、socket 设置非阻塞
    	int flags = fcntl(sockFd,F_GETFL,0);
    	if(-1 == fcntl(sockFd,F_SETFL,flags|O_NONBLOCK))
    	{
    		printf("Set socket unblock failed!");
    	}
    
    	// 3、准备服务器地址信息、连接
    	struct sockaddr_in server_addr;; 
    	server_addr.sin_family=PF_INET; 
    	server_addr.sin_port=htons(port); 
    	server_addr.sin_addr.s_addr = u32SerIp; 
    	bzero(&(server_addr.sin_zero), 0); 
        if (connect(sockFd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0)
        {
    		if(errno != EINPROGRESS)
    		{
    			printf("[%s %d] Connect fail!\n",__FILE__,__LINE__);
    			close(sockFd);
            	return -1;
    		}
        }
    
    	// 4、select等待
    	fd_set rset,wset;
    	FD_ZERO(&rset);
    	FD_SET(sockFd,&rset);
    	wset = rset;
    	struct timeval tv;
    	tv.tv_sec = timeoutMs/1000;
    	tv.tv_usec = (timeoutMs%1000)*1000;
    	if(select(sockFd+1, &rset, &wset, NULL, &tv)==0)
    	{
    		printf("[%s %d] select fail!\n",__FILE__,__LINE__);
    		close(sockFd);
        	return -1;		
    	}
    
    	if(FD_ISSET(sockFd,&rset) || FD_ISSET(sockFd,&wset))
    	{
    		int error;
    		socklen_t len = sizeof(error);
    		if(getsockopt(sockFd, SOL_SOCKET, SO_ERROR, &error, &len)<0)
    			return -1;	
    	}
    	
    	printf("[%s %d] Connect pass! %d\n",__FILE__,__LINE__,sockFd);
    	return sockFd;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    3.2 使用套接字选项设置发送超时

    // timeoutMs 毫秒
    int tcpClientConnect(uint32_t u32SerIp, uint16_t port, int timeoutMs)//连接服务器并放回套接字 2022-06-23 20:02:34
    {
    	// 1、创建tcp套接字
    	int sockFd = -1;
    	if ((sockFd = socket(PF_INET, SOCK_STREAM, 0)) == -1) 
    	{ 
    		printf("[%s %d] socket failed\n",__FILE__,__LINE__); 
    		return -2;
    	} 
    
    	// 2、设置 sockFd 超时时间*/
    	if(timeoutMs > 0)
    	{
    	    struct timeval tv;
    		tv.tv_sec = timeoutMs/1000;
    		tv.tv_usec = (timeoutMs%1000)*1000;
    		setsockopt(sockFd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
    	}
    
    	// 3、准备服务器地址信息、连接
    	struct sockaddr_in server_addr;; 
    	server_addr.sin_family=PF_INET; 
    	server_addr.sin_port=htons(port); 
    	server_addr.sin_addr.s_addr = u32SerIp; 
    	bzero(&(server_addr.sin_zero), 0); 
        if (connect(sockFd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0)
        {
    		printf("[%s %d] Connect fail!\n",__FILE__,__LINE__);
    		close(sockFd);
            return -1;
        }
        
    	printf("[%s %d] Connect pass! %d\n",__FILE__,__LINE__,sockFd);
    	return sockFd;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
  • 相关阅读:
    k8s day04
    【LeetCode热题100】--15.三数之和
    基于Promise + XHR 封装myAxios函数
    Maven依赖管理项目构建工具(保姆级教学)
    Flet教程之 11 Row组件在水平数组中显示其子项的控件 基础入门(教程含源码)
    JVM调优-配置参数
    如何完美通过token获取用户信息(springboot)
    【Shell】3万字图文讲解带你快速掌握shell脚本编程
    Shell笔记(超级完整)
    组装式已成大趋势,浅淡下组装式应用
  • 原文地址:https://blog.csdn.net/wkd_007/article/details/125521844