非阻塞connect用法

在广域网中,connect函数可能需要比较长的时间返回(等待对端发送ack),所以我们通常需要非阻塞connect。下面分别实现windows和linux的非阻塞connect,并作出对比。

先大概总结一下winsock和linux socket用法区别:

  1. winsock需要WSAStartup和WSACleanupleanup进行初始化和反初始化,linux socket不用
  2. 关闭套接字,winsock用closesocket,linux socket用close
  3. winsock fd类型为SOCKET,linux fd类型为int
  4. winsock设置非阻塞用fcntl,linux socket用ioctlsocket
  5. linux下判断函数错误码需要与EINTR比较,winsock不用
  6. winsock select第一个参数无实际意义,只用来保证兼容,linux socket需要传最大fd+1
  7. linux程序需要屏蔽sigpipe信号
  8. winsock错误码为SOCKET_ERROR或INVALID_SOCKET,linux socket为-1
  9. winsock IO复用模型有:select, WSAAsyncSelect,WSAEventSelect,IOCP,linux socketIO复用模型有:select,poll,epoll

demo1,windows非阻塞connect用法:

  1. 套接字设置非阻塞
  2. connect返回0则连接成功,否则判断错误码是否是WSAEWOULDBLOCK
  3. select判断套接字可写,则连接成功
bool nonblockingConnect(const char* ip, short port, int timeout = 3)
{
    SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == INVALID_SOCKET)
    {
        cout << "create socket failed, error: " << WSAGetLastError() << endl;
        return false;
    }

    //设置为非阻塞
    u_long nonBlock = 1;
    int ret = ioctlsocket(fd, FIONBIO, &nonBlock);
    if (ret == SOCKET_ERROR)
    {
        cout << "ioctlsocket failed" << endl;
        closesocket(fd);
        false;
    }

    SOCKADDR_IN srvAddr;
    memset(&srvAddr, 0, sizeof(SOCKADDR_IN));
    srvAddr.sin_addr.s_addr = inet_addr(ip);
    srvAddr.sin_port = htons(port);
    srvAddr.sin_family = AF_INET;

    ret = connect(fd, (PSOCKADDR)&srvAddr, sizeof(SOCKADDR_IN));
    if (ret == 0)
    {
        cout << "connect success" << endl;
        return true;
    }
    else if (ret == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK)
    {
        cout << "can not connect to server, error: " << WSAGetLastError() << endl;
        return false;
    }

    fd_set wfds;
    FD_ZERO(&wfds);
    FD_SET(fd, &wfds);
    timeval tv = { timeout, 0 };

    ret = select(fd + 1, nullptr, &wfds, nullptr, &tv);
    if (ret != 1)
    {
        cout << "can not connect to server";
        return false;
    }
    cout << "connect success" << endl;
    return true;
}

demo2,linux下非阻塞connect用法:

  1. 套接字设置非阻塞
  2. connect,比较错误码,EINPROGRESS表示正在建立连接
  3. select判断fd是否可写
  4. 跟windows不同,winsock用select就能判断connect连接是否建立,而linux下可能存在出错的情况,所以还需要getsocketopt函数判断是否出错,如果没错则连接成功。
bool nonblockingConnect(const char* ip, short port, int timeout = 3)
{
	int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1)
    {
        cout << "create socket failed" << endl;
        return false;
    }

    //设置非阻塞
	int flag = fcntl(fd, F_GETFL, 0);
	if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) == -1)
	{
		cout << "fcntl failed" << endl;
		close(fd);
		return false;
	}

	struct sockaddr_in srvAddr;
	memset(&srvAddr, 0, sizeof(struct sockaddr_in));
	srvAddr.sin_addr.s_addr = inet_addr(ip);
	srvAddr.sin_port = htons(port);
	srvAddr.sin_family = AF_INET;

    //为了处理EINTR,将connect放在循环内
    while (1)
    {
        int ret = connect(fd, (sockaddr*)&srvAddr, sizeof(struct sockaddr_in));
        if (ret == 0)
        {
            cout << "connect successfully" << endl;
            return true;
        }
        else if (ret == -1)
        {
            if (errno == EINTR)
            {
                cout << "signal interrupt" << endl;
                continue;
            }
            else if (errno != EINPROGRESS)
            {
                cout << "can not connect to server, errno: " << errno << endl;
                close(fd);
                return false;
            }
            else
            {
                break;
            }
        }

    }

	fd_set wfds;
    FD_ZERO(&wfds);
    FD_SET(fd, &wfds);
	timeval tv = { timeout, 0 };

	int ret = select(fd + 1, nullptr, &wfds, nullptr, &tv);
	if (ret <= 0)
	{
		cout << "can not connect to server" << endl;
		close(fd);
		return false;
	}

	if (FD_ISSET(fd, &wfds))
	{
		int error;
		socklen_t error_len = sizeof(int);
		ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &error_len);
		if (ret == -1 || error != 0)
		{
			cout << "getsockopt connect failed, errno: " << errno << endl;
			return false;
		}

        /**
        * 在linux下,select返回fd可写,有两种情况:1.连接成功,2.发生错误
        * getsockopt返回error为0则排除错误情况,连接已建立
        */
        cout << "connect successfully" << endl;

        while (1)
        {
            int send_len = 0;
            const char buf[] = "hello\n";

            if ((send_len = send(fd, buf, sizeof(buf), 0)) == -1)
            {
                cout << "send failed";
                return false;
            }
            cout << "send len: " << send_len << endl;
            sleep(3);
        }

        return true;
    }

    cout << "can not connect to server, errno: " << errno << endl;
	close(fd);
	return false;
}

