网络编程-TCP长连接和短连接
TCP是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。下面会介绍一个TCP连接是如何建立的以及通信结束后是如何终止的。
一、TCP连接的建立与终止
1.1 建立连接协议
- 请求端(通常称为客户)发送一个
SYN
段指明客户打算连接的服务器的端口,以及初始序号(ISN
,在这个例子中为1415531521)。这个SYN
段为报文段1。 - 服务器发回包含服务器的初始序号的
SYN
报文段(报文段2)作为应答。同时,将确认序号设置为客户的ISN
加1以对客户的SYN
报文段进行确认。一个SYN
将占用一个序号。 - 客户必须将确认序号设置为服务器的
ISN
加1以对服务器的SYN
报文段进行确认(报文段3)。
这三个报文段完成连接的建立。这个过程也称为三次握手(three-way handshake)。
1.2 连接终止协议
建立一个连接需要三次握手,而终止一个连接要经过4次握手。这由TCP的半关闭(halfclose)造成的。既然一个TCP连接是全双工(即数据在两个方向上能同时传递),因此每个方向必须单独地进行关闭。这原则就是当一方完成它的数据发送任务后就能发送一个FIN
来终止这个方向连接。当一端收到一个FIN
,它必须通知应用层另一端已经终止了那个方向的数据传送。发送FIN
通常是应用层进行关闭的结果。
当服务器收到这个FIN
,它发回一个ACK
,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN
将占用一个序号。同时TCP服务器还向应用程序(即丢弃服务器)传送一个文件结束符。接着这个服务器程序就关闭它的连接,导致它的TCP端发送一个FIN
(报文段6),客户必须发回一个确认,并将确认序号设置为收到序号加1(报文段7)。
1.3 序列号(seq)和确认号(ACK)
1.3.1 序列号(seq)
序列号(Sequence Number)字段标识了TCP发送端到TCP接收端的数据流的一个字节,该字节代表着包含该序列号的报文段的数据中的第一个字节
1.3.2 初始化序列号(ISN)
- 当建立一个新连接时,从客户机发送至服务器的第一个报文段的SYN位字段被启用。 这样的报文段称为SYN报文段,或简单地称为SYN。然后序列号字段包含了在本次连接的这个方向上要使用的第一个序列号,后续序列号和返回的ACK号也在这个方向上(回想一 下,连接都是双向的)。注意这个数字不是0和1,而是另一个数字,经常是随机选择的,称为初始序列号(ISN)。ISN不是0和1,是因为这是一种安全措施(在后面“TCP连接管理”中介绍)。发送在本次连接的这个方向上的数据的第一个字节的序列号是ISN加1,因为SYN位字段会消耗一个序列号。正如我们稍后将见到的,消耗一个序列号也意味着使用重传进行可靠传输。因此,SYN和应用程序字节(还有FIN,稍后我们将会见到)是被可靠传输的。不消耗序列号的ACK则不是。
- 在发送用于建立连接的SYN之前,通信双方会选择一个初始序列号。初始序列号会随时间而改变,因此每一个连接都拥有不同的初始序列号
- [RFCO793]指出初始序列号可被视为一个32位的计数器。该计数器的数值每4微秒加1。此举的目的在于为一个连接的报文段 安排序列号,以防止出现与其他连接的序列号重叠的情况。尤其对于同一连接的两个不同实例而言,新的序列号也不能出现重叠的情况。
1.3.3 确认号(ACK)
- 确认号字段(也简称ACK号或ACK字段)包含的值是该确认号的发送方期待接收的下一个序列号。即最后被成功接收的数据字节的序列号加1
- 这个字段只有在ACK位字段被启用的情况下才有效,这个ACK位字段通常用于除了初始和末尾报文段之外的所有报文段。发送一个ACK与发送任何一个TCP报文段的开销是一样的,因为那个32位的ACK号字段一直都是头部的一部分,ACK位字段也一样。
二、TCP长连接
-
长连接意味着进行一次数据传输后,不关闭连接,长期保持连通状态。如果两个应用程序之间有新的数据需要传输,则直接复用这个连接无需再建立一个新的连接。
-
长连接的优势是在多次通信中可以省去连接建立和关闭连接的开销,并且从总体上来看,进行多次数据传输的总耗时更少。缺点是需要花费额外的精力来保持这个连接一直是可用的,因为网络抖动、服务器故障等都会导致这个连接不可用,甚至是由于防火墙的原因。所以,一般我们会通过下面这几种方式来做“保活”工作,确保连接在被使用的时候是可用状态:
-
利用 TCP 自身的保活(Keepalive)机制来实现,保活机制会定时发送探测报文来识别对方是否可达。一般的默认定时间隔是2小时,可以根据自己的需要在操作系统层面去调整这个间隔,不管是 linux 还是 windows 系统。
-
上层应用主动的定时发送一个小数据包作为“心跳”,探测是否能成功送达到另外一端。 保活功能大多数情况下用于服务端探测客户端的场景,一旦识别客户端不可达,则断开连接,缓解服务端压力。
-
三、TCP短连接
-
短连接意味着每一次的数据传输都需要建立一个新的连接,用完再马上关闭它。下次再用的时候重新建立一个新的连接,如此反复。
它的优势是由于每次使用的连接都是新建的,所以基本上只要能够建立连接,数据就大概率能送达到对方。并且哪怕这次传输出现异常也不用担心影响后续新的数据传输,因为届时又是一个新的连接。缺点是每个连接都需要经过三次握手和四次握手的过程,耗时大大增加。 -
短连接还有一个致命的缺点。我们回到前面提到的维基百科对 socket 的定义,其中说到 socket 包含通信协议、目标地址、状
态等。实际当你在基于 socket 进行开发的时候,这些包含的具体资源主要就是这 5 个:源 IP、源端口、目的 IP、目的端口、协议,
有个专业的叫法称之为“五元组”。在一台计算机上只要这五元组的值不重复,那么连接就可以被建立。然而一台计算机最多只能开启
65535个端口,如果现在两个进程之间需要通信,作为服务端的 IP 和端口必然是固定的,因此单个客户端理论上最多只能与服务端同时建立65535个 socket 连接。如果除去操作系统和其它进程所占用的端口,实际还会更少。所以,一旦使用不当,在很短的时间内建立了大量连接,端口很容易被占用完。这不但会导致自身无法正常工作,还会影响到同一台计算机上的其它进程。
四、HTTP的长连接和短连接
-
HTTP1.1规定了默认保持长连接(HTTP persistent connection ,也有翻译为持久连接),数据传输完成了保持TCP连接不断开(
不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据;相反的就是短连接。HTTP首部的
Connection: Keep-alive
是HTTP1.0浏览器和服务器的实验性扩展,当前的HTTP1.1 RFC2616文档没有对它做说明,
因为它所需要的功能已经默认开启,无须带着它,但是实践中可以发现,浏览器的报文请求都会带上它。如果HTTP1.1版本的HTTP请求报文不希望使用长连接,则要在HTTP请求报文首部加上Connection: close。 -
长连接的过期时间
客户端的长连接不可能无限期的拿着,会有一个超时时间,服务器有时候会告诉客户端超时时间,譬如:Keep-Alive: timeout=20,
表示这个TCP通道可以保持20秒。另外还可能有max=XXX,表示这个长连接最多接收XXX次请求就断开。对于客户端来说,如果服务器没有告诉客户端超时时间也没关系,服务端可能主动发起四次握手断开TCP连接,客户端能够知道该TCP连接已经无效;另外TCP还有心跳包来检测当前连接是否还活着,方法很多,避免浪费资源。 -
长连接的数据传输完成识别
使用长连接之后,客户端、服务端怎么知道本次传输结束呢?两部分:1是判断传输数据是否达到了Content-Length指示的大小;2动态生成的文件没有Content-Length,它是分块传输(chunked),这时候就要根据chunked编码来判断,chunked编码的数据在最后有一个标明长度为0的chunk块,表示本次传输数据结束。补充:
chunk编码将数据分成一块一块的发生。Chunked编码将使用若干个Chunk串连而成,由一个标明长度为0的chunk标示结束。每个Chunk分为头部和正文两部分,头部内容指定正文的字符总数(十六进制的数字)和数量单位(一般不写),正文部分就是指定长度的实际内容,两部分之间用回车换行(CRLF)隔开。在最后一个长度为0的Chunk中的内容是称为footer的内容,是一些附加的Header信息(通常可以直接忽略)