Socket编程之非阻塞connect

connect()函数

头文件:

    #include<sys/types.h>

    #include<sys/socket.h>

声明:

    int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
功能:

    使用套接字sockfd建立到指定网络地址serv_addr的socket连接,参数addrlen为serv_addr指向的内存空间大小,即sizeof(struct sockaddr_in)。

返回值:

   1)成功返回0,表示连接建立成功(如服务器和客户端是同一台机器上的两个进程时,会发生这种情况)

   2)失败返回SOCKET_ERROR,相应的设置errno,通过errno获取错误信息。常见的错误有对方主机不可达或者超时错误,也可能是对方主机没有进程监听对应的端口。

非阻塞connect(non-block mode connect)

套接字执行I/O操作有阻塞和非阻塞两种模式。在阻塞模式下,在I/O操作完成前,执行操作的函数一直等候而不会立即返回,该函数所在的线程会阻塞在这里。相反,在非阻塞模式下,套接字函数会立即返回,而不管I/O是否完成,该函数所在的线程会继续运行。

客户端调用connect()发起对服务端的socket连接,如果客户端的socket描述符为阻塞模式,则connect()会阻塞到连接建立成功或连接建立超时(linux内核中对connect的超时时间限制是75s, Soliris 9是几分钟,因此通常认为是75s到几分钟不等)。如果为非阻塞模式,则调用connect()后函数立即返回,如果连接不能马上建立成功(返回-1),则errno设置为EINPROGRESS,此时TCP三次握手仍在继续。此时可以调用select()检测非阻塞connect是否完成。select指定的超时时间可以比connect的超时时间短,因此可以防止连接线程长时间阻塞在connect处。

select判断规则:

   1)如果select()返回0,表示在select()超时,超时时间内未能成功建立连接,也可以再次执行select()进行检测,如若多次超时,需返回超时错误给用户。

   2)如果select()返回大于0的值,则说明检测到可读或可写的套接字描述符。源自 Berkeley 的实现有两条与 select 和非阻塞 I/O 相关的规则:

        A) 当连接建立成功时,套接口描述符变成 可写(连接建立时,写缓冲区空闲,所以可写)

        B) 当连接建立出错时,套接口描述符变成 既可读又可写(由于有未决的错误,从而可读又可写)

        因此,当发现套接口描述符可读或可写时,可进一步判断是连接成功还是出错。这里必须将B)和另外一种连接正常的情况区分开,就是连接建立好了之后,服务器端发送了数据给客户端,此时select同样会返回非阻塞socket描述符既可读又可写。

        对于Unix环境,可通过调用getsockopt来检测描述符集合是连接成功还是出错(此为《Unix Network Programming》一书中提供的方法,该方法在Linux环境上测试,发现是无效的):

               A)如果连接建立是成功的,则通过getsockopt(sockfd,SOL_SOCKET,SO_ERROR,(char *)&error,&len) 获取的error 值将是0

               B)如果建立连接时遇到错误,则errno 的值是连接错误所对应的errno值,比如ECONNREFUSED,ETIMEDOUT 等

        一种更有效的判断方法,经测试验证,在Linux环境下是有效的

        再次调用connect,相应返回失败,如果错误errno是EISCONN,表示socket连接已经建立,否则认为连接失败。


“读取套接口上的错误”是遇到的【第一个可移植性问题】:如果出现问题,getsockopt 源自 Berkeley 的实现是返回 0 ,等待处理的错误在变量 errno 中返回;但是 Solaris 会让 getsockopt 返回 -1 ,errno 置为待处理的错误。我们对这两种情况都要处理。  

这样,在处理非阻塞 connect 时,在不同的套接口实现的平台中存在的移植性问题。首先,有可能在调用 select 之前,连接就已经建立成功,而且对方的数据已经到来。在这种情况下,连接成功时套接口将既可读又可写,这和连接失败时是一样的。这个时候我们还得通过 getsockopt 来读取错误值。这是【第二个可移植性问题】。  
=============  

