Chinaunix首页 | 论坛 | 博客
  • 博客访问: 294848
  • 博文数量: 119
  • 博客积分: 1235
  • 博客等级: 少尉
  • 技术积分: 1056
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-08 19:59
文章分类

全部博文(119)

文章存档

2018年(2)

2017年(4)

2016年(4)

2015年(43)

2014年(1)

2013年(12)

2012年(16)

2011年(36)

2010年(1)

分类: 其他平台

2017-03-17 16:20:15

建立socket后默认connect()函数为阻塞连接状态,在大多数实现中,connect的超时时间在75s至几分钟之间,想要缩短超时时间,可解决问题的两种方法:

  • 方法一、将socket句柄设置为非阻塞状态。
  • 方法二、采用信号处理函数设置阻塞超时控制。

设置套接字为非阻塞

可以将套接字设置为非阻塞,下面是具体步骤

  1. 建立socket
  2. 将该socket设置为非阻塞模式
  3. 调用connect(),如果返回0,则连接建立;如果返回-1,检查errno ,如果值为 EINPROGRESS,则连接正在建立。
  4. 使用select()检查该socket描述符是否可写(注意,是可写)
  5. 根据select()返回的结果判断connect()结果,如果规定时间内成功建立,则描述符变为可写;否则,采用getsockopt函数捕获错误信息
  6. 将socket设置为阻塞模式(如果你的程序不需要用阻塞模式的,这步就省了,不过一般情况下都是用阻塞模式的,这样也容易管理

非阻塞connect听起来虽然简单,但是仍然有一些细节问题要处理:
1.即使套接口是非阻塞的,如果连接的服务器在同一台主机上,那么在调用connect建立连接时,连接通常会立即建立成功.我们必须处理这种情况;
2.源自Berkeley的实现(和Posix.1g)有两条与select和非阻塞IO相关的规则:
A:当连接建立成功时,套接口描述符变成可写;
B:当连接出错时,套接口描述符变成既可读又可写;
注意:当一个套接口出错时,它会被select调用标记为既可读又可写(由于有未决的错误,从而可读又可写);

第一步:创建socket,返回套接口描述符;

第二步:调用fcntl把套接口描述符设置成非阻塞;
第三步:调用connect开始建立连接;
第四步:判断连接是否成功建立;
A:如果connect返回0,表示连接简称成功(服务器可客户端在同一台机器上时就有可能发生这种情况);
B:调用select来等待连接建立成功完成;
如果select返回0,则表示建立连接超时;我们返回超时错误给用户,同时关闭连接,以防止三路握手操作继续进行下去;
如果select返回大于0的值,则需要检查套接口描述符是否可读或可写;如果套接口描述符可读或可写,则我们可以通过调用getsockopt来得到套接口上待处理的错误(SO_ERROR),如果连接建立成功,这个错误值将是0,如果建立连接时遇到错误,则这个值是连接错误所对应的errno值(比如:ECONNREFUSED,ETIMEDOUT等).

如果select返回非阻塞socket描述符既可读又可写则表明连接出错(注意:这儿必须跟另外一种连接正常的情况区分开来,就是连接建立好了之后,服务器端发送了数据给客户端,此时select同样会返回非阻塞socket描述符既可读又可写,这时可以通过以下方法区分:
  1.调用getpeername获取对端的socket地址.如果getpeername返回ENOTCONN,表示连接建立失败,然后用SO_ERROR调用getsockopt得到套接口描述符上的待处理错误;
  2.调用read,读取长度为0字节的数据.如果read调用失败,则表示连接建立失败,而且read返回的errno指明了连接失败的原因.如果连接建立成功,read应该返回0;
  3.再调用一次connect.它应该失败,如果错误errno是EISCONN,就表示套接口已经建立,而且第一次连接是成功的;否则,连接就是失败的;
  对于无连接的socket类型(SOCK_DGRAM),客户端也可以调用connect进行连接,此连接实际上并不建立类似SOCK_STREAM的连接,而仅仅是在本地保存了对端的地址,这样后续的读写操作可以默认以连接的对端为操作对象。

 1.首先填写套接字结构,包括远程的ip,通信端口如下: */
  struct sockaddr_in serv_addr;
  serv_addr.sin_family=AF_INET;
  serv_addr.sin_port=htons(9999);
  serv_addr.sin_addr.s_addr = inet_addr("58.31.231.255"); //inet_addr转换为网络字节序
  bzero(&(serv_addr.sin_zero),8);

  // 2.建立socket套接字:
  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  {
  perror("socket creat error");
  return 1;
  }
  // 3.将socket建立为非阻塞,此时socket被设置为非阻塞模式
  flags = fcntl(sockfd,F_GETFL,0);//获取建立的sockfd的当前状态(非阻塞)
  fcntl(sockfd,F_SETFL,flags|O_NONBLOCK);//将当前sockfd设置为非阻塞

  /*4. 建立connect连接,此时socket设置为非阻塞,connect调用后,无论连接是否建立立即返回-1,同时将errno(包含errno.h就可以直接使用)设置为EINPROGRESS, 表示此时tcp三次握手仍旧进行,如果errno不是EINPROGRESS,则说明连接错误,程序结束。
  当客户端和服务器端在同一台主机上的时候,connect回马上结束,并返回0;无需等待,所以使用goto函数跳过select等待函数,直接进入连接后的处理部分。*/

  if ( ( n = connect( sockfd, ( struct sockaddr *)&serv_addr , sizeof(struct sockaddr)) ) < 0 )
  {
  if(errno != EINPROGRESS) return 1;
  }

  if(n==0)
  {
  printf("connect completed immediately");
  goto done;
  }
  /* 5.设置等待时间,使用select函数等待正在后台连接的connect函数,这里需要说明的是使用select监听socket描述符是否可读或者可写,如果只可写,说明连接成功,可以进行下面的操作。如果描述符既可读又可写,分为两种情况,第一种情况是socket连接出现错误(不要问为什么,这是系统规定的,可读可写时候有可能是connect连接成功后远程主机断开了连接close(socket)),第二种情况是connect连接成功,socket读缓冲区得到了远程主机发送的数据。需要通过connect连接后返回给errno的值来进行判定,或者通过调用 getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&len); 函数返回值来判断是否发生错误,这里存在一个可移植性问题,在solaris中发生错误返回-1,但在其他系统中可能返回0.我首先按unix网络编程的源码进行实现。如下:*/

  FD_ZERO(&rset);
  FD_SET(sockfd,&rset);
  wset = rset;
  tval.tv_sec = 0;
  tval.tv_usec = 300000;
  int error;
  socklen_t len;

  if(( n = select(sockfd+1, &rset, &wset, NULL,&tval)) <= 0)
  {
  printf("time out connect error");
  close(sockfd);
  return -1;
  }

  If ( FD_ISSET(sockfd,&rset) || FD_ISSET(sockfd,&west) )
  {
  len = sizeof(error);
  if( getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&len) <0)
  return 1;
  }


阅读(491) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~