网络请求的超时原因


网络的抽象表示

操作系统中,所有网络请求的建立和数据传输,在软件层面是使用socket(一般翻译为套接字)来表示,可以把它理解为搭建在网络请求的客户端和服务端之间的一个管道,socket的建立类比管道的搭建,数据的传输类比管道内水的流动。
套接字是基于TCP/IP实现的,它是TCP的接口在编程语言内的抽象,比如在Java语言中就有对应的ServerSocketSocket类,直接使用编程语言抽象的Socket API即可使用TCP服务。

套接字概述

以Java语言的套接字编程为例,网络中的数据被网卡接收,网卡把接收到的数据放到自己的接收队列(ReceiveQueue)中,用户态执行的代码没有办法直接获取网卡的数据,需要通过调用操作系统函数让内核层的代码获取网卡队列的数据,然后再交由用户编写的代码处理。
同样的,用户编写的代码需要向网络中发送数据时,需要通过调用操作系统函数把数据交由内核,然后由内核代码将数据发送至网卡,网卡把待发送的数据放在发送队列(SendQueue)中,等队列满或者内核主动触发发送的指令,再把队列中的数据发送至网络。

上述内容中接收队列(ReceiveQueue)和发送队列(SendQueue)称为操作系统的网络缓冲区。编程语言的Socket API主要就是抽象了网卡和内核的逻辑部分,让应用开发人员使用时不必考虑太多细节。例如可以直接通过SocketsetSendBufferSize(int):void控制某个连接的缓冲区大小,也可以直接调用Writerflush():void让网卡把缓冲区的数据发送至网络。

超时

连接超时

使用Socket编程或者HTTP编程,在建立连接的时候,经常会遇到连接超时(connect timeout)。一般服务端宕机、网络不可达等原因会造成这样的失败。在Scoket中,默认connect(SocketAddress):void连接方式,连接失败时会有异常java.net.ConnectException: Connection timed out: connect。使用带有连接超时参数的连接方式connect(SocketAddress, int):void,在超时时间内未连接成功会抛出异常。

等待超时(Socket读超时)

接收队列ReceiveQueue中用来存放从网络中接收的数据,若是ReceiveQueue没有数据,接收端会一直阻塞,直到有新的数据到来或者异常发生。若是对端机器宕机或者网络断开,接收端的线程仍在阻塞,就会导致连接不能正常关闭。
通过setSoTimeout(int):void设置等待时间,若是在指定时间内ReceiveQueue没有数据产生,就会有SocketTimeoutException异常,此时Socket连接仍是有效的,为保证资源的释放,需要在异常发生时关闭连接。

发送超时(Socket写超时)

Socket的写超时是基于TCP的超时重传。超时重传是TCP保证数据可靠性传输的一个重要机制,其原理是在发送一个数据报文后就开启一个计时器,在一定时间内如果没有得到发送报文的确认ACK,那么就重新发送报文。如果重新发送多次之后,仍没有确认报文,就发送一个复位报文RST,然后关闭TCP连接。首次数据报文发送与复位报文传输之间的时间差大约为9分钟,也就是说如果9分钟内没有得到确认报文,就关闭连接。但是这个值是根据不同的TCP协议栈实现而不同。

如果发送端持续地写出数据,直到SendQueue被填满。如果在SendQueue已满时仍发送数据,则write():void将被阻塞,直到SendQueue有新的空闲空间为止,也就是说直到一些字节传输到了接收者套接字的ReceiveQueue中。如果此时ReceiveQueue队列也已经被填满,所有操作都将停止,直到接收端将一些字节传输到应用程序。

当Socket的write():void发送数据时,如果网线断开、对端机器宕机等,TCP模块会重传数据,最后超时而关闭连接。下次如再调用write():void会导致一个异常而退出。

Socket写超时是基于TCP协议栈的超时重传机制,一般不需要设置发送超时时间,也没有提供这种方法。

而在OKHttp3中提供了基于HTTP的writeTimeout(long, TimeUnit)设置,OKHttp3提供的writeTimeout(long, TimeUnit)并不是设置数据发送到对端的超时时间,而是指在等待发送数据的过程中,可能由于数据一直被加锁导致当前程序无法读取,导致应用层的write()方法阻塞到指定时间而失败。

原文地址:https://www.cnblogs.com/weegee/p/13475864.html