Python Tornado不难聊天室

python
websocket

在类型中用到socket.io抓牢时推送,遂花了点时间看了socket.io实现,做个简易分析,如有错漏,欢迎指正。

译者说

Tornado 4.3于20壹5年11月七日公布,该版本正式协助Python3.5async/await重视字,并且用旧版本CPython编写翻译Tornado同样能够利用那七个首要字,那确实是1种提升。其次,那是最后三个支撑Python2.6Python3.2的本子了,在继承的本子了会移除对它们的相称。以往互连网上还未有Tornado4.3的汉语文书档案,所以为了让更加多的情侣能接触并就学到它,作者开始了那么些翻译项目,希望感兴趣的伴儿能够协同到场翻译,项目地址是tornado-zh
on
Github,翻译好的文书档案在Read
the
Docs上直接能够看到。欢迎Issues
or P汉兰达。本节谢谢@thisisx7翻译

python3知识点

jquery.min.js

365bet亚洲真人 1

安装

1 概述

socket.io是二个基于WebSocket的CS的实时通讯库,它底层基于engine.io。engine.io使用WebSocket和xhr-polling(或jsonp)封装了1套自身的商议,在不协理WebSocket的低版本浏览器中(协助websocket的浏览器版本见这里)使用了长轮询(long
polling)来代表。socket.io在engine.io的根基上平添了namespace,room,自动重连等本性。

正文接下去会先不难介绍websocket协议,然后在此基础上教学下engine.io和socket.io协议以及源码分析,后续再经过例子表达socket.io的办事流程。

PS:本节最棒直接在https://tornado-zh.readthedocs.org或者http://tornado.moelove.info/开卷,以拿到越来越好的翻阅体验(格式帮助)。原谅本人没排好版QAQ

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的3个单独的协商,它与HTTP协议的绝无仅有涉嫌便是它的拉手请求能够作为3个Upgrade request行经HTTP服务器解析,且与HTTP使用同样的端口。WebSocket私下认可对一般性请求使用80端口,协议为ws://,对TLS加密请求使用44三端口,协议为wss://

握手是透过三个HTTP Upgrade request开班的,2个呼吁和响应尾部示例如下(去掉了无关的头顶)。WebSocket握手请求底部与HTTP请求底部是相称的(见奥德赛FC261陆)。

## 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.1中规定的用于转移当前接连的应用层协议的底部,表示客户端希望用现有的连接转换成新的应用层协议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经SHA一+base6四编码后获得。总计进程的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的值不得法大概状态码不是拾一,则不会树立WebSocket连接,也不会发送WebSocket数据帧。

WebSocket商业事务使用帧(Frame)收发数据,帧格式如下。基于白城考虑衡量,客户端发送给服务端的帧必须通过四字节的掩码(Masking-key)加密,服务端收到新闻后,用掩码对数据帧的Payload
Data举办异或运算解码得到数码(详见uwsgi的 core/websockets.c
中的uwsgi_websockets_parse函数),假设服务端收到未经掩码加密的数据帧,则应当立刻关闭该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为一,分片帧的率先个分片的FIN为0,最终二个分片FIN为1。
  • opcode: 帧类型编号,个中控制帧:0x八 (Close), 0x玖 (Ping), and 0xA
    (Pong),数据帧首要有:0x一 (Text), 0x2 (Binary)。
  • MASK:客户端发给服务端的帧MASK为1,Masking-key为加密掩码。服务端发往客户端的MASK为0,Masking-key为空。
  • Payload len和Payload Data分别是帧的数据长度和数目内容。

tornado.websocket — 浏览器与服务器双向通讯

WebSocket 协议的兑现

WebSockets 允许浏览器和服务器之间举办 双向通讯

