1、WebSocket基础与应用系列(二)Engine.IO 原理了解1. WebSocket. Engine。. Socket.IO 之间的关系WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数 据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要 完成一次握手,两者之间就直接可以创立持久性的连接,并进行双向数据传输。Socket.IO 在 Socket.10 server (Node.js)和 Socket.IO client ( browser, Node.js, or anothe
2、r programming language )之间,基于 WebSocket (不支持 WebSocket 的情况下,退化成 long-polling )建立一条全双工实时通信通道.Engine.IO是一个Socket.IO的抽象实现,作为Socket.IO的服务器和浏览器之间交换的数据的传输层。 它不会取代Socket.IO,它只是抽象出固有的复杂性,支持多种浏览器,设备和网络的实时数据交换。Engine.IO 使用了 Websocket和 long-polling方式封装了一套socket协议。为了兼容不支持 Websocket 的低版本浏览器,使用长轮询(polling )替代WebS
3、ocketoSocketiO- aotomatc ro(xinct)cn- packet buttering- dcknowleOgments- broadcasting to ail c*nts or to a subset of eftonts (wtiat cafl FoorrT)- mutUpiQxing (what wv caflEngine.IO- the various transports and ihe upgrade mechanism- the disconnection defect tonWeOSodcet long-polling2. Engine.IO支持的功能E
4、ngine.IO负责在服务器和客户端之间建立底层连接。包括以下功能:多种传输通道及升级机制断连检测21,传输通道现在主要有2种传输通道实现 server sends: 4HelloWorldclient receives and calls callback socket.on(message, function (data) console.log(data); );example 2client sends: 4HelloWorldserver receives and calls callback socket.on(message, function (data) console.lo
5、g(data); );5 upgrade在engine.io切换传输通道之前,它测试服务器和客户端是否可以通过该传输进行通信。如果此测试成功, 客户端将发送一个升级包,请求服务器刷新旧传输上的缓存,并切换到新传输通道。6 noop一个noop包。主要用于建立websocket连接之后关闭长轮询。exampleclient connects through new transportclient sends 2probeserver receives and sends 3probeclient receives and sends 5server flushes and closes old
6、transport and switches to new.7 .3.2 PayloadPayload是捆绑在一起的一系列encoded packetso Payload编码格式如下:xlexle数据包分割符使用 record separator (xle). 更多可参考: s:/en.wikipedia.Org/wiki/C0_and_Cl_control_codes#Field_separators当有效负载中包含二进制数据时,它将作为base64编码字符串发送。为了解码的目的,将标识符b置于包 含二进制数据的分组编码之前。可以发送任意数量的字符串和base64编码字符串的组合。下面是ba
7、se 64 编码消息的例如:xleb.Payload用于不支持帧的传输通道,例如轮询协议。不包含二进制的例子:type : message,data: hello , type: message, data: ) 编码后:4helloxle4包含二进制的例子: type: message data: 匕 type: message, data: buffer ) 编码后:4xlebAQIDBA=分解:4=message packet type xle=record separatorb=indicates a base64 packetAQIDBA= =buffer content encod
8、edin base641.4 传输通道engine.io server必须支持三种传输通道:websocket server-sent events (SSE)polling jsonpxhrPolling轮询传输包括客户端向服务器发送周期性GET请求以获取数据,以及将带有有效负载的请求从客户端发送到 服务器以发送数据。XHR服务器必须支持CORS响应。JSONP服务器实现必须使用有效的JavaScript进行响应。在响应中需要使用URL中query中的j参数。j是一 个整数。JSONP数据包的格式。eio ( );为了确保payload得到正确处理,需要对payload进行转义,使得响应体是
9、一个合法的JavaScript服务器返回的JSONP数据帧的例子eio4(packet data);Posting data客户端通过隐藏的iframe发送数据。数据以URI编码格式发送给服务器,如下所示d=除了常规的qs转义之外,为了防止浏览器处理的不一致,n在被POSTd之前将被转义为n。1.4.1 Server-sent events客户端使用Eventsource对象接收数据,使用XML Request对象发送数据。1.4.2 WebSocket上面的对payloads的编码方式并不用于WebSocket通道,WebSocket通道本身已有轻量级的数据帧机 制。发送消息的时候,对数据包
10、进行单独编码,然后依次调用send ()进行发送。1.5 传输通道升级连接总是以轮询(XHR或JSONP)开始。WebSocket通过发送探针在侧面进行测试(2probe)o如果探测 由服务器响应(3probe),那么客户端会发送一个升级包(5)。为了确保没有消息丧失,只有在刷新现有传输的所有缓冲区并认为传输已暂停后,才会发送升级数据包。当服务器收到升级包时,它必须假定这是新的传输通道,并将所有现有缓冲区(如果有的话)发送给它。客户端发送的探测器是一个ping + probe作为数据发送。(2probe)服务端发送的探测器是一个 pong + probe 作为数据发送。(3probe)Time
11、outs客户端必须使用握手中发送的pingTimeout和pinginterval来确定服务器是否无响应。服务器发送一个ping数据包。如果在pingTimeout内未收到任何数据包类型,服务器将认为套接字已断 开连接。如果收到了 pong数据包,服务器将在等待pinginterval之后再次发送ping数据包。由于这两个值在服务器和客户端之间共享,当客户端在pingTimeout + pinglnterval内没有接收到任何数据 时,客户端也能探测到服务器是否变得无响应。4 一些注意点Engine.IO是Socket.IO的底层传输通道实现。Engine.IO、 Socket.IO在上层均有
12、自己的协议,因此服务端和客户端必须搭配才能使用。也就是说Socket.IO的客户端必须搭配Socket.IO的服务端才能正常交互数据。在浏览器中message中的能抓到的数据包,属于WebSocket协议中的message类型数据,WebSocket 的PING , PONG是和message类型是并列的,因此浏览器中的devTools并不能抓到而Engine.IO的 心跳机制的实现(下列图中的2和3),是message数据之上的协议定义,是Engine.IO用WebSocket 的message类型消息发送的。5 一个简单的例子服务端代码const engine = require(engi
13、ne.io);const server = engine.listen(3000,(cons: origin:);server.on(listen() = console.log(listening on 3000 ) )server.on(connection, socket = console.log(new connection)socket.send(utf 8 string);socket.send(Buffer.from(hello world); / binary data );客户端代码const Socket = require(engine.io-client);const
14、 socket = new Socket(ws:/localhost:3000); socket.on(open, () = socket.emit(message from client)socket.on(message, (data) = console.log(receive message: + data); socket.send(ack from client.););socket.on(close(e) = console.log(socket closee););浏览器请求抓包1、Polling传输通道握手Request:Response:2、发起长轮询请求服务端数据Requ
15、est:Response:3、POST方式发送数据到服务端Request:Request payload:Response:4、服务端告诉客户端传输通道已升级,回复一个6Request:Response:5、WebSocket通道建立之后,切换为WebSocket传输数据Connect:dtontMnM OeneraliK4AipoH-cx)lrv&tME3k*4iMtMOCtaatlATIKBuMISAAAD?E)Ovtw1 SAAAD R(04AnpQf%ooein9Al3MHUkM* VW1QT1 KSuOebl&AAAD 鞭 lOlranepcfMxAngtUNraiFeMN* YWt
16、QTl KduQvbi 5AAADRequMt URL: vs:locaIhost! 3eee/e9ine. 10/K1044tramportMebsocket&sldM-Ykrt011K9uQsbl5AAAD Method: STSUrtut Code: 111 Switching FrotoV51vg13Upgrade wetsoM4XkanacartpcMnQ&irNx3BF4lData f 2Bcb 4 apxote t 51 4cti fromctaMLngtti TWna6 1,:稣008014 00X717 1,:45co0X71 11AS33 9W1 11:433 9M也可以在
17、客户端指定传输通道为websocket那么就不会先建立Polling传输通道,直接用 WebSocket传输通道进行握手。const socketnew Socket(ws:/localhost:3000transports: websocket );Namex Headers Payload Messages Irwtiator Tknng ?EK)=44transport=weto8ocketEnter regex, for example: (webpsocketDatabundlejsQ cbent.htnW3 0,sid:,nE-_zL15ISHySKPFAAAG,upgrades:
18、0.,pn9ntervai:25000.-ptngTimecxjf:20000)3 4utf 8 string t 4ack from client. B*nary Message t 4acXfromciien(. long-pollingWebSocket2.1.1. long-polling long-polling transport (也简称polling)由连续的 requests 组成:long-running GET requests, for receiving data from the servershort-running POST requests, for send
19、ing data to the server基于 long-polling transport的特性,连续的emits可能合并在一个 Request中发送。2.1.2. WebSocketThe WebSocket传输通道包含一条WebSocket连接,WebSocket提供了服务端和客户端之间双向通信 及低时延的通信通道。基于传输通道特性,每个emit会以一个WebSocket数据帧发送,有时候会分为2个不同的数据帧发送。22 HandshakeEngine.IO连接建立的时候,Server端会发送一些消息到客户端: (sid : FSDjX-WRwSA4zTZMALqx,upgrades
20、: websocket,Hpinglnterval: 25000, pingTimeout: 20000 sid.是session的ID ,在所有的子序列 Request中都会在参数带上这个sid.upgrades upgrades array包含了服务端可以支持的更好的transport.pingintervalpingTimeout:用于心跳机制.23 .升级机制默认的情况下,客户端先建立 long-polling通信通道。为什么呢?WebSocket无疑是最好的双向通道,但是由于公司的代理、个人的防火墙、杀毒软件等,它并不是在什么情 况下都能成功建立。从用户的角度来看,如果WebSock
21、et连接建立失败,那么用户至少要等10S才能开始真正的数据传输, 这无疑伤害了用户的体验。总的来说,Engine.IO首先关注可靠性和用户体验,其次才是服务器性能。升级的时候,客户端会做如下动作:保证要发送的队列中是空的把当前的传输通道设为只读使用另外的transport建立新的连接如果新传输通道建立成功,关掉第一条传输通道可以在浏览器抓包看到如下网络连接:握手协议 (contains the session ID here, zBjrh.AAAK that is used in subsequent requests)发送数据( long-polling)接收数据( long-polling
22、)升级协议(WebSocket) 接收数据( long-polling, closed once the WebSocket connection in 4. is successfully established)1.4、 断连检测当以下情况出现时,Engine.IO的连接会判断为关闭。一次 request (either GET or POST)失败(比方服务器挂了)WebSocket连接关闭(比方用户关闭了浏览器的tab)在服务端或者客户端调用socket.disconnect ()还有一个心跳机制用来检测服务端和客户端的连接是否正常在运行。服务端会以pinginterval的间隔发送P
23、ING数据包,客户端收到后在pingTimeout时间之内需要发送 PONG数据包给服务端,如果服务端在pingTimeout时间内没有收到,那么就认为这条连接关闭了。相反, 客户端如果在pinginterval + pingTimeout时间内没有收到PING数据包,客户端也判断连接关闭。服务端触发断连事件的原因有:ReasonDescriptionserver namespace disconnectThe socket was forcefully disconnected with socket.disc onnectclient namespace disconnectThe cli
24、ent has manually disconnected the socket using socket.disconnect()server shutting downThe server is, well, shutting downping timeoutThe client did not send a PONG packet in the pingTi meout delaytransport closeThe connection was closed (example: the user has lost connection, or the network was chang
25、ed from WiFi to 4G)transport errorThe connection has encountered an error客户端触发断连事件的原因有:ReasonDescriptionio server disconnectThe server has forcefully disconnected the socket with socket.disc onnect()io client disconnectThe socket was manually disconnected using socket.disconnect()ping timeoutThe ser
26、ver did not send a PING within the pinginterval + pingTi meout rangetransport closeThe connection was closed (example: the user has lost connectio n, or the network was changed from WiFi to 4G)transport errorThe connection has encountered an error (example: the server wa s killed during a long-polli
27、ng cycle)3 . Engine.IO 的协议3.1、 一次 Engine.IO 会话传输通道通过Engine.IO URL进行连接建立连接建立之后,服务端会发一个JSON格式的握手数据sid :会话 id (string)upgrades:允许升级的传输通道(Array of String)pingTimeout:服务端配置的ping超时时间,发送给客户端,客户端用来检测服务端是否还正常响应 (Number)pinginterval:服务端配置的心跳间隔,客户端用来检测服务端是否还正常响应(Number)客户端收到服务端定时的ping packet之后,需要回复客户端pong pack
28、et客户端和服务端之间可以传输message packetsPolling transports 可以发送 close packet 来关闭 socket会话例子Request nl (open packet)GET /engine.io/?EI0=4&tnansport=polling&t=N8hyd6w /1.1 200 OK open packet typesid:.= the handshake dataNote: query参数中的才是用来防止浏览器缓存请求.Request n2 (message in)服务端执行 socket.send (hey):GET /engine.io/?
29、EI0=4&transport=polling&t=N8hyd7H&sid=lv_VI97HAXpY6yYWAAAC /1.1 200 OK message packet typehey= the actual messageNote: query中的s/d是握手协议中sid.Request n3 (message out)客户端执行:socket.send (hello); socket.send (world);POST /engine.io/?EI0=4&transport=polling&t=N8hzxke&sid=lv_VI97HAXpY6yYWAAAC Content-Type:
30、text/plain; charset=UTF-8 4helloxle4world /1.1 200 OK message packet typehello= the 1st messagexle= separator4= message message typeworld= the 2nd messageRequest n4 (WebSocket upgrade)GET /engine.io/?EI0=4&transport=websocket&sid=lv_VI97HAXpY6yYWAAAC /1.1 101 Switching ProtocolsWebSocket frames: 3pr
31、obe 4hello 4world 2 1= probe request= probe response= upgrade packet type= message (not concatenated)= ping packet type= pong packet type= close packet type只有WebSocket连接的会话在这个例子中,客户端只开启了 WebSocket传输通道(without polling).GET /engine.io/?EI0=4&transport=websocket /1.1 101 Switching ProtocolsWebSocket fr
32、ames: handshake4hey 4hello = message (not concatenated)4world 2= ping packet type3= pong packet type 1= close packet typeURLsEngine.IO url包含了以下内容 /engine.io/?engine.io路径名只能由基于Engine.io协议之上的的更高级别框架更改,如Socket.io.query string是可选的,有6个保存的key:transport:指定的 transport , 默认为 polling, websocket.j:如果需要JSONP响应,
33、j必须与JSONP响应索引一起设置。sid:如果客户端已经收到session id ,那么每次请求的query string中都必须带上sidEIO:协议的版本t:用来防止浏览器缓存编码有两种不同类型的编码packet payload3.3.1 Packet一个编码的数据包可以是UTF-8字符串或者二进制数据。字符串的数据包编码格式如下:example:4hello对于二进制数据,不包括数据包类型(packet type ),因为只有message”数据包类型可以包括二进制 数据。packet type0 open新传输通道建立的时候,从服务端发送 Sent from the server when a new transport is opened (recheck)close请求关闭此传输,但不关闭连接本身。1 ping由服务器发送。客户应该用pong数据包应答。exampleserver sends: 2 client sends: 32 pong由客户端发送以响应ping数据包。3 message实际传输的消息example 1