写了一个聊天软件,带超时的connect功能哦

322 篇文章 11 订阅
147 篇文章 46 订阅
本文通过一个生动的比喻介绍了如何在局域网的聊天软件中实现TCP连接超时功能,避免无响应的等待。文章分别展示了Windows和Linux环境下,使用非阻塞socket和select函数设置connect超时的代码实现,并强调了在网络编程中实践的重要性。同时,作者建议读者通过阅读Redis等开源代码加深理解,并提醒注意不同操作系统中select函数参数的区别。
摘要由CSDN通过智能技术生成

大家好,我是涛哥。

最近,写了一个简单的聊天软件,在局域网内玩得很溜,涉及到网络编程,其中一个场景是要实现超时connect功能。什么意思呢?我来举个例子,你就明白了。

一个男孩想追求一个女孩,但这个女孩迟迟不响应,男孩却默默傻傻等待,直到地老天荒。然而,现实情况是,很多男孩耐心有限,最多等三年,过期就不等了。

涛哥手绘

在网络编程中也是如此,默认情况下,建立TCP连接的connect是阻塞的,如果对方无回应,则会一直等待。那么,怎样才能给connect动作设置超时时间呢?

思路是:把socket改为非阻塞socket, 然后用select函数来监控socket相关的事件。我曾看过不少开源代码的网络模块的实现,基本上都是采用这种方式。

Windows版本的实现

我们来看下Windows版本的实现,客户端完整代码如下,请重点关注connect相关的代码:

#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
 
int main()
{
  // 网络初始化
  WORD wVersionRequested;
  WSADATA wsaData;
  wVersionRequested = MAKEWORD(2, 2);
  WSAStartup( wVersionRequested, &wsaData );
 
  // 创建客户端socket(默认为是阻塞socket)
  SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
 
  // 设置为非阻塞的socket
  int iMode = 1;
  ioctlsocket(sockClient, FIONBIO, (u_long FAR*)&iMode); 
 
  // 定义服务端
  SOCKADDR_IN addrSrv;
  addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
  addrSrv.sin_family = AF_INET;
  addrSrv.sin_port = htons(8888);
 
  // 超时时间
  struct timeval tm;
  tm.tv_sec  = 3;
  tm.tv_usec = 0;
  int ret = -1;
  
 
  // 尝试去连接服务端
  if (-1 != connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
  {
    ret = 1; // 连接成功
  }
  else
  {
    fd_set set;
    FD_ZERO(&set);
    FD_SET(sockClient, &set);
 
    if (select(-1, NULL, &set, NULL, &tm) <= 0)
    {
      ret = -1; // 有错误(select错误或者超时)
    }
    else
    {
      int error = -1;
      int optLen = sizeof(int);
      getsockopt(sockClient, SOL_SOCKET, SO_ERROR, (char*)&error, &optLen); 
      if (0 != error)
      {
        ret = -1; // 有错误
      }
      else
      {
        ret = 1;  // 无错误
      }
    }
  }
 
  // 设回为阻塞socket
  iMode = 0;
  ioctlsocket(sockClient, FIONBIO, (u_long FAR*)&iMode); //设置为阻塞模式
 
  // connect状态
  printf("ret is %d\n", ret);
 
  // 发送数据到服务端测试一下
  if(1 == ret)
  {
    send(sockClient, "hello world", strlen("hello world") + 1, 0);
  }
 
  // 释放网络连接
  closesocket(sockClient);
  WSACleanup();
 
  return 0;
}

经测试,当客户端去连接服务端时,如果3秒内没有响应,那么客户端就会超时,不再傻傻等待了。

Linux版本的实现

接下来,我们看Linux版本的实现,客户端的代码为:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <time.h>
 
int main(int argc, char *argv[]) // 注意输入参数, 带上ip和port
{
  int sockClient = socket(AF_INET, SOCK_STREAM, 0);
 
  struct sockaddr_in addrSrv;
  addrSrv.sin_addr.s_addr = inet_addr(argv[1]);
  addrSrv.sin_family = AF_INET;
  addrSrv.sin_port = htons(atoi(argv[2]));
 
  fcntl(sockClient, F_SETFL, fcntl(sockClient, F_GETFL, 0)|O_NONBLOCK);  
  
  int iRet = connect(sockClient, ( const struct sockaddr *)&addrSrv, sizeof(struct sockaddr_in));
  printf("connect iRet is %d, errmsg:%s\n", iRet, strerror(errno)); // 返回-1不一定是异常
 
  if (iRet != 0)  
  {  
    if(errno != EINPROGRESS)
    {
      printf("connect error:%s\n", strerror(errno));  
    }
    else  
    {
      struct timeval tm = {3, 0};  
      fd_set wset, rset;  
      FD_ZERO(&wset);  
      FD_ZERO(&rset);  
      FD_SET(sockClient, &wset);  
      FD_SET(sockClient, &rset); 
      int time1 = time(NULL);
      int n = select(sockClient + 1, &rset, &wset, NULL, &tm);  
      int time2 = time(NULL);
      printf("time gap is %d\n", time2 - time1);
 
      if(n < 0)  
      {  
         printf("select error, n is %d\n", n);  
      }  
      else if(n == 0)  
      {  
         printf("connect time out\n");  
      }  
      else if (n == 1)  
      {
         if(FD_ISSET(sockClient, &wset))  
         {  
           printf("connect ok!\n");  
           fcntl(sockClient, F_SETFL, fcntl(sockClient, F_GETFL, 0) & ~O_NONBLOCK);  
         }  
         else  
         {  
           printf("unknow error:%s\n", strerror(errno));  
         }  
      }
      else
      {
        printf("oh, not care now, n is %d\n", n);
      }
    }  
  }  
 
  printf("I am here!\n");
  getchar();
  close(sockClient);
  return 0;
}

经测试,当客户端去连接服务端时,如果3秒内没有响应,那么客户端就会超时,不再傻傻等待了。

发散思考和解释

我们注意到,Linux代码更加简洁明了,没有那些烦人的网络初始化和cleanup操作。我更爱Linux.

有一个重要的问题需要注意:在Windows和Linux中,select函数的第一个参数含义是不一样的哦。

在Windows中,一般默认填写-1就行;而在Linux中,需要设置为fdmax + 1, 这又是为什么呢?

看Linux select第一个参数的含义:待测试的描述集的总个数,而待测试描述集是从0,1,2开始。 

假如你要检测的描述符为8,9,10,那么系统实际也要监测0,1,2,3,4,5,6,7这些描述符。

此时,待测试描述符的个数为11,也就是max(8,9,10) + 1,所以,select首参为:fdmax + 1.

计算机网络是一门实践性很强的学科,在学习计算机网络和网络编程时,建议多写程序、多调试、多抓包,然后对照理论来分析。

我对网络这块比较熟悉,写过大量的网络程序,看过不少网络开源代码,建议大家有空去看看Redis的源码,短小精悍,棒棒哒。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值