短连接和长连接

  • 短连接:每次Http请求都会建立Tcp连接,管理容易

  • 长连接:只需要建立一次Tcp连接,以后Http请求重复使用同一个Tcp连接,管理难

长连接与短连接

如上图,短连接是每次请求都会创建一个tcp 连接,结束后关闭连接。长连接是创建一个tcp连接后,后续的请求都使用这个连接。

HTTP1.1规定了默认保持长连接(HTTP persistent connection ,也有翻译为持久连接),数据传输完成了保持TCP连接不断开(不发RST包、不四次挥手),等待在同域名下继续用这个通道传输数据;相反的就是短连接 如果服务器没有告诉客户端超时时间也没关系,服务端可能主动发起四次挥手断开TCP连接,客户端能够知道该TCP连接已经无效;另外TCP还有心跳包来检测当前连接是否还活着,方法很多,避免浪费资源。

在长连接的应用场景下, client 端一般不会主动关闭它们之间的连接, Clientserver 之间的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越多, server 早晚有扛不住的时候,这时候 server 端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致 server 端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的客户端连累后端服务。

长连接和短连接的产生在于 clientserver 采取的关闭策略,具体的应用场景采用具体的策略,没有十全十美的选择,只有合适的选择

应用场景区别:

一般长连接(追求实时性高的场景)用于少数 client-end to server-end 的频繁的通信,例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成 socket 错误,而且频繁的 socket 创建也是对资源的浪费。 而像WEB网站的http服务一般都用短链接(追求资源易回收场景),因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源。

短轮询和长轮询

和短连接和长连接有本质区别。长、短连接是客户端与服务端建立和保持TCP连接的机制;而长、短轮询是指客户端请求服务端,服务端给予应答的方式

  • 短轮询:重复发送Http请求,查询目标事件是否完成,优点:编写简单,缺点:浪费带宽和服务器资源

  • 长轮询:在服务端hold住Http请求(死循环或者sleep等等方式),等到目标时间发生(保持这个请求等待数据到来或者恰当的超时),返回Http响应。优点:在无消息的情况下不会频繁的请求,缺点:编写复杂

短轮询示例

1
2
3
func (a AjaxPollHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
	resp.Write([]byte("hello i am ajax poll."))
}

长轮询示例

1
2
3
4
5
6
7
8
9
10
11
12
func (a LongPollHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
	fmt.Println(time.Now(), ": begin...")
	go func() {
		time.Sleep(5 * time.Second)
		c <- 1
	}()
	fmt.Println(time.Now(), " wait...")
	<-c

	fmt.Println(time.Now(), " end...")
	resp.Write([]byte("hello i am long poll."))
}

WebSocket

与HTTP长连接区别

  • HTTP1.1通过使用 Connection:keep-alive 进行长连接,HTTP 1.1默认进行持久连接。在一次 TCP 连接中可以完成多个 HTTP 请求,但是对每个请求仍然要单独发 headerKeep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。

  • websocket 的长连接,是一个真的全双工,第一次 tcp 链路建立之后,后续数据可以双方都进行发送,不需要发送请求头,并且这个连接会持续存在直到客户端或者服务器端的某一方主动关闭连接,与 HTTP 长连接不同, WebSocket 可以更灵活的控制连接关闭的时机,而不是HTTP协议的 Keep-Alive 一到,服务端立马就关闭(这样很不人性化)。。。

连接建立

建立 WebSocket 连接时,需要通过客户端或者浏览器发出握手请求,请求消息示例如图:

websocket request

服务端返回给客户端的应答消息如图:

websocket response

为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个HTTP请求,这个请求和通常的HTTP请求不同,包含了一些附加头信息,其中附加头信息 “Upgrade: WebSocket” 表明这是一个申请协议升级的HTTP请求。服务器端解析这些附加的头信息,然后生成应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方可以通过这个连接通道自由地传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动关闭连接。

请求消息中的 “Sec-WebSocket-Key” 是随机的,服务器端会用这些数据来构造出一个 SHA-1 的信息摘要,把 “Sec-WebSocket-Key” 加上一个魔幻字符串“258EAFA5-E914- 47DA-95CA-C5AB0DC85B11”。使用SHA-1加密,然后进行BASE-64编码,将结果做为“Sec-WebSocket-Accept”头的值,返回给客户端。

举例

假设我们有一个网页版的及时聊天应用,由于使用 http 协议没法从服务端推送新消息到浏览器。那我们应该如何刷新页面中的消息呢?有如下的方法:

短连接

通过轮询机制,不停的刷新服务器中的数据,每次请求都重新建立连接,结束后在关闭连接。

长连接

由于每个客户端都会不停的刷新数据,我们可以重用我们之前建立的浏览器与服务端之间的连接,第一次建立连接后,以后每次都用这个连接来请求数据。这样,三次握手和四次挥手的次数都会减少,提升了效率。

短轮询

前面的长连接和端连接讲的都是如何建立连接,没有说到服务端接到请求后应该如何处理。

短轮询的方式就是根据当前的状态返回相应的信息。比如页面聊天系统中,可能就是返回客户端当前时刻应该收到的所有信息。

这样做的好处是,处理比较简单。但是客户端收到的数据绝大部分都是没有用的,比如,很长时间对方都没有回消息,那么这期间收到的信息都应该是相同的。

长轮询

为了避免短轮询时可能出现的无效数据过多的问题,我们可以采用长轮询的方式。

客户端发起请求,服务端收到请求后并不返回,而是等到有需要返回的数据的时候在返回。

比如:

  1. 客户端发起请求,通知服务端,我上一次收到的消息是时间 t 收到的,我先在要获取后续的记录。
  2. 服务端收到请求后,查找有没有 t 时间之后的消息,如果有则直接返回,如果没有则等待,直到有新消息发过来,或者请求超时的时候在返回。
  3. 客户端收到服务端返回后,如果是失败则重新在发起请求,如果是成功则发起新的请求,通知服务端自己最后一次获取到消息的时间。

这样的好处在于,只有在服务端有数据的时候才返回。较少了短轮询时频繁请求,且大多是无用数据的问题。

但是这里长轮询其实也是有一些问题的,比如,服务端如果一直都没有数据需要更新,那么服务端将会长时间的hold 住当前的连接。一般http请求都是有超时时间的,常会出现过了超时时间还没有返回的情况。如果 http 连接没有设置超时,那么当前连接将一直存在不会被释放。

小结

上面总结了几种不同的刷新方式,长、短连接和长、短轮询是从不同侧面对 http 请求的描述。还可以组合来使用,比如上面的场景可以使用 长连接 + 长轮询 的方式等。

每种方式都有自己的适合场景,应用应该根据自己的场景选择合适的方式。

References