不无主流浏览器的当代版本都扶助WebSockets(扶助景况详见:http://caniuse.com/websockets)

该模块依据最新 WebSocket 协议 哈弗FC 6455 完结.

在 4.0 版更改: Removed support for the draft 76 protocol version.

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为0x一(Text)类型的数据帧,不是websocket协和式飞机规定的ping/pong类型的帧,标准的
ping/pong 帧被uwsgi使用

engine.io的数据编码分为Packet和Payload,在那之中 Packet是数据包,有陆种档次:

  • 0 open:从服务端发出,标识3个新的传输方式已经开辟。
  • 壹 close:请求关闭那条传输连接,不过它自身并不关门那些一而再。
  • 二ping:客户端周期性发送ping,服务端响应pong。注意那么些与uwsgi自带的ping/pong分化,uwsgi里面发送ping,而浏览器再次来到pong。
  • 3 pong:服务端发送。
  • 4 message:实际发送的新闻。
  • 五upgrade:在转移transport前,engine.io会发送探测包测试新的transport(如websocket)是还是不是可用,借使OK,则客户端会发送一个upgrade消息给服务端,服务端关闭老的transport然后切换成新的transport。
  • 陆noop:空操作数据包,客户端收到noop音信会将事先等待暂停的轮询暂停,用于在吸收接纳到三个新的websocket强制3个新的轮询周期。

而Payload是指1密密麻麻绑定到一块的编码后的Packet,它只用在poll中,websocket里面使用websocket帧里面包车型大巴Payload字段来传输数据。倘使客户端不支持XHPRADO二,则payload格式如下,其中length是多少包Packet的尺寸,而packet则是编码后的数码包内容。

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

若辅助XHQashqai二,则payload中剧情全方位以2进制编码,当中第一位0表示字符串,一表示2进制数据,而背后随着的数字则是象征packet长度,然后以\xff结尾。假设3个尺寸为10玖的字符类型的数据包,则前面长度编码是
\x00\x01\x00\x09\xff,然后前边接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会通过活动重连机制再一次连接。

class tornado.websocket.WebSocketHandler(application, request, **kwargs)

透过延续该类来创设二个骨干的 WebSocket handler.

重写 on_message 来处理收到的音讯, 使用 write_message
来发送音信到客户端. 你也足以重写 open 和 on_close
来处理连接打开和关闭这多个动作.

有关JavaScript 接口的详细新闻:
http://dev.w3.org/html5/websockets/
具体的协商:
http://tools.ietf.org/html/rfc6455

3个简练的 WebSocket handler 的实例:
服务端直接再次来到全体接受的音讯给客户端

class EchoWebSocket(tornado.websocket.WebSocketHandler):
    def open(self):
        print("WebSocket opened")

    def on_message(self, message):
        self.write_message(u"You said: " + message)

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

WebSockets 并不是行业内部的 HTTP 连接. “握手”动作符合 HTTP
标准,不过在”握手”动作之后, 协议是根据新闻的. 因此,Tornado 里多数的
HTTP 工具对于那类 handler 都以不可用的. 用来报导的不二等秘书诀只有write_message() , ping() , 和 close() . 同样的,你的 request handler
类里应该利用 open() 而不是 get() 或然 post()

万一您在应用中校以此 handler 分配到 /websocket, 你可以透过如下代码完结:

var ws = new WebSocket("ws://localhost:8888/websocket");
ws.onopen = function() {
   ws.send("Hello, world");
};
ws.onmessage = function (evt) {
   alert(evt.data);
};

其壹本子将会弹出2个提拔框 :”You said: Hello, world”

浏览器并不曾如约同源策略(same-origin policy),相应的同意了任性站点使用
javascript 发起任意 WebSocket
连接来决定其余网络.那让人惊讶,并且是二个暧昧的安全漏洞,所以 从 Tornado
4.0 开端 WebSocketHandler 须求对愿意接受跨域请求的选拔通过重写.

check_origin (详细新闻请查看文书档案中关于该方法的一部分)来开始展览设置.
没有科学配置那几个天性,在创造 WebSocket 连接时候很可能会造成 40叁 错误.

当使用安全的 websocket 连接(wss://) 时, 来自浏览器的连接或许会失利,因为
websocket 未有地点输出 “认证成功” 的对话. 你在 websocket
连接建立成功在此之前,必须 使用同壹的证件访问贰个寻常化的 HTML 页面.

 

肆 源码分析

在确立连接后,各样socket会被活动进入到一个默许的命名空间/。在各类命名空间中,socket会被暗许参加四个名称叫Nonesid的房间。None的房间用于广播,而sid是当前客户端的session
id,用于单播。除私下认可的房间外,大家能够依照须要将对应socket参与自定义房间,roomid唯1即可。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.玖.0,engine.io版本为二.0.四。

Event handlers

先来看一下,长连接调用格局:

连年建立

第1,客户端会发送贰个polling请求来确立连接。此时的伸手参数未有sid,表示要建立连接。
engine.io服务器通过handle_get_request()handle_post_request()措施来分别处理初阶化连接以及长轮询中的
GET 和 POST 请求。

socket.io在早先化时便登记了三个事件到engine.io的handlers中,分别是connect(处理函数_handle_eio_connect),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服务器会发送1个OPEN类型的多少包给客户端,接着会触发socket.io服务器的connect事件。

客户端第2次一而再的时候,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。在那之中肆象征的是engine.io的message信息,0则象征socket.io的connect音信,以字节流回到。那里的pingTimeout客户端和服务端共享这么些布局,用于检查评定对端是或不是过期。

随着会发送贰个轮询请求和websocket握手请求,假使websocket握手成功后客户端会发送2 probe探测帧,服务端回应3 probe,然后客户端会发送内容为5的Upgrade帧,服务端回应内容为6的noop帧。探测帧检查通过后,客户端停止轮询请求,将传输通道转到websocket连接,转到websocket后,接下去就开首为期(暗中同意是2伍秒)的
ping/pong(那是socket.io自定义的ping/pong,除此而外,uwsgi也会定期(暗中同意30秒)对客户端ping,客户端回应pong,这么些在chrome的Frames里面是看不到的,须求借助wireshark恐怕用别样浏览器插件来观看)。

WebSocketHandler.open(*args, **kwargs)

当打开贰个新的 WebSocket 时调用

open 的参数是从 tornado.web.U福睿斯LSpec 通过正则表达式获取的, 如同获取
tornado.web.RequestHandler.get 的参数壹样

    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()

服务端信息接收流程

对收取音讯的则统1通过engine.io套接字的receive()函数处理:

  • 对于轮询,1旦接到了polling的POST请求,则会调用receive往该socket的音信队列之中发送音讯,从而释放在此以前hang住的GET请求。
  • 对于websocket:
    • 收受了ping,则会立时响应1个pong。
    • 选用到了upgrade消息,则马上发送2个noop音讯。
    • 收到到了message,则调用socket.io注册到engine.io的_handle_eio_message方法来拍卖socket.io自身定义的各类信息。

WebSocketHandler.on_message(message)

处理在 WebSocket 中接受的消息

其一措施必须被重写

 

服务端音信发送流程

而服务端要给客户端发送音信,则要求经过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频道,而且都有1个协程(或线程)在监听频道中是还是不是有音讯,壹旦有新闻,就会调用pubsub_manager._handle_emit()主意对本机对应的socket发送对应的音讯,最后是经过socket.io服务器的_emit_internal()主意完成对本机中room为sid的具有socket发送音信的,即便room为None,则正是广播,即对富有连接到本机的富有客户端推送音信。

socket.io服务器发送音讯要基于engine.io音信包装,所以归纳到底依然调用的engine.io套接字中的send()主意。engine.io为种种客户端都会维护一个新闻队列,发送数据都以先存到行列之中待拉取,websocket除了探测帧之外的此外数据帧也都是经过该新闻队列发送。

WebSocketHandler.on_close()

当关闭该 WebSocket 时调用

当连接被彻底关闭并且援助 status code 或 reason phtase 的时候, 能够通过
self.close_code 和 self.close_reason 那七个天性来获取它们

在 4.0 版更改: Added close_code and close_reason attributes. 添加
close_code 和 close_reason 这多个属性

 长连接,参数介绍:

闭馆连接(只分析websocket)

websocket可能非凡关闭的状态多多。比如客户端发了ping后等候pong超时关闭,服务端接收到ping跟上3个ping之间超过了pingTimeout;用的uwsgi的话,uwsgi发送ping,假若在websockets-pong-tolerance(私下认可3秒)内接受不到pong回应,也会倒闭连接;还有借使nginx的proxy_read_timeout配置的比pingInterval小等。

若是或不是客户端主动关闭连接,socket.io就会在连续出错后持续重试以建立连接。重试间隔和重试次数由reconnectionDelayMax(默认5秒)reconnectionAttempts(私下认可向来重连)设定。上面商讨客户端平常关闭的景色,种种极度关闭状态请具体情况具体分析。

客户端主动关闭

壹旦客户端调用socket.close()再接再砺关闭websocket连接,则会头阵送贰个音信41(四:engine.io的message,一: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()不需求再做别的操作(这些操作不是多余的,其效劳见后1节)。

浏览器关闭

向来关闭浏览器发送的是websocket的标准CLOSE音讯,opcode为八。socket.io服务端处理格局基本壹致,由于那种情景下并未发送socket.io的倒闭消息41,socket.io的闭馆操作供给等到engine.io触发的_handle_eio_disconnect()中拍卖,那就是前1节中为啥engine.io服务器后边还要多调用一回
_handle_eio_disconnect()的原委所在。

WebSocketHandler.select_subprotocol(subprotocols)

当二个新的 WebSocket 请求特定子协议(subprotocols)时调用

subprotocols 是三个由一多重能够被客户端正确识别出相应的子协议
(subprotocols)的字符串构成的 list . 那个形式大概会被重载,用来回到 list
中某 个相称字符串, 未有匹配到则赶回 None.
借使未有找到相应的子协议,就算服务端并 不会活动关闭 WebSocket
连接,可是客户端能够选择关闭连接.

(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标识回显回来。

Output

(贰)header:
客户发送websocket握手请求的请求头,{‘head1:value一’,’head二:value2′}。

确立连接

在chrome中打开页面可以见到发了二个请求,分别是:

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,注意命名空间并不会在途径中,而是在参数中传送。第3个请求是polling,EIO是engine.io协议的本子号,t是1个随意字符串,第二个请求时还还从未生成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等;第3个packet类型是四(message),而它的多少内容是0,表示socket.io的CONNECT。而当中的看起来乱码的局地其实是前方提到的payload编码中的长度的编码\x00\x01\x00\x09\xff\x00\x02\xff

  • 第1个请求是轮询请求,借使websocket建立并测试成功(使用内容为probe的ping/pong帧)后,会暂停轮询请求。能够阅览轮询请求平昔hang住到websocket建立并测试成功后才再次回到,响应结果是�ÿ6,前边乱码部分是payload长度编码\x00\x01\xff,前边的数字陆是engine.io的noop新闻。

  • 第叁个请求是websocket握手请求,握手成功后,可以在chrome的Frames个中看到websocket的数量帧交互流程,能够旁观如前方分析,确实是头阵的探测帧,然后是Upgrade帧,接着正是定期的ping/pong帧了。

    2probe
    3probe
    5
    2
    3
    ...
    

WebSocketHandler.write_message(message, binary=False)

将付出的 message 发送到客户端

message 能够是 string 只怕 dict(将会被编码成 json ) 尽管 binary 为
false, message 将会以 utf八 的编码发送; 在 binary 情势下 message 能够是
任何 byte string.

比方总是已经倒闭, 则会触发 WebSocketClosedError

在 三.2 版更改: 添加了 WebSocketClosedError (在后边版本会触发
AttributeError)

在 四.3 版更改: 重返可以被用于 flow control 的 Future.

(3)on_open:在建立Websocket握手时调用的可调用对象,这一个方法只有三个参数,正是此类自己。

客户端发送消息给服务端

要是要发送音讯给服务器,在浏览器输入框输入test365bet亚洲真人,,点击echo按钮,能够看看websocket发送的帧的内容如下,在那之中四是engine.io的message类型标识,2是socket.io的EVENT类型标识,而前面则是事件名称和数据,数据足以是字符串,字典,列表等品种。

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

WebSocketHandler.close(code=None, reason=None)

关闭当前 WebSocket

即使挥手动作成功,socket将会被关闭.

code 可能是一个数字组成的状态码, 选取 PRADOFC 645伍 section 柒.四.一. 概念的值.

reason 只怕是讲述连接关闭的文本新闻. 这一个值被提给客户端,不过不会被
WebSocket 协议单独解释.

在 4.0 版更改: Added the code and reason arguments.

(4)on_message:这几个指标在接到到服务器再次来到的新闻时调用。有八个参数,一个是此类本人,三个是大家从服务器获取的字符串(utf-八格式)。

服务端接收音讯流程

而服务端接收音信并重返二个新的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()})

Configuration

发表评论

电子邮件地址不会被公开。 必填项已用*标注