注意:

  • winsock可以用WSAAsyncSelect或WSAEventSelect替代select判断连接是否成功
  • linux可以用poll或epoll判断连接是否成功,poll的优点是不需要getsockopt就能判断连接成功,缺点是不支持跨平台,epoll目前不确定能否直接判断连接成功。
  • 以上代码没有很好的考虑到socket的创建位置和关闭位置,没有考虑超时时间的消耗,也没有处理到所有的错误码,实际开发还需要进一步优化才能使用。

 

根据UNP,linux下判断connect连接成功还有三种方法,用于替代getsockopt:

  1. 调用getpeername,如果没错则连接成功
  2. 以0长度参数调用read,如果read返回0则连接成功
  3. 再次调用connect,如果错误是EISCONN,则表示之前的连接已成功

 

下面还有两个demo用于在linux下判断非阻塞connect连接成功

demo3,linux下通过重新connect判断连接成功demo:

本机虚拟机测试,第一次重connect返回值仍然是EINPROGRESS,第二次才返回EISCONN

//重connect判断连接
//本机虚拟机测试,第一次重connect返回值仍然是EINPROGRESS,第二次才返回EISCONN
bool nonblockingConnect_v2(const char* ip, short port, int timeout = 3)
{
	int fd = socket(AF_INET, SOCK_STREAM, 0);
	if (fd == -1)
	{
		cout << "create socket failed" << endl;
		return false;
	}

	//设置非阻塞
	int flag = fcntl(fd, F_GETFL, 0);
	if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) == -1)
	{
		cout << "fcntl failed" << endl;
		close(fd);
		return false;
	}

	struct sockaddr_in srvAddr;
	memset(&srvAddr, 0, sizeof(struct sockaddr_in));
	srvAddr.sin_addr.s_addr = inet_addr(ip);
	srvAddr.sin_port = htons(port);
	srvAddr.sin_family = AF_INET;

    while (1)
    {
        int ret = connect(fd, (sockaddr*)&srvAddr, sizeof(struct sockaddr_in));
        if (ret == 0)
        {
            cout << "connect successfully" << endl;
            return true;
        }
        else if (ret == -1)
        {
            if (errno == EINTR)
            {
                cout << "signal interrupt" << endl;
                continue;
            }
            else if (errno != EINPROGRESS)
            {
                cout << "can not connect to server, errno: " << errno << endl;
                close(fd);
                return false;
            }
            else
            {
                break;
            }
        }
    }

	fd_set wfds;
	FD_ZERO(&wfds);
	FD_SET(fd, &wfds);
	timeval tv = { timeout, 0 };

    //循环select直到EISCONN,实际使用应设置超时
	while (1)
	{
		int ret = select(fd + 1, nullptr, &wfds, nullptr, &tv);
		if (ret <= 0)
		{
			cout << "can not connect to server" << endl;
			close(fd);
			return false;
		}

		if (FD_ISSET(fd, &wfds))
		{
			//重新connect,如果errno==EISCONN则表明连接已建立
			ret = connect(fd, (sockaddr*)&srvAddr, sizeof(sockaddr_in));
			if (errno == EISCONN)
			{
				cout << "connect successfully" << endl;

                //测试代码,循环发送
                while (1)
                {
                    int send_len = 0;
                    const char buf[] = "hello\n";

                    if ((send_len = send(fd, buf, sizeof(buf), 0)) == -1)
                    {
                        cout << "send failed";
                        return false;
                    }
                    cout << "send len: " << send_len << endl;
                    sleep(3);
                }

				return true;
			}
			else
			{
				cout << "repet while, errno:" << errno << endl;
			}
		}
	}

	cout << "can not connect to server, errno: " << errno << endl;
	close(fd);
	return false;
}

demo4,linux下通过poll判断连接

//poll判断连接
bool nonblockingConnect_v3(const char* ip, short port, int timeout = 3)
{
	int fd = socket(AF_INET, SOCK_STREAM, 0);
	if (fd == -1)
	{
		cout << "create socket failed" << endl;
		return false;
	}

	//设置非阻塞
	int flag = fcntl(fd, F_GETFL, 0);
	if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) == -1)
	{
		cout << "fcntl failed" << endl;
		return false;
	}

	struct sockaddr_in srvAddr;
	memset(&srvAddr, 0, sizeof(struct sockaddr_in));
	srvAddr.sin_addr.s_addr = inet_addr(ip);
	srvAddr.sin_port = htons(port);
	srvAddr.sin_family = AF_INET;

    while (1)
    {
        int ret = connect(fd, (sockaddr*)&srvAddr, sizeof(struct sockaddr_in));
        if (ret == 0)
        {
            cout << "connect successfully" << endl;
            return true;
        }
        else if (ret == -1)
        {
            if (errno == EINTR)
            {
                cout << "signal interrupt" << endl;
                continue;
            }
            else if (errno != EINPROGRESS)
            {
                cout << "can not connect to server, errno: " << errno << endl;
                close(fd);
                return false;
            }
            else
            {
                break;
            }
        }
    }

	struct pollfd pfd;
	pfd.fd = fd;
	pfd.events = POLLOUT | POLLERR;

	int ret = poll(&pfd, 1, timeout * 1000);
	if (ret == 1 && pfd.revents == POLLOUT)
	{
		cout << "connect successfully" << endl;

        //测试代码,循环发送
        while (1)
		{
			int send_len = 0;
			const char buf[] = "hello\n";

			if ((send_len = send(fd, buf, sizeof(buf), 0)) == -1)
			{
				cout << "send failed";
				return false;
			}
			cout << "send len: " << send_len << endl;
			sleep(3);
		} 

		return true;
	}

	cout << "can not connect to server, errno: " << errno << endl;
	close(fd);
	return false;
}

linux非阻塞connect源码

你可能感兴趣的:(网络编程)