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

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

    image-20210706233125990

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

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

    • 在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对应的接收缓冲区中的可读数据量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值