python
websocket

在类型中用到socket.io压实时推送,遂花了点时间看了socket.io实现,做个轻便解析,如有错漏,款待指正。

python3知识点

jquery.min.js

home88一必发 1

安装

1 概述

socket.io是三个根据WebSocket的CS的实时通讯库,它底层基于engine.io。engine.io使用WebSocket和xhr-polling(或jsonp)封装了风流罗曼蒂克套本身的契约,在不扶植WebSocket的低版本浏览器中(扶植websocket的浏览器版本见这里)使用了长轮询(long
polling)来顶替。socket.io在engine.io的根底上扩展了namespace,room,自动重连等风味。

本文接下去会先简介websocket协议,然后在那功底上教学下engine.io和socket.io左券以致源码深入分析,后续再经过例子表明socket.io的干活流程。

web服务器代码:

#coding=utf-8

importtornado.websocket

importtornado.web

importtornado.ioloop

importdatetime

classIndexHandler(tornado.web.RequestHandler):

defget(self, *args, **kwargs):

self.render(‘templates/index.html’)

classWebHandler(tornado.websocket.WebSocketHandler):

users =set()#寄放在线顾客

defopen(self, *args, **kwargs):

self.users.add(self)#把树立连接后的客户拉长到客户容器中

foruserinself.users:#向在线的顾客发送步向音信

user.write_message(“[%s]-[%s]-步入聊天室”%
(self.request.remote_ip,

datetime.datetime.now().strftime(“%Y-%m-%d %H:%M:%S”)))

defon_close(self):

self.users.remove(self)# 顾客关闭连接后从容器中移除客户

foruserinself.users:

user.write_message(“[%s]-[%s]-离开闲谈室”%
(self.request.remote_ip,

datetime.datetime.now().strftime(“%Y-%m-%d %H:%M:%S”)))

defon_message(self, message):

foruserinself.users:#向在线客户发送聊天新闻

user.write_message(“[%s]-[%s]-说:%s”% (self.request.remote_ip,

datetime.datetime.now().strftime(“%Y-%m-%d %H:%M:%S”), message))

defcheck_origin(self, origin):

return True# 允许WebSocket的跨域须求

importos

BASE_DIR = os.path.dirname(__file__)

settings = {

‘static_path’:os.path.join(BASE_DIR,’static’),

“websocket_ping_interval”:1,

“websocket_ping_timeout”:10

}

app = tornado.web.Application([(r’/’,IndexHandler),

(r’/chat’,WebHandler)],

**settings)

app.listen(8009)

tornado.ioloop.IOLoop.instance().start()


pip install websocket-client

2 WebSocket协议

咱俩驾驭,在HTTP 合同开辟的时候,并非为着双向通讯程序希图的,初始的
web 应用程序只供给 “央浼-响应”
就够了。由于历史原因,在开创具备双向通讯机制的 web
应用程序时,就不能不选拔 HTTP 轮询的方式,由此发生了 “短轮询” 和
“长轮询”(注意区分短连接和长连接)。

短轮询通过客户端依期轮询来通晓服务端是或不是有新的新闻产生,劣点也是明显,轮询间距大了则音信相当不够实时,轮询间距过小又会损耗过多的流量,扩展服务器的承当。长轮询是对短轮询的优化,必要服务端做相应的改进来扶持。客商端向服务端发送诉求时,假设此时服务端未有新的音信发出,并不即刻回去,而是Hang住一段时间等有新的新闻依然逾期再回到,客商端收到服务器的作答后持续轮询。能够看到长轮询比短轮询可以减小大气空头的伸手,况且客户端接抽取新消息也会实时不菲。

纵然长轮询比短轮询优化了相当多,可是每一次央浼依然都要带上HTTP供给底部,而且在长轮询的连天完成将来,服务器端积累的新音信要等到后一次顾客端连接时技艺传递。越来越好的办法是只用叁个TCP连接来达成客商端和服务端的双向通讯,WebSocket合计就是为此而生。WebSocket是基于TCP的三个独立的协商,它与HTTP协议的独步一时涉及正是它的拉手央求能够当作叁个Upgrade request行经HTTP服务器深入解析,且与HTTP使用同样的端口。WebSocket默许对布衣蔬食央求使用80端口,合同为ws://,对TLS加密央浼使用443端口,公约为wss://

