socket编程中的超时设置示例详解之一

来源:互联网 发布:怎么找文献的数据 编辑:程序博客网 时间:2024/05/01 10:01
简介:
在网络通讯中,如果socket处于阻塞模式运行时,就需要考虑处理socket操作超时的问题。


所谓阻塞模式,是指其完成指定的操作之前阻塞当前的进程或线程,直到操作有结果返回.
在我们直接调用socket操作函数时,如果不进行特意声明的话,它们都是工作在阻塞模式的,
如 connect, send, recv等.


更多关于阻塞/非阻塞,同步/异步的讲解可以参见我总结的相关专题文章:
http://blog.csdn.net/fireroll/article/details/8998715
http://blog.csdn.net/fireroll/article/details/9008335
http://blog.csdn.net/fireroll/article/details/9008359

简单分类的话,可以将超时处理分成两类:
连接(connect)超时;
发送(send), 接收(recv)超时;
下面对这两类超时一一做示例讲解

一、连接(connect)超时
基本实现流程如下:
1.建立socket;
2.将该socket设置为非阻塞(Non-blocking)模式;
3.调用connect();
   正常情况下,因为TCP三次握手需要一些时间;
   而非阻塞调用只要不能立即完成就会返回错误,
   所以这里会返回EINPROGRESS,表示在建立连接但还没有完成。
4. 在读套接口描述符集(fd_set readset)和写套接口描述符集(fd_set writeset)中
   将当前套接口置位(用FD_ZERO()、FD_SET()宏);
   并设置好超时时间(struct timeval *timeout);
    
   如果你设置的超时时间大于75秒就没有必要这样做了,因为内核中对connect有超时限制就是75秒。


5.使用select()检查该socket描述符是否可写(注意,是可写);
6.根据select()返回的结果判断connect()结果
   返回0表示connect超时;


7.将socket设置为阻塞模式;
   如果你的程序不需要用阻塞模式的,这步就省了,
   不过一般情况下都是用阻塞模式的,这样也容易管理;


下面是示例代码的实现:
/*
 * \brief
 * tcp client
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <ctype.h>
#include <netdb.h>
#include <string.h>

#define TRUE  1
#define FALSE 0


#define SERVPORT 8080
#define MAXDATASIZE 100


#define TIMEOUT_TIME 10


int main(int argc, char *argv[])
{
  int sockfd, recvbytes;
  char rcv_buf[MAXDATASIZE]; /*./client 127.0.0.1 hello */
  char snd_buf[MAXDATASIZE];
  struct hostent *host; /* struct hostent
                                     * {
                                     * char *h_name; // general hostname
                                     * char **h_aliases; // hostname's alias
                                     * int h_addrtype; // AF_INET
                                     * int h_length; 
                                     * char **h_addr_list;
                                     * };
                                     */
  struct sockaddr_in server_addr;


  /* */
  fd_set readset, writeset;
  struct timeval timeout;
  unsigned long ul = 1;
  int error = -1, len = sizeof(int);
  int bTimeoutFlag = FALSE;
  int ret;


  if (argc < 3)
  {
    printf("Usage:%s [ip address] [any string]\n", argv[0]);
    return 1;
  }
  *snd_buf = '\0';
  strcat(snd_buf, argv[2]);


  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  {
    perror("socket:");
    exit(1);
  }


  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(SERVPORT);
  inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
  memset(&(server_addr.sin_zero), 0, 8);


  /*Setting socket to non-blocking mode */
  ioctl(sockfd, FIONBIO, &ul);


  /* create the connection by socket 
   * means that connect "sockfd" to "server_addr"
   */
  if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
  {
    timeout.tv_sec  = TIMEOUT_TIME;
    timeout.tv_usec = 0;
    FD_ZERO(&writeset);
    FD_SET(sockfd, &writeset);


    ret = select(sockfd+1, NULL, &writeset, NULL, &timeout);
    if (ret == 0)              //返回0,代表在描述词状态改变已超过timeout时间
    {
      getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
      if (error == 0)          // 超时,可以做更进一步的处理,如重试等
      {
        bTimeoutFlag = TRUE;
        printf("Connect timeout!\n");
      }
      else
      {
        printf("Cann't connect to server!\n");
      }
      goto end;
    }
    else if ( ret == -1)      // 返回-1, 有错误发生,错误原因存在于errno
    {
      printf("Cann't connect to server!\n");
      goto end;
    }
    else                      // 成功,返回描述词状态已改变的个数
    {
      printf("Connect success!\n");
    }
  }
  else
  {
    printf("Connect success!\n");
    ret = TRUE;
  }


  ul = 0;
  ioctl(sockfd, FIONBIO, &ul); //重新将socket设置成阻塞模式



  /* 同步阻塞模式,未设置超时 */
  if (send(sockfd, snd_buf, sizeof(snd_buf), 0) == -1)
  {
    perror("send:");
    exit(1);
  }
  printf("send:%s\n", snd_buf);


  if ((recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, 0)) == -1)
  {
    perror("recv:");
    exit(1);
  }


  rcv_buf[recvbytes] = '\0';
  printf("recv:%s\n", rcv_buf);


