socket.io 原理研讨

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

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

python3知识点

jquery.min.js

图片 1

 

服务端音信采用流程

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

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

}

 

服务端发送音信到客商端

服务端发送音讯通过 flask_socketio提供的emit方法完成,如前后生可畏节分析的,最后照旧经过的engine.io包装成engine.io的音讯格式后发生。

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

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


 

在类型中用到socket.io加强时推送,遂花了点时间看了socket.io完毕,做个简易解析,如有错漏,应接指正。

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

参谋资料

(11)subprotocols:生机勃勃组可用的子公约,默认为空。

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的职业流程。

 

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种档期的顺序:

而Payload是指意气风发雨后玉兰片绑定到协同的编码后的Packet,它只用在poll中,websocket里面使用websocket帧里面包车型地铁Payload字段来传输数据。要是客商端不扶助XHR2,则payload格式如下,此中length是数额包Packet的长度,而packet则是编码后的多寡包内容。

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

若扶持XHENVISION2,则payload中内容总体以二进制编码,在那之中第二位0表示字符串,1意味二进制数据,而背后跟着的数字则是代表packet长度,然后以\xff结尾。要是三个尺寸为109的字符类型的数据包,则前边长度编码是 \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会通过自行重连机制再度连接。

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

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诉求底部是合作的(见中华VFC2616)。

## 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

WebSocket商讨使用帧(Frame)收发数据,帧格式如下。基于安全考虑衡量,客商端发送给服务端的帧必得经过4字节的掩码(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 ...                :
     +---------------------------------------------------------------+

帧分为调控帧和数据帧,调节帧无法分片,数据帧能够分片。重要字段表达如下:

短连接:

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。

我们看源代码,会意识这么意气风发断代码:

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超时而不仅重连。

 

顾客端发送消息给服务端

万意气风发要发送音讯给服务器,在浏览器输入框输入test,点击echo开关,能够看见websocket发送的帧的故事情节如下,当中4是engine.io的message类型标志,2是socket.io的EVENT类型标记,而前边则是事件名称和数量,数据可以是字符串,字典,列表等种类。

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

ping的逾期时间,要大于ping间隔时间

成立连接

在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编码中的长度的编码\x00\x01\x00\x09\xff\x00\x02\xff

(2)header: 客户发送websocket握手央求的伏乞头,{'head1:value1','head2:value2'}。

服务端新闻发送流程

而服务端要给顾客端发送音信,则须求经过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除了探测帧之外的任何数据帧也都以透过该消息队列发送。

ping_interval:自动发送“ping”命令,各种钦定的日子(秒),若是设置为0,则不会自行发送。

服务端选择新闻流程

而服务端选择音讯并回到一个新的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()})
ws.run_forever(ping_interval=60,ping_timeout=5)

#ping_interval心跳发送间隔时间

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

关闭连接(只深入分析websocket)

websocket恐怕特别关闭的事态多多。比方顾客端发了ping后等候pong超时关闭,服务端选择到ping跟上八个ping之间当先了pingTimeout;用的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()的缘故所在。

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)

关闭连接

客商端要风雨无阻关闭连接,在JS中调用 socket.close() 就能够,那个时候出殡的数额包为 41,当中4表示的是engine.io的新闻类型message,而数据1则是指的socket.io的音讯类型disconnect,关闭流程见上意气风发章的证实。

(5)on_error:那个目的在碰着错误时调用,有七个参数,第八个是此类自身,第三个是不行对象。

接连几日来创设

首先,顾客端会发送多少个polling央浼来确立连接。那时候的央求参数未有sid,表示要安家立业连接。 engine.io服务器通过handle_get_request()handle_post_request()方式来分别管理早先化连接以致长轮询中的 GET 和 POST 央浼。

socket.io在初叶化时便登记了3个事件到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服务器会发送叁个OPEN类型的多少包给客商端,接着会触发socket.io服务器的connect事件。

客商端第三次三回九转的时候,socket.io也要做一些发轫化的干活,那是在socket.io服务器的_handle_eio_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也许用其它浏览器插件来考查)。