握手是经过三个HTTP Upgrade request起始的,三个伸手和响应尾部示举例下(去掉了非亲非故的尾部)。WebSocket握手央浼底部与HTTP央浼底部是同盟的(见RAV4FC2616卡塔尔国。

## Request Headers ##
Connection: Upgrade
Host: socket.io.demo.com
Origin: http://socket.io.demo.com
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: mupA9l2rXciZKoMNQ9LphA==
Sec-WebSocket-Version: 13
Upgrade: websocket

## Response Headers ##
101 Web Socket Protocol Handshake
Connection: upgrade
Sec-WebSocket-Accept: s4VAqh7eedG0a11ziQlwTzJUY3s=
Sec-WebSocket-Origin: http://socket.io.demo.com
Server: nginx/1.6.2
Upgrade: WebSocket
  • Upgrade
    是HTTP/1.第11中学分明的用来转移当前线总指挥部是的应用层左券的头顶,表示客商端希望用现成的连年转变成新的应用层左券WebSocket合同。

  • Origin
    用于幸免跨站攻击,浏览器日常会选用这一个来标记原始域,对于非浏览器的客商端应用能够依靠须要采纳。

  • 乞求头中的 Sec-WebSocket-Version
    是WebSocket版本号,Sec-WebSocket-Key
    是用来握手的密钥。Sec-WebSocket-Extensions 和 Sec-WebSocket-Protocol
    是可接收,暂不斟酌。

  • 响应头中的 Sec-WebSocket-Accept 是将央求头中的 Sec-WebSocket-Key
    的值加上三个定点魔数258EAFA5-E914-47DA-95CA-C5AB0DC85B11经SHA1+base64编码后得到。总结进程的python代码示例(uwsgi中的达成见
    core/websockets.c的 uwsgi_websocket_handshake函数):

    magic_number = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
    key = 'mupA9l2rXciZKoMNQ9LphA=='
    accept = base64.b64encode(hashlib.sha1(key + magic_number).digest())
    assert(accept == 's4VAqh7eedG0a11ziQlwTzJUY3s=')
    
  • 顾客端会检查响应头中的status code 和 Sec-WebSocket-Accept
    值是不是是期望的值,如若开掘Accept的值不得法恐怕状态码不是101,则不会创立WebSocket连接,也不会发送WebSocket数据帧。

WebSocket商量使用帧(Frame卡塔 尔(英语:State of Qatar)收发数据,帧格式如下。基于广安考虑衡量,顾客端发送给服务端的帧必需经过4字节的掩码(Masking-key卡塔尔加密,服务端收到音讯后,用掩码对数据帧的Payload
Data举办异或运算解码获得数码(详见uwsgi的 core/websockets.c
中的uwsgi_websockets_parse函数卡塔 尔(英语:State of Qatar),假诺服务端收到未经掩码加密的数据帧,则应该及时关闭该WebSocket。而服务端发给客商端的数额则无需掩码加密,顾客端若是选取了服务端的掩码加密的数码,则也必得关闭它。

 0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-------+-+-------------+-------------------------------+
     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
     | |1|2|3|       |K|             |                               |
     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     |     Extended payload length continued, if payload len == 127  |
     + - - - - - - - - - - - - - - - +-------------------------------+
     |                               |Masking-key, if MASK set to 1  |
     +-------------------------------+-------------------------------+
     | Masking-key (continued)       |          Payload Data         |
     +-------------------------------- - - - - - - - - - - - - - - - +
     :                     Payload Data continued ...                :
     +---------------------------------------------------------------+

帧分为调节帧和数据帧,调控帧不能分片,数据帧能够分片。首要字段表明如下:

  • FIN:
    未有分片的帧的FIN为1,分片帧的第多个分片的FIN为0,最终叁个分片FIN为1。
  • opcode: 帧类型编号,当中央调节制帧:0x8 (Close), 0x9 (Ping), and 0xA
    (Pong),数据帧主要有:0x1 (Text), 0x2 (Binary)。
  • MASK:客商端发给服务端的帧MASK为1,Masking-key为加密掩码。服务端发往顾客端的MASK为0,Masking-key为空。
  • Payload len和Payload Data分别是帧的多寡长度和数据内容。

HTML代码:python3知识点

微信QQ

#chatcontent{

/*展现内容使用的*/

width:500px;

height:200px;

background-color:pink;

overflow-y:scroll;

overflow-x:scroll;

}

发送

ws=newWebSocket(‘ws://192.168.1.27:8009/chat’)

//服务器给浏览器推送信息的时候回调

ws.onmessage=function(p1) {

$(‘#chatcontent’).append(‘

‘+p1.data+’

‘)

}

functionsend() {

varcontent=$(‘#msg_container’).val()

ws.send(content)

$(‘#msg_container’).val(”)

}

 

3 engine.io和socket.io

前方提到socket.io是基于engine.io的包裹,engine.io(左券版本3卡塔 尔(阿拉伯语:قطر‎有意气风发套本人的商量,任何engine.io服务器都必须扶植polling(蕴涵jsonp和xhr)和websocket二种传输方式。engine.io使用websocket时有豆蔻年华套自身的ping/pong机制,使用的是opcode为0x1(Text)类型的数据帧,不是websocket共同商议分明的ping/pong类型的帧,标准的
ping/pong 帧被uwsgi使用

engine.io的数据编码分为Packet和Payload,此中 Packet是数据包,有6种类型:

  • 0 open:从服务端发出,标识一个新的传输格局已经展开。
  • 1 close:央求关闭那条传输连接,不过它自个儿并不停业那几个一而再。
  • 2
    ping:客商端周期性发送ping,服务端响应pong。注意那几个与uwsgi自带的ping/pong不相似,uwsgi里面发送ping,而浏览器重临pong。
  • 3 pong:服务端发送。
  • 4 message:实际发送的新闻。
  • 5
    upgrade:在改变transport前,engine.io会发送探测包测验新的transport(如websocket卡塔 尔(英语:State of Qatar)是不是可用,如若OK,则顾客端会发送二个upgrade音信给服务端,服务端关闭老的transport然后切换成新的transport。
  • 6
    noop:空操作数据包,客商端收到noop音信会将事先等待暂停的轮询暂停,用于在选择到一个新的websocket强制三个新的轮询周期。

而Payload是指后生可畏多元绑定到合作的编码后的Packet,它只用在poll中,websocket里面使用websocket帧里面的Payload字段来传输数据。假设客商端不协助XHEvoque2,则payload格式如下,个中length是数额包Packet的长度,而packet则是编码后的数据包内容。

<length1>:<packet1>[<length2>:<packet2>[...]]

若协理XHLAND2,则payload中内容全方位以二进制编码,当中第一位0表示字符串,1意味二进制数据,而前面跟着的数字则是表示packet长度,然后以xff结尾。假若二个尺寸为109的字符类型的数据包,则前边长度编码是
x00x01x00x09xff,然后前面接packet内容。

<0 for string data, 1 for binary data><Any number of numbers between 0 and 9><The number 255><packet1 (first type,
then data)>[...]

engine.io服务器维护了三个socket的字典结构用于管理总是到该机的客户端,而客商端的标记便是sid。即便有几个worker,则须求保证同二个客商端的三番一次落在相似台worker上(能够配备nginx根据sid分发)。因为各样worker只保险了一片段客户端连接,要是要支持广播,room等特征,则后端要求接受redis 也许 RabbitMQ
新闻队列,使用redis的话则是透过redis的订阅揭橥机制完成多机多worker之间的消息推送。

socket.io是engine.io的包裹,在其底子上加码了自动重连,多路复用,namespace,room等特征。socket.io本人也是有朝气蓬勃套合同,它Packet类型分为(CONNECT 0, DISCONNECT 1, EVENT 2, ACK 3, ERROR 4, BINARY_EVENT 5, BINARY_ACK 6)。注意与engine.io的Packet类型有所分歧,然而socket.io的packet实际是依据的engine.io的Message类型发送的,在末端实例中得以看来Packet的编码方式。当连接出错的时候,socket.io会通过机关心重视连机制再一次连接。

 

4 源码剖析

在创设连接后,每一种socket会被活动步入到一个默认的命名空间/。在各样命名空间中,socket会被默许插手几个名称叫Nonesid的房子。None的屋企用于广播,而sid是眼前顾客端的session
id,用于单播。除暗中认可的屋家外,大家得以凭借要求将对应socket参与自定义房间,roomid唯风华正茂就可以。socket.io基于engine.io,帮衬websocket和long
polling。若是是long polling,会依期发送GET,
POST供给,当相当少时,GET央求在拉取队列音讯时会hang住(超时时间为pingTimeout),如若hang住中间服务器一贯尚未数量爆发,则须求等到客户端发送下叁个POST诉求时,此时服务器会往队列中寄存POST诉求中的新闻,那样上二个GET哀告才会回去。借使upgrade到了websocket连接,则会依期ping/pong来保活连接。

为方便描述,上面提到的engine.io服务器对应源文件是engineio/server.py,engine.io套接字对应源文件engineio/socket.py,而socket.io服务器则对应socketio/server.py。下面剖判下socket.io连接建构、音信接纳和出殡和安葬、连接关闭进度。socket.io版本为1.9.0,engine.io版本为2.0.4。

先来看一下,长连接调用情势:

连接建设构造

第风姿洒脱,顾客端会发送三个polling伏乞来树立连接。那时候的伏乞参数未有sid,表示要创制连接。
engine.io服务器通过handle_get_request()handle_post_request()方法来分别管理初阶化连接以至长轮询中的
GET 和 POST 央求。

socket.io在伊始化时便登记了3个事件到engine.io的handlers中,分别是connect(处理函数_handle_eio_connect)home88一必发,,message(_handle_eio_message),disconnect(_handle_eio_disconnect),在engine.io套接字接纳到了上述多少个类其他新闻后,在本身做了对应管理后都会触发socket.io中的对应的管理函数做进一层管理。

当接到到GET央求且未有sid参数时,则engine.io服务器会调用
_handle_connect()办法来树立连接。那些主意首要专业是为前段时间顾客端生成sid,创设Socket对象并保留到engine.io服务器的sockets集结中。做了这几个初叶化专业后,engine.io服务器会发送七个OPEN类型的数额包给顾客端,接着会触发socket.io服务器的connect事件。

客商端第二回接二连三的时候,socket.io也要做一些开首化的干活,那是在socket.io服务器的_handle_eio_connect()拍卖的。这里做的政工根本有几点:

  • 开始化manager,比方用的是redis做后端队列的话,则需求伊始化redis_manager,包蕴安装redis连接配置,订阅频道,暗中同意频道是”socket.io”,尽管选择flask_socketio则频道是”flask_socketio”,假如用到gevent,则还要对redis模块的socket库打monkey-patch等。

  • 将该客商端参与到暗中认可房间None,sid中。

  • 调用代码中对connect事件注册的函数。如下边这几个,注意下,socket.io中也可能有个用于事件管理的handlers,它保存的是在后端代码中对socket.io事件注册的函数(开拓者定义的),而engine.io的handlers中保留的函数是socket.io注册的这多少个针对connect,message和disconnect事件的永久的管理函数。

    socketio.on("connect")
    def test_connect():
        print "client connected"
    
  • 发送一个sockeio的connect数据包给顾客端。

最终在响应中engine.io会为客商端设置三个名称为io值为sid的cookie,响应内容payload满含多个数据包,三个是engine.io的OPEN数据包,内容为sid,pingTimeout等配备和参数;另叁个是socket.io的connect数据包,内容为40。当中4意味的是engine.io的message消息,0则表示socket.io的connect信息,以字节流回到。这里的pingTimeout客商端和服务端分享这一个布局,用于检验对端是或不是过期。

接着会发送二个轮询诉求和websocket握手诉求,若是websocket握手成功后顾客端会发送2 probe探测帧,服务端回应3 probe,然后客户端会发送内容为5的Upgrade帧,服务端回应内容为6的noop帧。探测帧检查通过后,顾客端结束轮询央求,将传输通道转到websocket连接,转到websocket后,接下去就最早依期(默许是25秒)的
ping/pong(那是socket.io自定义的ping/pong,除外,uwsgi也会定时(默许30秒)对客商端ping,顾客端回应pong,这些在chrome的Frames里面是看不到的,要求依靠wireshark只怕用别样浏览器插件来观看)。

    ws = websocket.WebSocketApp("ws://echo.websocket.org/",
                              on_message = on_message,
                              on_error = on_error,
                              on_close = on_close)
    ws.on_open = on_open
    ws.run_forever()

服务端音讯接受流程

对接到新闻的则统一通过engine.io套接字的receive()函数管理:

  • 对于轮询,生机勃勃旦接到了polling的POST诉求,则会调用receive往该socket的新闻队列之中发送音讯,进而释放早前hang住的GET供给。
  • 对于websocket:
    • 吸纳了ping,则会即时响应一个pong。
    • 收下到了upgrade消息,则立刻发送一个noop音讯。
    • 接过到了message,则调用socket.io注册到engine.io的_handle_eio_message艺术来拍卖socket.io自身定义的各样音讯。

 

服务端新闻发送流程

而服务端要给顾客端发送消息,则须要经过socket.io服务器的emit方法,注意emit方法是针对room来发送音信的,假诺是context-aware的,则emit默许是对namespace为/且room名字为sid的房间发送,假诺是context-free的,则暗中认可是广播即对负有连接的客商端发送消息(当然在context-free的境况上边,你也得以钦点room来只给钦命room推送音讯卡塔尔国。

socket.io要落成多进度以致广播,房间等功能,势必须求衔接四个redis之类的音信队列,进而socket.io的emit会调用对应队列微机pubsub_manager的emit方法,举个例子用redis做新闻队列则最终调用
redis_manager中的_publish()
方法通过redis的订阅发布功效将音讯推送到flask_socketio频道。其他方面,全部的socket在三回九转时都订阅了
flask_socketio频道,何况都有叁个体协会程(或线程)在监听频道中是还是不是有新闻,生龙活虎旦有音信,就能够调用pubsub_manager._handle_emit()措施对本机对应的socket发送对应的新闻,最终是经过socket.io服务器的_emit_internal()主意完结对本机中room为sid的有着socket发送音信的,假诺room为None,则正是广播,即对具有连接到本机的兼具客商端推送音信。

socket.io服务器发送音信要基于engine.io新闻包装,所以总结到底仍然调用的engine.io套接字中的send()办法。engine.io为种种客商端都会保养三个音信队列,发送数据都以先存到行列之中待拉取,websocket除了探测帧之外的其它数据帧也都以透过该新闻队列发送。

 长连接,参数介绍:

关门连接(只剖判websocket)

websocket可能那三个关闭的景观多多。比方客商端发了ping后等候pong超时关闭,服务端接受到ping跟上三个ping之间超过了ping提姆eout;用的uwsgi的话,uwsgi发送ping,倘若在websockets-pong-tolerance(默许3秒)内接纳不到pong回应,也会停业连接;还宛假如nginx的proxy_read_timeout配置的比pingInterval小等。

尽管不是客商端主动关闭连接,socket.io就能够在三番五遍出错后持续重试以创立连接。重试间距和重试次数由reconnectionDelayMax(默认5秒)reconnectionAttempts(默许从来重连卡塔尔国设定。上面商讨顾客端平常关闭的情状,各样特别关闭状态请具体景况具体深入分析。

客户端主动关闭

万后生可畏客户端调用socket.close()积极关闭websocket连接,则会头阵送叁个音信41(4:engine.io的message,1:socket.io的disconnect)再关闭连接。如前方提到,engine.io套接字采取到信息后会交给socket.io服务器注册的
_handle_eio_message()处理。最后是调用的socket.io的_handle_disconnect(),该函数工作包蕴调用socketio.on("disconnect")挂号的函数,将该客商端从投入的屋企中移除,清理情状变量等。

uwsgi而接受到顾客端关闭websocket连接音信后会关闭服务端到客户端的连天。engine.io服务器的websocket数据接纳例程ws.wait()因为三番一次关闭报IOError,触发服务端循环收发数据经过结束,并从保证的sockets集合中移除那么些闭馆的sid。然后调用engine.io套接字的close(wait=True, abort=True)方法,由于是客商端主动关闭,这里就不会再给顾客端发送一个CLOSE新闻。而
engine.io服务器的close方法大器晚成致会触发socket.io在此之前注册的disconnect事件管理函数,由于后边已经调用_handle_disconnect()管理了关闭连接事件,所以这里_handle_eio_disconnect()不需求再做任何操作(这么些操作不是多余的,其效果见后意气风发节卡塔尔。

浏览器关闭

直白关门浏览器发送的是websocket的标准CLOSE新闻,opcode为8。socket.io服务端处理格局基本生机勃勃致,由于这种场馆下并不曾发送socket.io的关闭音讯41,socket.io的关闭操作供给等到engine.io触发的_handle_eio_disconnect()中拍卖,那就是前风流倜傥节中为何engine.io服务器前面还要多调用三遍
_handle_eio_disconnect()的缘由所在。

(1)url:
websocket的地址。

5 实例

商业事务表明轻巧令人多少头晕,websocket,engine.io,socket.io,各自行车运动组织议是怎么样职业的,看看实例恐怕会相比明晰,为了有支持测量试验,小编写了个Dockerfile,安装了docker的童鞋可以拉代替码试行
bin/start.sh 就可以运行具有完整的
nginx+uwsgi+gevent+flask_socketio测量试验蒙受的器皿早前测验,浏览器张开http://127.0.0.1就能够测量试验。async_mode用的是gevent_uwsgi,完整代码见
这里。

对此不援救websocket的低版本浏览器,socket.io会退化为长轮询的措施,通过为期的出殡和安葬GET,
POST伏乞来拉取数据。未有数据时,会将诉求数据的GET须求hang住,直到服务端有数量发生只怕顾客端的POST诉求将GET诉求释放,释放之后会随之再次发送三个GET央浼,除却,公约分析和拍卖流程与websocket方式基本意气风发致。实例只针对利用websocket的张开剖析

为了考查socket.io客商端的调用流程,能够安装localStorage.debug = '*';,测量检验的前段代码片段如下(完整代码见旅馆):

 <script type="text/javascript" charset="utf-8">
    var socket = io.connect('/', {
        "reconnectionDelayMax": 10000,
        "reconnectionAttempts": 10
    });
    socket.on('connect', function() {
        $('#log').append('<br>' + $('<div/>').text('connected').html());
    })

    $(document).ready(function() {

        socket.on('server_response', function(msg) {
            $('#log').append('<br>' + $('<div/>').text('Received from server: ' + ': ' + msg.data).html());
        });

        $('form#emit').submit(function(event) {
            socket.emit('client_event', {data: $('#emit_data').val()});
            return false;
        });
    });

 </script>

测验代码比较轻松,引进socket.io的js库文件,然后在连年成功后在页面展现“connected”,在输入框输入文字,能够由此连接发送至服务器,然后服务器将浏览器发送的字符串加上server标记回显回来。

(2卡塔尔国header:
客商发送websocket握手诉求的央求头,{‘head1:value1′,’head2:value2’}。

创建连接

在chrome中打开页面能够看看发了3个央浼,分别是:

1 http://127.0.0.1/socket.io/?EIO=3&transport=polling&t=MAkXxBR
2 http://127.0.0.1/socket.io/? EIO=3&transport=polling&t=MAkXxEz&sid=9c54f9c1759c4dbab8f3ce20c1fe43a4
3 ws://127.0.0.1/socket.io/?EIO=3&transport=websocket&sid=9c54f9c1759c4dbab8f3ce20c1fe43a4

呼吁私下认可路径是/socket.io,注意命名空间并不会在路径中,而是在参数中传送。首个须求是polling,EIO是engine.io左券的版本号,t是三个随机字符串,第叁个乞求时还还并未有生成sid。服务端接受到音讯后会调用engine.io/server.py_handle_connect()创造连接。

回去的结果是

## Response Headers: Content-Type: application/octet-stream ##
�ÿ0{"pingInterval":25000,"pingTimeout":60000,"upgrades":["websocket"],"sid":"9c54f9c1759c4dbab8f3ce20c1fe43a4"}�ÿ40

能够看看,这里重临的是字节流的payload,content-type为”application/octet-stream”。这些payload其实包蕴七个packet,第二个packet是engine.io的OPEN消息,类型为0,它的从头到尾的经过为pingInterval,pingTimeout,sid等;第贰个packet类型是4(message),而它的数额内容是0,表示socket.io的CONNECT。而内部的看起来乱码的风华正茂对其实是日前提到的payload编码中的长度的编码x00x01x00x09xffx00x02xff

  • 第一个央浼是轮询伏乞,假设websocket建构并测验成功(使用内容为probe的ping/pong帧)后,会搁浅轮询央求。可以见见轮询央浼平昔hang住到websocket建立并测量检验成功后才重临,响应结果是�ÿ6,前边乱码部分是payload长度编码x00x01xff,前边的数字6是engine.io的noop新闻。

  • 第2个央浼是websocket握手央浼,握手成功后,能够在chrome的Frames在那之中见到websocket的数目帧人机联作流程,可以看出如前方剖析,确实是首发的探测帧,然后是Upgrade帧,接着就是准时的ping/pong帧了。

    2probe
    3probe
    5
    2
    3
    ...
    

(3)on_open:在创制Websocket握手时调用的可调用对象,那么些情势只有一个参数,正是此类本人。

顾客端发送消息给服务端

万风姿浪漫要发送消息给服务器,在浏览器输入框输入test,点击echo按键,能够观看websocket发送的帧的开始和结果如下,在那之中4是engine.io的message类型标志,2是socket.io的EVENT类型标记,而背后则是事件名称和数码,数据能够是字符串,字典,列表等等级次序。

42["client_event",{"data":"test"}]

(4)on_message:那么些目标在吸收接纳到服务器重回的音讯时调用。有四个参数,二个是此类自己,一个是大家从服务器获取的字符串(utf-8格式卡塔尔国。

服务端接收消息流程

而服务端选用音讯并回到三个新的event为”server_response”,数据为”TEST”,代码如下,在这之中socketio是flask_socketio模块的SocketIO对象,它提供了装饰器方法
on将自定义的client_event和拍卖函数test_client_event注册到sockerio服务器的handlers中。

当接到到 client_event 消息时,会通过sockerio/server.py中的
_handle_eio_message()措施管理信息,对于socket.io的EVENT类型的新闻最后会透过_trigger_event()方法管理,该办法相当于从handlers中获得client_event对应的管理函数并调用之。

from flask_socketio import SocketIO, emit
socketio = SocketIO(...)

@socketio.on("client_event")
def test_client_event(msg):
    emit("server_response", {"data": msg["data"].upper()})

(5)on_error:那几个目的在碰着错误时调用,有七个参数,第二个是此类本人,第二个是拾壹分对象。

服务端发送新闻到顾客端

服务端发送音讯通过
flask_socketio提供的emit方法达成,如前焕发青新禧深入分析的,最后还是经过的engine.io包装成engine.io的音讯格式后发生。

42["server_response",{"data":"TEST"}]

(6)on_close:在碰到一连关闭的动静时调用,参数独有贰个,就是此类本身。

关门连接

客户端要百尺竿头更上一层楼关闭连接,在JS中调用 socket.close()
就能够,那时出殡的多少包为
41,当中4意味着的是engine.io的音信类型message,而数据1则是指的socket.io的音信类型disconnect,关闭流程见上风姿浪漫章的印证。

(7)on_cont_message:那一个目的在选择到一而再一而再再而三帧数据时被调用,有四个参数,分别是:类本身,从服务器选用的字符串(utf-8卡塔尔,一而再标记。

6 总结

本文示例中,为了便于解析,只用了暗中同意的namespace和room,而在事实上项目中能够依照专门的学问须要使用namespace,room等高等脾气。

nginx+uwsgi运用socket.io时,当用到websocket时,注意nginx的逾期配置proxy_read_timeout和uwsgi的websocket超时配置websocket-ping-freq和websockets-pong-tolerance,配置不当会招致socke.io因为websocket的ping/pong超时而不息重连。

(8)on_data:当从服务器收到到新闻时被调用,有七个参数,分别是:该类自个儿,接纳到的字符串(utf-8卡塔 尔(英语:State of Qatar),数据类型,三番五次标记。

参谋资料

  • https://tools.ietf.org/html/rfc6455
  • https://www.nginx.com/blog/websocket-nginx/
  • https://security.stackexchange.com/questions/36930/how-does-websocket-frame-masking-protect-against-cache-poisoning
  • https://github.com/suexcxine/blog/blob/master/source/_posts/websocket.md
  • https://github.com/abbshr/abbshr.github.io/issues/47
  • https://socket.io/docs/logging-and-debugging/
  • http://uwsgi-docs.readthedocs.io/en/latest/WebSockets.html
  • https://flask-socketio.readthedocs.io/en/latest/

(9)keep_running:多少个二进制的标识位,假设为True,那些app的主循环将不仅运行,暗许值为True。

(10)get_mask_key:用于发生三个掩码。

(11卡塔尔国subprotocols:后生可畏组可用的子协议,默感觉空。

 

长连接重要办法:ws.run_forever(ping_interval=60,ping_timeout=5)

 若是持续按键闭websocket连接,会一贯不通下去。其它那个函数带七个参数,若是传的话,运维心跳包发送。

 

ping_interval:自动发送“ping”命令,每一种钦点的年月(秒),若是设置为0,则不会自行发送。

ping_timeout:如果未有摄取pong新闻,则为超时(秒)。

ws.run_forever(ping_interval=60,ping_timeout=5)

#ping_interval心跳发送间隔时间

#ping_timeout 设置,发送ping到收到pong的超时时间

 

咱俩看源代码,会意识这么风姿洒脱断代码:

ping的晚点时间,要高于ping间距时间

 

        if not ping_timeout or ping_timeout <= 0:
            ping_timeout = None
        if ping_timeout and ping_interval and ping_interval <= ping_timeout:
            raise WebSocketException("Ensure ping_interval > ping_timeout")

 

 

 

 

长连接:

示例1:

 

import websocket
try:
    import thread
except ImportError:
    import _thread as thread
import time

def on_message(ws, message):
    print(message)

def on_error(ws, error):
    print(error)

def on_close(ws):
    print("### closed ###")


def on_open(ws):
    def run(*args):
        ws.send("hello1")
        time.sleep(1)
        ws.close()
    thread.start_new_thread(run,())

if __name__ == "__main__":
    websocket.enableTrace(True)
    ws = websocket.WebSocketApp("ws://echo.websocket.org/",
                              on_message = on_message,
                              on_error = on_error,
                              on_close = on_close)
    ws.on_open = on_open
    ws.run_forever(ping_interval=60,ping_timeout=5)

 

示例2:

import websocket
from threading import Thread
import time
import sys


class MyApp(websocket.WebSocketApp):
    def on_message(self, message):
        print(message)

    def on_error(self, error):
        print(error)

    def on_close(self):
        print("### closed ###")

    def on_open(self):
        def run(*args):
            for i in range(3):
                # send the message, then wait
                # so thread doesn't exit and socket
                # isn't closed
                self.send("Hello %d" % i)
                time.sleep(1)

            time.sleep(1)
            self.close()
            print("Thread terminating...")

        Thread(target=run).start()


if __name__ == "__main__":
    websocket.enableTrace(True)
    if len(sys.argv) < 2:
        host = "ws://echo.websocket.org/"
    else:
        host = sys.argv[1]
    ws = MyApp(host)
    ws.run_forever()

 

 

短连接:

from websocket import create_connection
ws = create_connection("ws://echo.websocket.org/")
print("Sending 'Hello, World'...")
ws.send("Hello, World")
print("Sent")
print("Receiving...")
result =  ws.recv()
print("Received '%s'" % result)
ws.close()

 

——

相关文章