end:
  close(sockfd);
  return 0;
}




以上代码,仅供参考,也是为初学者提供一些提示;
主要用到的几个函数,select, ioctl, getsockopt都可以找到相关资料;


需要再说明的是:
1. 虽然用ioctl把套接口设置为非阻塞模式,但select本身是阻塞的,
   阻塞的时间就是其超时的时间,
   由调用select 的时候的最后一个参数timeval类型的变量指针指向的timeval结构变量来决定的,
   timeval结构由一个表示秒数的和一个表示微秒数(long类型)的成员组成,
   一般我们设置了秒数就行了,把微妙数设为0(注:1秒等于100万微秒)。


2. select函数中另一个值得一提的参数就是上面我们用到的fd_set类型的变量指针。
   调用之前,这个变量里面存了要用select来检查的描述符,
   调用之后,针对上面的程序这里面是可写的描述符,我们可以用宏FD_ISSET来检查某个描述符是否在其中。
   由于这里只有一个套接口描述符,就没有使用FD_ISSET宏来检查调用select之后这个sockfd是否在set里面,
   其实是需要加上这个判断的。
   
   不过这里用了getsockopt来检查,这样才可以判断出这个套接口是否是真的连接上了,
   因为我们只是变相的用select来检查它是否连接上了,


实际上select检查的是它是否可写,而对于可写,是针对以下三种条件任一条件满足时都表示可写的:
1) 套接口发送缓冲区中的可用控件字节数大于等于套接口发送缓冲区低潮限度的当前值,
    且或者
    i) 套接口已连接,或者
    ii)套接口不要求连接(UDP方式的)
2) 连接的写这一半关闭。
3) 有一个套接口错误待处理。


这样,我们就需要用getsockopt函数来获取套接口目前的一些信息来判断是否真的是连接上了,
没有连接上的时候还能给出发生了什么错误,
当然我程序中并没有标出那么多状态,只是简单的表示可连接/不可连接。


下面谈谈对这个程序测试的结果。这里针对3种情形做了测试:
1. 目标机器网络正常的情况
   可以连接到目标主机,并能成功以阻塞方式进行发包收包作业。
2.目标机器网络断开的情况
   在等待设置的超时时间(上面的程序中为10秒)后,显示目标主机不能连接。
3.程序运行前断开目标机器网络,超时时间内,恢复目标机器的网络
   在恢复目标主机网络连接之前,程序一只等待;
   恢复目标主机后,程序显示连接目标主机成功,并能成功以阻塞方式进行发包收包作业。

   以上各种情况的测试结果表明,这种设置connect超时的方法是完全可行的。


九五,飞龙在天,利见大人。

【白话】九五,龙飞上了高空,利于出现德高势隆的大人物。

 

《象》曰:“飞龙在天”,大人造也。

【白话】《象辞》说:“龙飞上了高空”,象征德高势隆的大人物一定会有所作为。