移植性问题总结      
1.对于出错的套接口描述符,getsockopt 的返回值源自 Berkeley 的实现是返回 0 ,待处理的错误值存储在 errno 中;而源自 Solaris 的实现是返回 -1 ,待处理的错误存储在 errno 中。(套接口描述符出错时调用 getsockopt 的返回值不可移植)  
2.有可能在调用 select 之前,连接就已经建立成功,而且对方的数据已经到来,在这种情况下,套接口描述符是既可读又可写,这与套接口描述符出错时是一样的。(怎样判断连接是否建立成功的条件不可移植)  

这样的话,在我们判断连接是否建立成功的条件不唯一时,我们可以有以下的方法来解决这个问题:  
1.调用获取对端socket地址的 getpeername 代替 getsockopt 。如果调用 getpeername 失败,getpeername 返回 ENOTCONN ,表示连接建立失败,之后我们必须再以 SO_ERROR 调用 getsockopt 得到套接口描述符上的待处理错误;  
2.调用 read ,读取长度为 0 字节的数据。如果连接建立失败,则 read 会返回 -1 ,且相应的 errno 指明了连接失败的原因;如果连接建立成功,read 应该返回 0 。  
3.再调用一次 connect 。它应该失败,如果错误 errno 是 EISCONN ,就表示套接口已经建立,而且第一次连接是成功的;否则,连接就是失败的。  

被中断的 connect     

如果在一个阻塞式套接口上调用 connect ,在 TCP 的三次握手操作完成之前被中断了,比如说被捕获的信号中断,将会发生什么呢?假定 connect 不会自动重启,它将返回 EINTR 。那么这个时候,我们就不能再调用 connect 等待连接建立完成了,如果再次调用 connect 来等待连接建立完成的话,connect 将会返回错误值 EADDRINUSE 。在这种情况下,应该做的是调用 select ,就像在非阻塞式 connect 中所做的一样。然后 select 在连接建立成功(使套接口描述符可写)或连接建立失败(使套接口描述符既可读又可写)时返回。 

socket api 存在一批核心接口,而这一批核心接口就是几个看似简单的函数,尽管实际上这些函数没有一个是简单。connect 函数就是这些核心接口中的一个函数,它完成主动连接的过程。 

connect 函数的功能对于TCP来说就是完成面向连接的协议的连接过程,它的函数原型:   


linux下

1#include<sys/socket.h>
2#include<sys/types.h>
3int connect(int sockfd, const struct sockaddr* server_addr, socklen_t addrlen)
windows下   
1int connect(
2    SOCKET s,     // 没绑定套接口描述字
3    const struct sockaddr FAR *name,   // 目标地址指针,目标地址中必须包含IP和端口信息。
4    int namelen   // name的长度
5    );

面向连接的协议,在建立连接的时候总会有一方先发送数据(SYN),那么谁调用了 connect 谁就是先发送数据的一方。如此理解 connect 三个参数就容易了。我必需指定数据发送的目的地址,同时也必需指定数据从哪里发送,这正好是 connect 的前两个参数,而第三个参数是为第二个参数服务的。   

参数    sockfd       
指定数据发送的套接字,解决从哪里发送的问题。内核需要维护大量 I/O 通道,所以用户必需通过这个参数告诉内核从哪个 I/O 通道(此处就是从哪个 socket 接口)中发送数据。   

参数    server_addr       
指定数据发送的目的地,也就是服务器端的地址。这里服务器是针对 connect 说的,因为 connect 是主动连接的一方调用的,所以相应的要存在一个被连接的一方,被动连接的一方需要调用 listen 以接受 connect 的连接请求,如此被动连接的一方就是服务器了。    

参数    addrlen       
指定 server_addr 结构体的长度。我们知道系统中存在大量的地址结构,但 socket 接口只是通过一个统一的结构来指定参数类型,所以需要指定一个长度,以使内核在进行参数复制的时候有个界限。    

返回值:没有错误发生,返回0;否则返回 SOCKET_ERROR(-1) 。

=============================================

=============================================

与所有的 socket 网络接口一样,connect 总会在某个时候可能失败,此时它会返回 -1 ,相应的 errno 会被设置,用户可能通过这个值确定是哪个错误。常见的错误有对方主机不可达或者超时错误,也可以是对方主机没有相应的进程在对应端口等待。

connect 函数可用于    面向连接套接字       也可用于    无连接套接字       。   

