connect函数在阻塞和非阻塞模式下的行为

socket使用阻塞模式时,connect函数会阻塞到有明确结果才会返回,如果网络环境较差,可能要等一会,影响体验,

为了解决这个问题,我们使用异步connect技术

  1. 创建socket,将socket设置为非阻塞模式
  2. 调用connect函数,此时无论connect函数是否连接成功,都会立即返回,如果返回-1,不一定表示连接出错,如果此时错误码为EINPROGRESS表示正在尝试连接
  3. 调用select函数,在指定时间内判断该socket是否可写,可写说明连接成功,反之,连接失败
    上述流程代码
#include <sys/types.h> 
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

#define SERVER_ADDRESS  "127.0.0.1"
#define SERVER_PORT     3000
#define SEND_DATA       "helloworld"

int main(int argc, char* argv[])
{
    //1.创建一个socket
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd == -1)
    {
        std::cout << "create client socket error." << std::endl;
        return -1;
    }
	
	//将clientfd设置成非阻塞模式
	int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);
	int newSocketFlag = oldSocketFlag | O_NONBLOCK;
	if (fcntl(clientfd, F_SETFL,  newSocketFlag) == -1)
	{
		close(clientfd);
		std::cout << "set socket to nonblock error." << std::endl;
		return -1;
	}
	
	//2.连接服务器
	struct sockaddr_in serveraddr;
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);
	serveraddr.sin_port = htons(SERVER_PORT);
	for (;;)
	{
		int ret = connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
		if (ret == 0)
		{
			std::cout << "connect to server successfully." << std::endl;
			close(clientfd);
			return 0;
		} 
		else if (ret == -1) 
		{
			if (errno == EINTR)
			{
				//connect 动作被信号中断,重试connect
				std::cout << "connecting interruptted by signal, try again." << std::endl;
				continue;
			} 
			else if (errno == EINPROGRESS)
			{
				//连接正在尝试中
				break;
			} 
			else
			{
				//真的出错了,
				close(clientfd);
				return -1;
			}
		}
	}
	
	fd_set writeset;
	FD_ZERO(&writeset);
	FD_SET(clientfd, &writeset);
	struct timeval tv;
	tv.tv_sec = 3;  
	tv.tv_usec = 0;
	//3.调用select函数判断socket是否可写
	if (select(clientfd + 1, NULL, &writeset, NULL, &tv) == 1)
	{
		std::cout << "[select] connect to server successfully." << std::endl;
	} 
	else 
	{
		std::cout << "[select] connect to server error." << std::endl;
	}

	close(clientfd);
	
	return 0;
}

首先先用nc命令启动一个服务端程序并执行

nc -v -l -n 0.0.0.0 3000

然后运行程序,我用的clion

connect函数在阻塞和非阻塞模式下的行为_网络

把服务端关掉,在重新启动客户端,一看结果,还是

connect函数在阻塞和非阻塞模式下的行为_网络_02

为什么连接不上也会输出同样的结果?原因如下:

  • 在Windows上,一个socket没有建立连接之前,我们用select检测是否可写,是可以得到正确结果的,即不可写;连接成功后在检测,就会变为可写
  • 在Linux上一个socket没有建立连接之前,用select函数检测是否可写,我们也会得到可写的结果,**所以,在Linux上,我们不仅要用select检测socket是否可写还要用getsocketopt检测socket此时是否出错
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT     3000
#define SEND_DATA       "helloworld"

int main(int argc, char* argv[])
{
    //1.创建一个socket
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd == -1)
    {
        std::cout << "create client socket error." << std::endl;
        return -1;
    }

    //将clientfd设置成非阻塞模式
    int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);
    int newSocketFlag = oldSocketFlag | O_NONBLOCK;
    if (fcntl(clientfd, F_SETFL,  newSocketFlag) == -1)
    {
        close(clientfd);
        std::cout << "set socket to nonblock error." << std::endl;
        return -1;
    }

    //2.连接服务器
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);
    serveraddr.sin_port = htons(SERVER_PORT);
    for (;;)
    {
        int ret = connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
        if (ret == 0)
        {
            std::cout << "connect to server successfully." << std::endl;
            close(clientfd);
            return 0;
        }
        else if (ret == -1)
        {
            if (errno == EINTR)
            {
                //connect 动作被信号中断,重试connect
                std::cout << "connecting interruptted by signal, try again." << std::endl;
                continue;
            }
            else if (errno == EINPROGRESS)
            {
                //连接正在尝试中
                break;
            }
            else
            {
                //真的出错了,
                close(clientfd);
                return -1;
            }
        }
    }

    fd_set writeset;
    FD_ZERO(&writeset);
    FD_SET(clientfd, &writeset);
    struct timeval tv;
    tv.tv_sec = 3;
    tv.tv_usec = 0;
    //3.调用select函数判断socket是否可写
    if (select(clientfd + 1, NULL, &writeset, NULL, &tv) != 1)
    {
        std::cout << "[select] connect to server error." << std::endl;
        close(clientfd);
        return -1;
    }

    int err;
    socklen_t len = static_cast<socklen_t>(sizeof err);
    //4.调用getsockopt检测此时socket是否出错
    if (::getsockopt(clientfd, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
    {
        close(clientfd);
        return -1;
    }

    if (err == 0)
        std::cout << "connect to server successfully." << std::endl;
    else
        std::cout << "connect to server error." << std::endl;

    close(clientfd);

    return 0;
}

TCP网络编程的基本流程

Linux与C++11多线程编程(学习笔记)

Linux select函数用法和原理

socket的阻塞模式和非阻塞模式(send和recv函数在阻塞和非阻塞模式下的表现)

connect函数在阻塞和非阻塞模式下的行为

获取socket对应的接收缓冲区中的可读数据量