使用tornado 的 websocket 断开的时候,连接会自动断开是什么原因

请问Websocket断线重连怎么实现的? - Pomelo Club
请问Websocket断线重连怎么实现的?
try catch无法捕获
监听 close 事件
谢谢回复,我的代码是在onclose中 try catch ws.on(&close&,function(event){try(重新new websocket )catch(e){}};请明示使用tonado作为websocket服务器
sudo pip install tornado==4.2b1
#!/usr/bin/python
#-*- coding:utf-8 -*-
import os.path
import tornado.httpserver
import tornado.web
import tornado.ioloop
import tornado.options
import tornado.httpclient
import tornado.websocket
from tornado.ioloop import PeriodicCallback
import MySQLdb
import json
import time
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
class SocketHandler(tornado.websocket.WebSocketHandler):
"""docstring for SocketHandler"""
clients = set()
num_record_firsttime=[0,0,0]#这里面放的是表1,2,3的首次record的条数
num_record=[0,0,0]#这是后续查询的条数,如果对应的值大于上一次,则给浏览器推送消息
table_count=3 #iot_db中表的个数,num1,num2,num3
@staticmethod
def send_to_all(message):
for c in SocketHandler.clients:
c.write_message(json.dumps(message))
def send_hello(self):
SocketHandler.send_to_all({
'type':'sys',
'message':'hello world!',
def open(self):
#self.write_message(json.dumps({
'type': 'sys',
'message': 'Welcome to WebSocket',
#SocketHandler.send_to_all({
'type': 'sys',
'message': str(id(self)) + ' has joined',
SocketHandler.clients.add(self)
#首先查询一次记录条数并且把last 10条记录,发送给chart
for i in range(1,(self.table_count+1)):#表1,2,3
self.num_record_firsttime[(i-1)]=self.mysql_count(i)
print "num%d_record_firsttime=%ld" % (i,self.num_record_firsttime[(i-1)])
#然后周期性质查询,如果结果大于原来的东西,则send_to_all
self.callback = PeriodicCallback(self.mysql_poll, 1000)
self.callback.start()
def on_close(self):
SocketHandler.clients.remove(self)
#SocketHandler.send_to_all({
'type': 'sys',
'message': str(id(self)) + ' has left',
print (u'message: '+str(id(self))+u' has left')
self.callback.stop()
def on_message(self, message):
SocketHandler.send_to_all({
'type': 'user',
'id': id(self),
'message': message,
def check_origin(self,origin):
return True
#数据库操作相关开始
def mysql_count(self,i):
#print type(i)
sql=u'select id from num'+str(i)+ u' order by id desc limit 1'
conn=MySQLdb.connect(host="localhost",port=3306,user="root",passwd="wangshen",db="iot_db",charset="utf8")
cursor=conn.cursor()
#进来第一次获取记录条数
n=cursor.execute(sql)
rows=cursor.fetchone()
#print type(rows)
#print rows
if(n&10):#如果记录多于10条,就取后10条,否则,取全部
#按照n,将n-10,到n的数据给chart,在这两条语句之间有新数据到来的机会不大吧!???
sql=u'select * from num'+str(i)+u' where id & '+str(n-10)+u' and id &= '+ str(n)
sql=u'select * from num'+str(i)+u' where id & '+str(0)+u' and id &= '+ str(n)
cursor.execute(sql)
rows=cursor.fetchall()
rowarray_list=[]
print rows
index_tuple=('id','times','temp','ph','rsvd','do','addr')
time_tuple=(100,10,1000)#倍数
for row_tuple in rows:
print len(row_tuple)
row_dict={'id':0,'times':'','temp':0,'ph':0,'rsvd':0,'do':0,'addr':''}
for ele in range(0,1):
row_dict[index_tuple[ele]]=row_tuple[ele]#id直接加入
for ele in range(1,2):
#x=time.localtime(row_tuple[ele])
#row_dict[index_tuple[ele]]=time.strftime('%Y-%m-%dT%H:%M:%S',x)#时间戳转化为相应的字符串后加入dict
row_dict[index_tuple[ele]]=row_tuple[ele]#时间戳转化为相应的字符串后加入dict
for ele in range(2,5):#从温度开始,除以相应的倍数,恢复真是的温度值
row_dict[index_tuple[ele]]=float(row_tuple[ele])/time_tuple[ele-2]#加入dict
for ele in range(5,len(row_tuple)):#addr 直接写入
row_dict[index_tuple[ele]]=row_tuple[ele]#加入dict
#print row_dict
rowarray_list.append(row_dict)
#发送给chart,这里的话,应该是用write——message吧,不能用send_to_all因为,这是此client第一次过来,不能将这个结果影响到其他的client,在发送最新的数据的时候,使用也应该使用write_message,因为各个client去获取值,不是同步的
print "lastest 10 records:"
print (json.dumps(rowarray_list))
self.write_message(json.dumps(rowarray_list))
cursor.close()
conn.close()
except MySQLdb.Error as e:
#message=e
def mysql_poll(self):
conn=MySQLdb.connect(host="localhost",port=3306,user="root",passwd="wangshen",db="iot_db",charset="utf8")
for i in range(1,(self.table_count+1)):#同理依次查询表num1,表num2,表num3
cursor=conn.cursor()
sql=u'select id from num'+str(i)+u' order by id desc limit 1'
cursor.execute(sql)
rows=cursor.fetchone()# 当表是空的时候,rows值为notype
self.num_record[(i-1)]=rows[0]
self.num_record[(i-1)]=0
print "[%s] id:%s--&num%d_current record=%ld" % (time.time(),str(id(self)),i,self.num_record[(i-1)])
#说明有新数据
if(self.num_record[(i-1)]&self.num_record_firsttime[(i-1)]):
print "new data coming:"
#这里的话如果是一个的话,就使用这个,但是js里面的chart就必须自己写一个队列。不断的将旧数据pop出去,将新数据push进来,如果不想改动js那么就是使用每发现新的数据更新,就直接从数据库取10个数据。无论数据采集有多快,也不会达到每一秒会进来10个数据吧!
#sql=u'select id,times,temp,ph,do from datatest where id & '+str(self.num_record_firsttime)+u' and id &= '+ str(self.num_record)
if(self.num_record[(i-1)]&10):#只有多余10条,你才能取出10条记录,否则怎么办法?
sql=u'select * from num'+str(i)+u' where id & '+str(self.num_record[(i-1)]-10)+u' and id &= '+ str(self.num_record[(i-1)])
sql=u'select * from num'+str(i)+u' where id & '+str(0)+u' and id &= '+ str(self.num_record[(i-1)])
cursor.execute(sql)
rows=cursor.fetchall()
rowarray_list=[]
index_tuple=('id','times','temp','ph','rsvd','do','addr')
time_tuple=(100,10,1000)#倍数
for row_tuple in rows:
row_dict={'id':0,'times':'','temp':0,'ph':0,'rsvd':0,'do':0,'addr':''}
for ele in range(0,1):
row_dict[index_tuple[ele]]=row_tuple[ele]#id直接加入
for ele in range(1,2):
#x=time.localtime(row_tuple[ele])
#row_dict[index_tuple[ele]]=time.strftime('%Y-%m-%dT%H:%M:%S',x)#时间戳转化为相应的字符串后加入dict
row_dict[index_tuple[ele]]=row_tuple[ele]#时间戳转化为相应的字符串后加入dict
for ele in range(2,5):#从温度开始,除以相应的倍数,恢复真是的温度值
row_dict[index_tuple[ele]]=float(row_tuple[ele])/time_tuple[ele-2]#加入dict
for ele in range(5,len(row_tuple)):#addr 直接写入
row_dict[index_tuple[ele]]=row_tuple[ele]#加入dict
#print row_dict
rowarray_list.append(row_dict)
#把最新的数据发送给chart
if(self.num_record[(i-1)]&self.num_record_firsttime[(i-1)]):
print json.dumps(rowarray_list)
self.write_message(json.dumps(rowarray_list))
self.num_record_firsttime[(i-1)]=self.num_record[(i-1)]
cursor.close()
#mit() 这个只是查询,不提交任何更改
conn.close()
except MySQLdb.Error as e:
#message=e
#return message
#数据库操作相关结束
if __name__ == '__main__':
app = tornado.web.Application(
handlers=[
(r"/", IndexHandler),
(r"/chat", SocketHandler)
debug = True,
template_path = os.path.join(os.path.dirname(__file__), "templates"),
static_path = os.path.join(os.path.dirname(__file__), "static")
app.listen(8000)
tornado.ioloop.IOLoop.instance().start()
客户段的js代码如下:
var ws = new WebSocket("ws://localhost:8000/chat");
ws.onmessage = function(event) {
console.log(event);
function send() {
ws.send(document.getElementById('chat').value );
ws.onopen=function(){
alert("open");
ws.onclose=function(){
alert("close");
ws.error=function(){
alert("error happended");
直接使用localhost:8000/chat可以访问,但是如果要是想直接使用localhost,需要配置nginx转发websocket规则.
location /chat/ {
#proxy_pass http://unix:/home/wangshen/download_firefox/django-websocket-redis-master/examples/web.
proxy_pass http://0.0.0.0:8000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_
proxy_set_header Connection $connection_
map $http_upgrade $connection_upgrade {
此条目发表在, 分类目录。将加入收藏夹。
2016年十月 &(6)
2016年九月 &(13)
2016年八月 &(6)
2016年七月 &(1)
2016年六月 &(2)
2016年五月 &(4)
2016年四月 &(17)
2016年三月 &(45)
2016年二月 &(8)
2016年一月 &(11)
2015年十二月 &(19)
2015年十一月 &(20)
2015年十月 &(10)
2015年九月 &(9)
2015年八月 &(24)
2015年七月 &(15)
2015年六月 &(32)
2015年五月 &(20)
2015年四月 &(33)
2015年三月 &(44)
2015年二月 &(23)
2015年一月 &(74)
2014年十二月 &(88)
2014年十一月 &(21)
2014年九月 &(6)
2016年十月
10111213141516
17181920212223
24252627282930
分类目录分类目录
选择分类目录
一些问题&&解决方法&&(24)
人工智能&&(15)
&&&专家系统&&(14)
嵌入式&&(1)
bootstrap&&(5)
c & c++&&(9)
command&&(39)
Contiki&&(9)
算法相关&&(4)
网络相关&&(20)
Django&&(25)
driver&&(2)
emotion&&(12)
读书笔记&&(26)
freeshell&&(11)
Html&&(14)
makefile&&(1)
nginx&&(4)
paper notes&&(4)
python&&(61)
shell脚本&&(15)
software configure&&(51)
the knowledge u need know&&(104)
ubuntu&&(83)
windows&&(3)
未分类&&(105)
收集的信息&&(10)<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
您的访问请求被拒绝 403 Forbidden - ITeye技术社区
您的访问请求被拒绝
亲爱的会员,您的IP地址所在网段被ITeye拒绝服务,这可能是以下两种情况导致:
一、您所在的网段内有网络爬虫大量抓取ITeye网页,为保证其他人流畅的访问ITeye,该网段被ITeye拒绝
二、您通过某个代理服务器访问ITeye网站,该代理服务器被网络爬虫利用,大量抓取ITeye网页
请您点击按钮解除封锁&[tornado]使用webscoket的使用总是403错误
[tornado]使用webscoket的使用总是403错误
[摘要:应用的tornado版本为4.0 背景: PS D:CodeHousetornadowebsocket python .ws_app.py WARNING:tornado.access:403 GET /ws (::1) 1.00ms WARNING:tornado.access:403 GET /ws (::1) 1.00ms 前台: WebSocket connection to 'ws://localhost:8080/ws]
使用的tornado版本为4.0&#43;
PS D:CodeHousetornadowebsocket& python .ws_app.py
WARNING:tornado.access:403 GET /ws (::1) 1.00ms
WARNING:tornado.access:403 GET /ws (::1) 1.00ms
WebSocket connection to 'ws://localhost:8080/ws' failed: Error during WebSocket handshake: Unexpected response code: 403&
修改方法:
针对websocket处理类重写同源检查的方法:
class WebSocketHandler(tornado.websocket.WebSocketHandler):
def check_origin(self, origin):
return True
这个据说是tornado 4.0版本以后新增加的一个特性。
本文出自&“orangleliu笔记本”&博客,转载请务必保留此出处http://blog.csdn.net/orangleliu/article/details/
作者orangleliu&采用署名-非商业性使用-相同方式共享协议
感谢关注 Ithao123Tornado频道,是专门为互联网人打造的学习交流平台,全面满足互联网人工作与学习需求,更多互联网资讯尽在 IThao123!
Hadoop是一个由Apache基金会所开发的分布式系统基础架构。
用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储。
Hadoop实现了一个分布式文件系统(Hadoop Distributed File System),简称HDFS。HDFS有高容错性的特点,并且设计用来部署在低廉的(low-cost)硬件上;而且它提供高吞吐量(high throughput)来访问应用程序的数据,适合那些有着超大数据集(large data set)的应用程序。HDFS放宽了(relax)POSIX的要求,可以以流的形式访问(streaming access)文件系统中的数据。
Hadoop的框架最核心的设计就是:HDFS和MapReduce。HDFS为海量的数据提供了存储,则MapReduce为海量的数据提供了计算。
随着国内互联网的发展,产品经理岗位需求大幅增加,在国内,从事产品工作的大部分岗位为产品经理,其实现实中,很多从事产品工作的岗位是不能称为产品经理,主要原因是对产品经理的职责不明确,那产品经理的职责有哪些,本专题将详细介绍产品经理的主要职责
Swift是Apple在WWDC2014所发布的一门编程语言,用来撰写OS X和iOS应用程序[1]。在设计Swift时.就有意和Objective-C共存,Objective-C是Apple操作系统在导入Swift前使用的编程语言
Swift是供iOS和OS X应用编程的新编程语言,基于C和Objective-C,而却没有C的一些兼容约束。Swift采用了安全的编程模式和添加现代的功能来使得编程更加简单、灵活和有趣。界面则基于广受人民群众爱戴的Cocoa和Cocoa Touch框架,展示了软件开发的新方向。
PHP(外文名:PHP: Hypertext Preprocessor,中文名:“超文本预处理器”)是一种通用开源脚本语言。语法吸收了C语言、Java和Perl的特点,利于学习,使用广泛,主要适用于Web开发领域。PHP 独特的语法混合了C、Java、Perl以及PHP自创的语法。它可以比CGI或者Perl更快速地执行动态网页。用PHP做出的动态页面与其他的编程语言相比,PHP是将程序嵌入到HTML(标准通用标记语言下的一个应用)文档中去执行,执行效率比完全生成HTML标记的CGI要高许多;PHP还可以执行编译后代码,编译可以达到加密和优化代码运行,使代码运行更快。
IThao123周刊tornado如何实现异步websocket推送?
在实现一个websocket的推送,主要就是后端推送pub/sub了redis的信息到前端。然而自己实现的推送开了两个chrome tab就有一个一直pending,可我明明在redis阻塞的部分用了gen.coroutine,一直不明白。想请问一下如何改进?&br&&br&主要实现代码:&br&&div class=&highlight&&&pre&&code class=&language-python&&&span class=&k&&class&/span& &span class=&nc&&MessageHandler&/span&&span class=&p&&(&/span&&span class=&n&&websocket&/span&&span class=&o&&.&/span&&span class=&n&&WebSocketHandler&/span&&span class=&p&&):&/span&
&span class=&k&&def&/span& &span class=&nf&&check_origin&/span&&span class=&p&&(&/span&&span class=&bp&&self&/span&&span class=&p&&,&/span& &span class=&n&&origin&/span&&span class=&p&&):&/span&
&span class=&k&&return&/span& &span class=&bp&&True&/span&
&span class=&nd&&@gen.coroutine&/span&
&span class=&k&&def&/span& &span class=&nf&&handler&/span&&span class=&p&&(&/span&&span class=&bp&&self&/span&&span class=&p&&):&/span&
&span class=&k&&def&/span& &span class=&nf&&cb&/span&&span class=&p&&(&/span&&span class=&n&&it&/span&&span class=&p&&,&/span& &span class=&n&&callback&/span&&span class=&p&&):&/span&
&span class=&k&&try&/span&&span class=&p&&:&/span&
&span class=&n&&value&/span& &span class=&o&&=&/span& &span class=&n&&it&/span&&span class=&o&&.&/span&&span class=&n&&next&/span&&span class=&p&&()&/span&
&span class=&k&&except&/span&&span class=&p&&:&/span&
&span class=&n&&value&/span& &span class=&o&&=&/span& &span class=&bp&&None&/span&
&span class=&n&&callback&/span&&span class=&p&&(&/span&&span class=&n&&value&/span&&span class=&p&&)&/span&
&span class=&n&&conn&/span& &span class=&o&&=&/span& &span class=&n&&redis&/span&&span class=&o&&.&/span&&span class=&n&&StrictRedis&/span&&span class=&p&&(&/span&&span class=&o&&**&/span&&span class=&n&&REDIS_CONFIG&/span&&span class=&p&&)&/span&
&span class=&n&&ps&/span& &span class=&o&&=&/span& &span class=&n&&conn&/span&&span class=&o&&.&/span&&span class=&n&&pubsub&/span&&span class=&p&&()&/span&
&span class=&n&&ps&/span&&span class=&o&&.&/span&&span class=&n&&subscribe&/span&&span class=&p&&(&/span&&span class=&n&&channel&/span&&span class=&p&&)&/span&
&span class=&k&&while&/span& &span class=&bp&&True&/span&&span class=&p&&:&/span&
&span class=&n&&item&/span& &span class=&o&&=&/span& &span class=&k&&yield&/span& &span class=&n&&gen&/span&&span class=&o&&.&/span&&span class=&n&&Task&/span&&span class=&p&&(&/span&&span class=&n&&cb&/span&&span class=&p&&,&/span& &span class=&n&&ps&/span&&span class=&o&&.&/span&&span class=&n&&listen&/span&&span class=&p&&())&/span&
&span class=&k&&print&/span& &span class=&n&&item&/span&
&span class=&k&&if&/span& &span class=&n&&item&/span&&span class=&p&&:&/span&
&span class=&k&&if&/span& &span class=&n&&item&/span&&span class=&p&&[&/span&&span class=&s&&'type'&/span&&span class=&p&&]&/span& &span class=&o&&==&/span& &span class=&s&&'message'&/span&&span class=&p&&:&/span&
&span class=&bp&&self&/span&&span class=&o&&.&/span&&span class=&n&&write_message&/span&&span class=&p&&(&/span&&span class=&n&&item&/span&&span class=&p&&[&/span&&span class=&s&&&data&&/span&&span class=&p&&])&/span&
&span class=&k&&else&/span&&span class=&p&&:&/span&
&span class=&k&&break&/span&
&span class=&k&&def&/span& &span class=&nf&&open&/span&&span class=&p&&(&/span&&span class=&bp&&self&/span&&span class=&p&&):&/span&
&span class=&k&&print&/span& &span class=&s&&&New WebSocket connection&&/span&
&span class=&n&&ioloop&/span&&span class=&o&&.&/span&&span class=&n&&IOLoop&/span&&span class=&o&&.&/span&&span class=&n&&current&/span&&span class=&p&&()&/span&&span class=&o&&.&/span&&span class=&n&&spawn_callback&/span&&span class=&p&&(&/span&&span class=&bp&&self&/span&&span class=&o&&.&/span&&span class=&n&&handler&/span&&span class=&p&&)&/span&
&span class=&k&&def&/span& &span class=&nf&&on_message&/span&&span class=&p&&(&/span&&span class=&bp&&self&/span&&span class=&p&&,&/span& &span class=&n&&message&/span&&span class=&p&&):&/span&
&span class=&k&&pass&/span&
&span class=&k&&def&/span& &span class=&nf&&on_close&/span&&span class=&p&&(&/span&&span class=&bp&&self&/span&&span class=&p&&):&/span&
&span class=&k&&print&/span&&span class=&p&&(&/span&&span class=&s&&&WebSocket close&&/span&&span class=&p&&)&/span&
&span class=&bp&&self&/span&&span class=&o&&.&/span&&span class=&n&&close&/span&&span class=&p&&()&/span&
&span class=&n&&app&/span& &span class=&o&&=&/span& &span class=&n&&web&/span&&span class=&o&&.&/span&&span class=&n&&Application&/span&&span class=&p&&([&/span&
&span class=&p&&(&/span&&span class=&s&&r'/ws'&/span&&span class=&p&&,&/span& &span class=&n&&MessageHandler&/span&&span class=&p&&),&/span&
&span class=&p&&])&/span&
&span class=&k&&if&/span& &span class=&n&&__name__&/span& &span class=&o&&==&/span& &span class=&s&&'__main__'&/span&&span class=&p&&:&/span&
&span class=&n&&app&/span&&span class=&o&&.&/span&&span class=&n&&listen&/span&&span class=&p&&(&/span&&span class=&mi&&8888&/span&&span class=&p&&)&/span&
&span class=&n&&ioloop&/span&&span class=&o&&.&/span&&span class=&n&&IOLoop&/span&&span class=&o&&.&/span&&span class=&n&&current&/span&&span class=&p&&()&/span&&span class=&o&&.&/span&&span class=&n&&start&/span&&span class=&p&&()&/span&
&/code&&/pre&&/div&
在实现一个websocket的推送,主要就是后端推送pub/sub了redis的信息到前端。然而自己实现的推送开了两个chrome tab就有一个一直pending,可我明明在redis阻塞的部分用了gen.coroutine,一直不明白。想请问一下如何改进?主要实现代码:class MessageHandler(websocket.WebSocketHandler):
def check_origin(self, origin):
return True
@gen.coroutine
def handler(self):
def cb(it, callback):
value = it.next()
value = None
callback(value)
conn = redis.StrictRedis(**REDIS_CONFIG)
ps = conn.pubsub()
ps.subscribe(channel)
while True:
item = yield gen.Task(cb, ps.listen())
print item
if item['type'] == 'message':
self.write_message(item["data"])
def open(self):
print "New WebSocket connection"
ioloop.IOLoop.current().spawn_callback(self.handler)
def on_message(self, message):
def on_close(self):
print("WebSocket close")
self.close()
app = web.Application([
(r'/ws', MessageHandler),
if __name__ == '__main__':
app.listen(8888)
ioloop.IOLoop.current().start()
& 可我明明在redis阻塞的部分用了gen.coroutine这样并不能使阻塞操作变得不阻塞。你需要用
这种东西,或者用线程池(tornado.gen.run_on_executor)。
Comzyh解释的很好。首先要理解gen.coroutine的意义,这个装饰器其实并不能让阻塞代码变成非阻塞,而是将本身需要异步回调的代码写成同步风格。它并不能改变什么,而是让你可以换一种代码风格增强代码可读性。整个tornado生态是基于它IOLoop的,所以不是拿到任何异步库都能直接使用,我们都知道异步是基于事件的,而事件的通知管理则是依靠事件轮询设施eventloop的它通常构建于select、epoll、kq,这个IOloop就是事件轮询设施,一切不能被它接纳的异步库都是无法集成到tornado的。所以如果你想把redis集成到tornado就必须是IOLoop能够接纳的非阻塞redis——其实您的需求也不一定需要redis作为消息中间件这么重型的解决方案,如果您寻求服务端通知,zeromq是个非常轻量化的解决方案。在下不才,自己开发过一套tornado集成zeromq的集群完整解决方案——涵盖tornado网关,消息发布中心,消息发布sdk三大核心组件,届时如果有需要可以仿造sdk开发多语言的sdk。sdk给消息发布中心inform headquarter(ihq)下达主题消息,客户端连接tornado可以发送订阅某主题。性能还行,普通i5的机子在没有tcp参数调优的情况下能扛起两万左右连接数。注释中的todo,您如果有能力优化的话,相信性能又可以提升不少。
value = it.next()
这里 it.next() 仍然是阻塞的,Tornado 的异步模型应该是在你的 while True 里面 item = yield 一个future 才对,虽然你 yield 了一个 Task,但是这个 Task 本身不是异步的(等待的时候不能被打断,不能将控制权交给IOLoop)。如果你要非阻塞的listen我能想到的有三种方案使用非阻塞的 Redis 客户端一个 listen 开个线程,拿到数据塞到队列里去,让 Tornado 去 get,因为你要异步 get, 要用 tornado 的 Queue,但是这个Queue 貌似本身不是线程安全的,所以你还要自己加锁用一个 Redis 连接 SUB 所有消息,然后分发,但是程序会阻塞在 SUBSCRIBE 上,你新来的WebSocket 连接都不能处理 open了总的来说用个非阻塞的 Redis 客户端比较靠谱,比如这个例子:
已有帐号?
无法登录?
社交帐号登录}

我要回帖

更多关于 tornado的websocket 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信