{{ v.name }}
{{ v.cls }}类
{{ v.price }} ¥{{ v.price }}
websocketprotocol是html5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助http请求完成。
——百度百科
网站上的即时通讯是很常见的,比如网页的qq,聊天系统等。按照以往的技术能力通常是采用轮询、comet技术解决。
http协议是非持久化的,单向的网络协议,在建立连接后只允许浏览器向服务器发出请求后,服务器才能返回相应的数据。当需要即时通讯时,通过轮询在特定的时间间隔(如1秒),由浏览器向服务器发送request请求,然后将最新的数据返回给浏览器。这样的方法最明显的缺点就是需要不断的发送请求,而且通常httprequest的header是非常长的,为了传输一个很小的数据需要付出巨大的代价,是很不合算的,占用了很多的宽带。
缺点:会导致过多不必要的请求,浪费流量和服务器资源,每一次请求、应答,都浪费了一定流量在相同的头部信息上
然而websocket的出现可以弥补这一缺点。在websocket中,只需要服务器和浏览器通过http协议进行一个握手的动作,然后单独建立一条tcp的通信通道进行数据的传送。
websocket同http一样也是应用层的协议,但是它是一种双向通信协议,是建立在tcp之上的。
websocket在建立握手时,数据是通过http传输的。但是建立之后,在真正传输时候是不需要http协议的。
socket其实并不是一个协议,而是为了方便使用tcp或udp而抽象出来的一层,是位于应用层和传输控制层之间的一组接口。
socket是应用层与tcp/ip协议族通信的中间软件抽象层,它是一组接口。在设计模式中,socket其实就是一个门面模式,它把复杂的tcp/ip协议族隐藏在socket接口后面,对用户来说,一组简单的接口就是全部,让socket去组织数据,以符合指定的协议。
当两台主机通信时,必须通过socket连接,socket则利用tcp/ip协议建立tcp连接。tcp连接则更依靠于底层的ip协议,ip协议的连接则依赖于链路层等更低层次。
websocket则是一个典型的应用层协议。
socket是传输控制层协议,websocket是应用层协议。
websocketapi是html5标准的一部分,但这并不代表websocket一定要用在html中,或者只能在基于浏览器的应用程序中使用。
实际上,许多语言、框架和服务器都提供了websocket支持,例如:
以下简要介绍一下websocket的原理及运行机制。
websocket是html5一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在tcp之上,同http一样通过tcp来传输数据,但是它和http最大不同是:
非websocket模式传统http客户端与服务器的交互如下图所示:
图1.传统http请求响应客户端服务器交互图
使用websocket模式客户端与服务器的交互如下图:
图2.websocket请求响应客户端服务器交互图
上图对比可以看出,相对于传统http每次请求-应答都需要客户端与服务端建立连接的模式,websocket是类似socket的tcp长连接的通讯模式,一旦websocket连接建立后,后续数据都以帧序列的形式传输。在客户端断开websocket连接或server端断掉连接前,不需要客户端和服务端重新发起连接请求。在海量并发及客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。
我们再通过客户端和服务端交互的报文看一下websocket通讯与传统http的不同:
在客户端,newwebsocket实例化一个新的websocket客户端对象,连接类似ws://yourdomain:port/path的服务端websocketurl,websocket客户端对象会自动解析并识别为websocket请求,从而连接服务端端口,执行双方握手过程,客户端发送数据格式类似:
清单1.websocket客户端连接报文
get/webfin/websocket/http/1.1host:localhostupgrade:websocketconnection:upgradesec-websocket-key:xqbt3imnzjbyqrinxeflkg==origin:http://localhost:8080sec-websocket-version:13
可以看到,客户端发起的websocket连接报文类似传统http报文,”upgrade:websocket”参数值表明这是websocket类型请求,“sec-websocket-key”是websocket客户端发送的一个base64编码的密文,要求服务端必须返回一个对应加密的“sec-websocket-accept”应答,否则客户端会抛出“errorduringwebsockethandshake”错误,并关闭连接。
服务端收到报文后返回的数据格式类似:
清单2.websocket服务端响应报文
http/1.1101switchingprotocolsupgrade:websocketconnection:upgradesec-websocket-accept:k7djldlooiwig/mopvwfb3y3fe8=
“sec-websocket-accept”的值是服务端采用与客户端一致的密钥计算出来后返回客户端的,“http/1.1101switchingprotocols”表示服务端接受websocket协议的客户端连接,经过这样的请求-响应处理后,客户端服务端的websocket连接握手成功,后续就可以进行tcp通讯了。
在开发方面,websocketapi也十分简单,我们只需要实例化websocket,创建连接,然后服务端和客户端就可以相互发送和响应消息,在下文websocket实现及案例分析部分,可以看到详细的websocketapi及代码实现。
如上文所述,websocket的实现分为客户端和服务端两部分,客户端(通常为浏览器)发出websocket连接请求,服务端响应,实现类似tcp握手的动作,从而在浏览器客户端和websocket服务端之间形成一条http长连接快速通道。两者之间后续进行直接的数据互相传送,不再需要发起连接和相应。
以下简要描述websocket服务端api及客户端api。
websocket服务端在各个主流应用服务器厂商中已基本获得符合jeejsr356标准规范api的支持,以下列举了部分常见的商用及开源应用服务器对websocketserver端的支持情况:
表1.websocket服务端支持
以下我们使用tomcat7.0.5版本的服务端示例代码说明websocket服务端的实现:
jsr356的websocket规范使用javax.websocket.*的api,可以将一个普通java对象(pojo)使用@serverendpoint注释作为websocket服务器的端点,代码示例如下:
清单3.websocket服务端api示例
@serverendpoint("/echo")publicclassechoendpoint{@onopenpublicvoidonopen(sessionsession)throwsioexception{//以下代码省略...}@onmessagepublicstringonmessage(stringmessage){//以下代码省略...}@message(maxmessagesize=6)publicvoidreceivemessage(strings){//以下代码省略...}@onerrorpublicvoidonerror(throwablet){//以下代码省略...}@onclosepublicvoidonclose(sessionsession,closereasonreason){//以下代码省略...}}
代码解释:
上文的简洁代码即建立了一个websocket的服务端,@serverendpoint("/echo")的annotation注释端点表示将websocket服务端运行在ws://[server端ip或域名]:[server端口]/websockets/echo的访问端点,客户端浏览器已经可以对websocket客户端api发起http长连接了。
使用serverendpoint注释的类必须有一个公共的无参数构造函数,@onmessage注解的java方法用于接收传入的websocket信息,这个信息可以是文本格式,也可以是二进制格式。
onopen在这个端点一个新的连接建立时被调用。参数提供了连接的另一端的更多细节。session表明两个websocket端点对话连接的另一端,可以理解为类似httpsession的概念。
onclose在连接被终止时调用。参数closereason可封装更多细节,如为什么一个websocket连接关闭。
更高级的定制如@message注释,maxmessagesize属性可以被用来定义消息字节最大限制,在示例程序中,如果超过6个字节的信息被接收,就报告错误和连接关闭。
注意:早期不同应用服务器支持的websocket方式不尽相同,即使同一厂商,不同版本也有细微差别,如tomcat服务器7.0.5以上的版本都是标准jsr356规范实现,而7.0.2x/7.0.3x的版本使用自定义api(websocketservlet和streaminbound,前者是一个容器,用来初始化websocket环境;后者是用来具体处理websocket请求和响应,详见案例分析部分),且tomcat7.0.3x与7.0.2x的createwebsocketinbound方法的定义不同,增加了一个httpservletrequest参数,使得可以从request参数中获取更多websocket客户端的信息,如下代码所示:
清单4.tomcat7.0.3x版本websocketapi
publicclassechoservletextendswebsocketservlet{@overrideprotectedstreaminboundcreatewebsocketinbound(stringsubprotocol,httpservletrequestrequest){//以下代码省略....returnnewmessageinbound(){//以下代码省略....}protectedvoidonbinarymessage(bytebufferbuffer)throwsioexception{//以下代码省略...}protectedvoidontextmessage(charbufferbuffer)throwsioexception{getwsoutbound().writetextmessage(buffer);//以下代码省略...}};}}
因此选择websocket的server端重点需要选择其版本,通常情况下,更新的版本对websocket的支持是标准jsr规范api,但也要考虑开发易用性及老版本程序移植性等方面的问题,如下文所述的客户案例,就是因为客户要求统一应用服务器版本所以使用的tomcat7.0.3x版本的websocketservlet实现,而不是jsr356的@serverendpoint注释端点。
对于websocket客户端,主流的浏览器(包括pc和移动终端)现已都支持标准的html5的websocketapi,这意味着客户端的websocketjavascirpt脚本具备良好的一致性和跨平台特性,以下列举了常见的浏览器厂商对websocket的支持情况:
表2.websocket客户端支持
客户端websocketapi基本上已经在各个主流浏览器厂商中实现了统一,因此使用标准html5定义的websocket客户端的javascriptapi即可,当然也可以使用业界满足websocket标准规范的开源框架,如socket.io。
以下以一段代码示例说明websocket的客户端实现:
清单5.websocket客户端api示例
varws=newwebsocket(“ws://echo.websocket.org”);ws.onopen=function(){ws.send(“test!”);};ws.onmessage=function(evt){console.log(evt.data);ws.close();};ws.onclose=function(evt){console.log(“websocketclosed!”);};ws.onerror=function(evt){console.log(“websocketerror!”);};
第一行代码是在申请一个websocket对象,参数是需要连接的服务器端的地址,同http协议开头一样,websocket协议的url使用ws://开头,另外安全的websocket协议使用wss://开头。
第二行到第五行为websocket对象注册消息的处理函数,websocket对象一共支持四个消息onopen,onmessage,onclose和onerror,有了这4个事件,我们就可以很容易很轻松的驾驭websocket。