HTTP协议
HTTP协议定义服务器端和客户端之间文件传输的沟通方式。目前比较常用的HTTP协议版本是Http1.0/1.1
HTTP协议的客户端和服务器端的通信方式
在1.0模式下,当客户机向服务器请求一个文件时,首先要建立一条TCP连接,在TCP连接好后,客户机就 向服务器发送URL,让服务器根据URL去寻找相应的文件,找到文件后就从已建 立好的TCP通道上发给客户机,文件发送完毕即拆除TCP连接。当要寻找另一个 文件时,需要重复上述过程。
1.1模式在传输上针对1.0的变化主要在于多次请求和响应可以在一条连接上完成,不必每次请求建立一个新的连接。HTTP 1.1支持持久连接,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟。一个包含有许多图像的网页文件的多个请求和应答可以在一个连接中传输,但每个单独的网页文件的请求和应答仍然需要使用各自的连接。HTTP 1.1还允许客户端不用等待上一次请求结果返回,就可以发出下一次请求,但服务器端必须按照接收到客户端请求的先后顺序依次回送响应结果,以保证客户端能够区分出每次请求的响应内容。
HTTP 1.1传输方式
传统的webIM模式:如同很多门户网站的文字直播,客户端定期向服务端发送请求,服务端响应请求并返回数据,客户端刷新,显示数据。这种方式的实时性较差。
基于Server Push的Comet技术:Comet使用长连接,实现实时的服务器-客户端数据推送。Comet的实现可以有两种方式,Streaming和Long-Polling。Streaming方式建立连接后,两端均不断开,使用此连接实时传输消息。Long-Polling方式一旦完成数据接收,即断开当前连接并重新建立新连接。二者相比Streaming性能最优,但即使是Long-Polling,不管是服务端负载还是对网络带宽的使用,也大大优于传统的Polling。Long-Polling长连接过程如下图
长连接无外乎保持一个维持时间较长的http连接管道,这在HTTP1.1中成为可能,在1.1版本中,要终止次连接必须由服务端接收到客户端发出的含有包含类似close信息的请求,服务端接收到此请求之后切断连接,而如果服务器要主动切断连接的话,也应该向客户端发出类似的response后再执行断开操作。
长连接思想在现有机制适应上的一些缺点:
由于HTTP协议并不是针对长连接设置的,尤其是在1.0版本中,用户发出一个get/post请求后,当服务器将所需资源返回,一个连接即被终止。1.1版本的多次请求共用一个管道实际上是为了减少多次请求小数据时建立销毁连接所造成的性能损失而制定的,并不是真正意义上的长连接。
HTTP1.1标准中有如下规定:“A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy”,并且此规则被包括IE和FF在内的绝大多数浏览器遵守。因此,当comet占用一个HTTP长连接,将可能导致浏览器不能为ajax请求创建新的连接,比如浏览器正在读取大量图片时。
一些防火墙对于较长时间的链接会强行终止。
维持较多的连接对服务端性能要求极高。
基于以上几点,可以发现,Long-Polling方式在兼容性上具有其优势性,但是性能上来说,会出现对连接的频繁建立和销毁。因此,现在流行的解决方式是采用erlang语言对于大并发良好的性能特点来弥补服务端维持数量众多长连接所带来的性能问题。
Erlang并行编程:
典型的多线程的建立如以下代码所示:
-module(tut14).
-export([start/0, say_something/2]).
say_something(What, 0) ->
done;
say_something(What, Times) ->
io:format("~p~n", [What]),
say_something(What, Times - 1).
start() ->
spawn(tut14, say_something, [hello, 3]),
spawn(tut14, say_something, [goodbye, 3])
在上述代码中我们在start函数中建立了两个线程,这两个线程分别输出hello和good bye各三次。
调用start()函数,输出结果如下:
9> tut14:start().
hello
goodbye
<0.63.0>
hello
goodbye
hello
goodbye
此外,erlang允许线程间通信。
-module(tut15).
-export([start/0, ping/2, pong/0]).
ping(0, Pong_PID) ->
Pong_PID ! finished,
io:format("ping finished~n", []);
ping(N, Pong_PID) ->
Pong_PID ! {ping, self()},
receive
pong ->
io:format("Ping received pong~n", [])
end,
ping(N - 1, Pong_PID).
pong() ->
receive
finished ->
io:format("Pong finished~n", []);
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
pong()
end.
start() ->
Pong_PID = spawn(tut15, pong, []),
spawn(tut15, ping, [3, Pong_PID]).
在以上代码中,首先为pong函数建立一个线程,当pong执行到receive时,被阻塞,之后,执行ping线程,并传入参数3和pong线程的id,通过Pong_PID ! {ping, self()},语句,ping线程向pong线程发送了一条信息,内容为ping和ping线程的id,pong线程接收到信息以后,去receive中匹配信息,执行io:format("Pong received ping~n", []),在终端 输出Pong received ping。之后执行Ping_PID ! pong,向ping线程发送消息pong,之后调用自身重新回到阻塞状态接收数据。处在阻塞中的ping线程接收到pong消息以后,执行io:format("Ping received pong~n", []),向终端输出Ping received pong,并执行ping(N - 1, Pong_PID).递归调用自身。直到N – 1(N原始值为3)为0时,执行ping(0, Pong_PID)方法,传递给pong一个finished消息,并且结束递归,而pong接收到finished消息后,调用io:format("Pong finished~n", []),之后终止。输出结果如下:
Pong received ping
Pingreceived pong
Pong received ping
Pingreceived pong
Pong received ping
Pingreceived pong
ping finished
Pong finished
此外,考虑服务器频繁数据传输的处理,对于客户端来说,AJAX技术已经提供了一套非常成熟的解决方案
AJAX利用局部刷新机制对于从服务端返回的信息进行刷新,避免了频繁的页面刷新对于服务端和网络所带来的负担。
综上所述,对所采用技术和需求进行映射,得出下表:
Long-Polling
长连接兼容性
Erlang/MochiWeb
大并发连接
AJAX
前端刷新