无连接套接字   :对于无连接的套接字 (SOCK_DGRAM) ,该套接字与目标地址之间建立默认的对应关系,且在本地保存了对端的地址,这样后续的读写操作可以默认以连接的对端为操作对象,网络数据交互发生时可以直接使用 send ,而不是用 sendto 来向该地址发送数据;内核会丢弃所有发送给该套接字的源地址不是 connect 地址的报文。再次调用 connect 函数,若此时 name 和 namelen 两个参数均为空指针,就会将该套接字恢复为未连接状态,再调用 send 函数,系统会提示 WSAENOTCONN 错误码。   

面向连接套接字   :面向连接的套接字 (SOCK_STREAM) ,函数 connect 会引起调用端主动进行 TCP 的三次握手过程。结果通常是成功连接、WSAETIMEDOUT (多次发送SYN报文,始终未收到回复)、WSAECONNREFUSED (目标主机返回 RST) 等。   

  当对端机器 crash 或者网络连接被断开(比如路由器不工作,网线断开等),此时发送数据给对端然后读取本端 socket 会返回 ETIMEDOUT 或者 EHOSTUNREACH 或者 ENETUNREACH (后两个是中间路由器判断服务器主机不可达的情况)。   

  当对端机器 crash 之后又重新启动,然后客户端再向原来的连接发送数据,因为服务器端已经没有原来的连接信息,此时服务器端回送 RST 给客户端,此时客户端读本地端口返回 ECONNRESET 错误。   

  当服务器所在的进程正常或者异常关闭时,会对所有打开的文件描述符进行 close ,因此对于连接的 socket 描述符则会向对端发送 FIN 分节进行正常关闭流程。对端在收到 FIN 之后端口变得可读,此时读取端口会返回 0 表示到了文件结尾(对端不会再发送数据)。    

  当一端收到 RST 导致读取 socket 返回 ECONNRESET ,此时如果再次调用 write 发送数据给对端则触发 SIGPIPE 信号,信号默认终止进程,如果忽略此信号或者从 SIGPIPE 的信号处理程序返回则 write 出错返回 EPIPE 。   

=================================    =====      
1) Broken PIPE 的字面意思是“管道破裂”。Broken PIPE 产生的原因是该管道的读端被关闭。   
2) Broken PIPE 经常发生在 Client 端通过 Socket 发送信息到 Server 端后,就关闭当前 Socket , 之后 Server 端回复信息给 Client 端时。   
3) 发生 Broken PIPE 错误时,调用写的进程会收到 SIGPIPE 信号,默认动作是导致当前进程终止。   
4) Broken PIPE 最直接的意思是:写入端出现的时候,PIPE 的另一端却休息或退出了,因此造成没有及时取走管道中的数据,从而系统异常退出。   

Client 端通过 PIPE 发送信息到 Server 端后,就关闭 Client 端 Socket , 这时 Server 端返回信息给 Client 端时就会产生 Broken PIPE 信号。      

======================================

  可以看出只有当本地端口主动发送消息给对端才能检测出连接异常中断的情况,搭配 select 进行多路分离的时候,socket 收到 RST 或者 FIN 时候,select 返回可读(心跳消息就是用于检测连接的状态)。也可以使用 socket 的 KEEPLIVE 选项,依赖 socket 本身侦测 socket 连接异常中断的情况。   


错误信息:      

EACCES, EPERM:用户试图在套接字广播标志没有设置的情况下连接广播地址或由于防火墙策略导致连接失败。   
EADDRINUSE:本地地址处于使用状态。   
EAFNOSUPPORT:参数 serv_add 中的地址非法。   
EAGAIN:没有足够空闲的本地端口。   
EALREADY:套接字为非阻塞套接字,并且原来的连接请求还未完成。   
EBADF:非法的文件描述符。   
ECONNREFUSED:远程地址并没有处于监听状态。   
EFAULT:指向套接字结构体的地址非法。   
EINPROGRESS:套接字为非阻塞套接字,且连接请求没有立即完成。   
EINTR:系统调用的执行由于捕获中断而中止。   
EISCONN:已经连接到该套接字。   
ENETUNREACH:网络不可到达。   
ENOTSOCK:文件描述符不与套接字相关。   

ETIMEDOUT:连接超时。


转载自:http://my.oschina.net/u/617889/blog/87946

转自:http://blog.csdn.net/nphyez/article/details/10268723

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值