【整理】Socket编程之非阻塞connect(一)

简介:


非阻塞 connect:

      在 TCP socket 被设置为非阻塞的情况下调用 connect ,若没有立即返回成功,则会返回 -1 以及 errno =    EINPROGRESS 的   错误,其表示连接操作正在进行中,但是尚未完成,与此同时 TCP 三次握手操作会同时进行。在这之后,我们可以通过调用 select 来检查这个链接是否建立成功。 

非阻塞 connect 的三种用途:  
  • 可以在 TCP 三次握手的同时做一些其它的处理。connect 操作需要一个往返时间才能完成,从几个毫秒(局域网)到几百毫秒或几秒(广域网)。在这段时间内我们可能有一些其他的处理想要同时执行; 
  • 可以用这种技术同时建立多个连接。在 Web 浏览器中很普遍; 
  • 由于我们使用 select 来等待连接的完成,因此我们可以给 select 设置一个时间限制,从而缩短 connect 的超时时间。在大多数实现中,connect 的超时时间在 75 秒到几分钟 之间(linux 内核中对 connect 的超时限制是 75 秒)。有时候应用程序想要一个更短的超时时间,使用非阻塞 connect 就是一种方法。 

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

注意:当一个套接口出错时,它会被 select 调用标记为既可读又可写。 
非阻塞 connect 有这么多好处,但是处理非阻塞 connect 时会遇到很多【可移植性问题】。 

处理非阻塞 connect 的步骤:  
第一步,创建 socket,返回套接字描述符; 
第二步,调用 fcntl 或 ioctlsocket 把套接口描述符设置成非阻塞; 
第三步,调用 connect 开始建立连接; 
第四步,判断连接是否成功建立: 
    A) 如果 connect 返回 0 ,表示连接成功(服务器和客户端在同一台机器上时就有可能发生这种情况); 
    B) 调用 select 来判定连接建立的是否成功; 
       如果 select 返回 0 ,则表示在 select 的超时时间内未能成功建立连接;我们需要返回超时错误给用户,同时关闭连接,以防止 TCP 三次握手继续进行下去; 
       如果 select 返回大于 0 的值,则说明检测到可读或可写或异常的套接字描述符存在;此时我们可以通过调用 getsockopt 来检测集合中的套接口上是否存在待处理的错误,如果连接建立是成功的,则通过 getsockopt(sockfd,SOL_SOCKET,SO_ERROR,(char *)&error,&len) 获取的 error 值将是 0 ,如果建立连接时遇到错误,则 error 的值是连接错误所对应的 errno 值,比如 ECONNREFUSED,ETIMEDOUT 等。 

============= 
      “读取套接口上的错误”是遇到的【第一个可移植性问题】:如果出现问题,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 在连接建立成功(使套接口描述符可写)或连接建立失败(使套接口描述符既可读又可写)时返回。 

=================== 
目录
相关文章
|
1天前
|
Java 数据安全/隐私保护
深入剖析:Java Socket编程原理及客户端-服务器通信机制
【6月更文挑战第21天】Java Socket编程用于构建网络通信,如在线聊天室。服务器通过`ServerSocket`监听,接收客户端`Socket`连接请求。客户端使用`Socket`连接服务器,双方通过`PrintWriter`和`BufferedReader`交换数据。案例展示了服务器如何处理每个新连接并广播消息,以及客户端如何发送和接收消息。此基础为理解更复杂的网络应用奠定了基础。
|
1天前
|
Java 应用服务中间件 开发者
【实战指南】Java Socket编程:构建高效的客户端-服务器通信
【6月更文挑战第21天】Java Socket编程用于构建客户端-服务器通信。`Socket`和`ServerSocket`类分别处理两端的连接。实战案例展示了一个简单的聊天应用,服务器监听端口,接收客户端连接,并使用多线程处理每个客户端消息。客户端连接服务器,发送并接收消息。了解这些基础,加上错误处理和优化,能帮你开始构建高效网络应用。
|
1天前
|
IDE Java 开发工具
从零开始学Java Socket编程:客户端与服务器通信实战
【6月更文挑战第21天】Java Socket编程教程带你从零开始构建简单的客户端-服务器通信。安装JDK后,在命令行分别运行`SimpleServer`和`SimpleClient`。服务器监听端口,接收并回显客户端消息;客户端连接服务器,发送“Hello, Server!”并显示服务器响应。这是网络通信基础,为更复杂的网络应用打下基础。开始你的Socket编程之旅吧!
|
1天前
|
缓存 监控 Java
Java Socket编程最佳实践:优化客户端-服务器通信性能
【6月更文挑战第21天】Java Socket编程优化涉及识别性能瓶颈,如网络延迟和CPU计算。使用非阻塞I/O(NIO)和多路复用技术提升并发处理能力,减少线程上下文切换。缓存利用可减少I/O操作,异步I/O(AIO)进一步提高效率。持续监控系统性能是关键。通过实践这些策略,开发者能构建高效稳定的通信系统。
|
1天前
|
网络协议 Java Linux
探索Java Socket编程:实现跨平台客户端-服务器通信的奥秘
【6月更文挑战第21天】Java Socket编程示例展示了如何构建跨平台聊天应用。服务器端使用`ServerSocket`监听客户端连接,每个连接启动新线程处理。客户端连接服务器,发送并接收消息。Java的跨平台能力确保代码在不同操作系统上无需修改即可运行,简化开发与维护。
|
1天前
|
Java
Java Socket编程与多线程:提升客户端-服务器通信的并发性能
【6月更文挑战第21天】Java网络编程中,Socket结合多线程提升并发性能,服务器对每个客户端连接启动新线程处理,如示例所示,实现每个客户端的独立操作。多线程利用多核处理器能力,避免串行等待,提升响应速度。防止死锁需减少共享资源,统一锁定顺序,使用超时和重试策略。使用synchronized、ReentrantLock等维持数据一致性。多线程带来性能提升的同时,也伴随复杂性和挑战。
|
1天前
|
安全 Java 网络安全
Java Socket编程教程:构建安全可靠的客户端-服务器通信
【6月更文挑战第21天】构建安全的Java Socket通信涉及SSL/TLS加密、异常处理和重连策略。示例中,`SecureServer`使用SSLServerSocketFactory创建加密连接,而`ReliableClient`展示异常捕获与自动重连。理解安全意识,如防数据截获和中间人攻击,是首要步骤。通过良好的编程实践,确保网络应用在复杂环境中稳定且安全。
|
1月前
Socket编程(头脑清晰,防止过载)
Socket编程(头脑清晰,防止过载)
|
22天前
|
网络协议 Java
Java的Socket编程:TCP/IP与UDP深入探索
Java的Socket编程:TCP/IP与UDP深入探索
21 0
|
5天前
|
网络协议 Java API
【Java】Java Socket编程:建立网络连接的基础
【Java】Java Socket编程:建立网络连接的基础
13 1