Tornado WebSocket简单聊天
Tornado实现了对socket的封装:tornado.web.RequestHandler
工程目录:

1、主程序 manage.py
import tornado.web
import tornado.httpserver
from tornado.options import define, options, parse_command_line from chat.views import IndexHandler, LoginHandler, ChatHandler
from util.settings import TEMPLATE_PATH, STATIC_PATH define("port", default=8180, help='run on the port', type=int) def make_app():
return tornado.web.Application(handlers=[
(r'/', IndexHandler),
(r'/login', LoginHandler),
(r'/chat', ChatHandler),
],
pycket={
'engine': 'redis',
'storage': {
'host': 'fot.redis.cache.net',
'port': 6379,
'password': 'yKigE3ZF0mGBSP4/M=',
'db_sessions': 5,
'db_notifications': 11,
'max_connections': 2 ** 31,
},
'cookies': {
'expires_days': 30,
'max_age': 100
},
},
login_url='/login',
template_path=TEMPLATE_PATH,
static_path=STATIC_PATH,
debug=True,
cookie_secret='cqVJzSSjQgWzKtpHMd4NaSeEa6yTy0qRicyeUDIMSjo='
) if __name__ == '__main__':
tornado.options.parse_command_line()
app = make_app()
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.current().start()
2、配置 settings.py
import os BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) TEMPLATE_PATH = os.path.join(BASE_DIR, 'templates') STATIC_PATH = os.path.join(BASE_DIR, 'static')
3、聊天程序 views.py
# -*- coding: utf-8 -*-
import datetime
import json import tornado.web
import tornado.websocket
from tornado.web import authenticated # 导入装饰器
from pycket.session import SessionMixin # 设置BaseHandler类,重写函数get_current_user
class BaseHandler(tornado.web.RequestHandler, SessionMixin):
def get_current_user(self): # 前面有绿色小圆圈带个o,再加一个箭头表示重写
current_user = self.session.get('user') # 获取加密的cookie
if current_user:
return current_user
return None # 基类
class BaseWebSocketHandler(tornado.websocket.WebSocketHandler, SessionMixin):
def get_current_user(self):
current_user = self.session.get('user') if current_user:
return current_user
return None # 跳转
class IndexHandler(BaseHandler):
@authenticated # 内置装饰器,检查是否登录
def get(self):
self.render('chat.html') class LoginHandler(BaseHandler):
def get(self):
self.render('index.html') # 跳转页面带上获取的参数 def post(self, *args, **kwargs):
user = self.get_argument('nickname', '')
if user: self.session.set('user', user) # 设置加密cookie
self.redirect('/') # 跳转到之前的路由
else:
self.render('index.html') class ChatHandler(BaseWebSocketHandler):
# 定义接收/发送聊天消息的视图处理类,继承自websocket的WebSocketHandler
# 定义一个集合,用来保存在线的所有用户 online_users = set() # 从客户端获取cookie信息 # 重写open方法,当有新的聊天用户进入的时候自动触发该函数
def open(self): # 新用户上线,加入集合
self.online_users.add(self)
# 将新用户加入的信息发送给所有用户 for user in self.online_users:
user.write_message('[%s]join room' % self.current_user) # 重写on_message方法,当聊天消息有更新时自动触发的函数
def on_message(self, message):
msgobj = {'msg': message} for user in self.online_users:
msgobj['key'] = '%s-%s-sea: ' % (self.current_user, datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
user.write_message(json.dumps(msgobj)) # 重写on_close方法,当有用户离开时自动触发的函数
def on_close(self):
# 移除用户
self.online_users.remove(self)
for user in self.online_users:
user.write_message('[%s]remove room' % self.current_user) # 重写check_origin方法, 解决WebSocket的跨域请求
def check_origin(self, origin):
return True
4、前端登录 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>聊天室登录首页</title>
<script src="../static/jquery-3.4.1.js"></script>
</head>
<body>
<div>
<div style="width:60%;">
<div>
聊天室个人登录
</div>
<div>
<form method="post" action="/login" style="width:80%">
<p>昵称:<input type="text" placeholder="请输入昵称" name="nickname"></p>
<button type="submit">登录</button>
</form>
</div>
</div>
</div>
</body>
</html>
5、前端聊天室 chat.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> WebSocket </title>
<style>
*{
margin: 0;
padding: 0;
}
.box{
width: 800px;
margin-left: auto;
margin-right: auto;
margin-top: 25px;
}
#text{
width: 685px;
height: 130px;
border: 1px solid skyblue;
border-radius: 10px;
font-size: 20px;
text-indent: 1em;
resize:none;
outline: none;
}
#text::placeholder{
color: skyblue;
}
.btn{
width: 100px;
margin: -27px 0 0px 8px;
}
#messages{
padding-left: 10px;
font-size: 25px;
}
#messages li{
list-style: none;
color: #000;
line-height: 30px;
font-size: 18px; }
</style>
</head>
<body>
<div class="box">
<div>
<textarea id="text" placeholder="请输入您的内容"></textarea>
<a href="javascript:WebSocketSend();" class="btn btn-primary">发送</a>
</div>
<ul id="messages">
</ul>
</div>
<script src="../static/jquery-3.4.1.js"></script>
<script type="text/javascript">
var mes = document.getElementById('messages');
var wsUrl = "ws://"+ window.location.host +"/chat";
var Socket = ''; if('WebSocket' in window){ /*判断浏览器是否支持WebSocket接口*/
/*创建创建 WebSocket 对象,协议本身使用新的ws://URL格式*/ createWebSocket();
}else{
/*浏览器不支持 WebSocket*/
alert("您的浏览器不支持 WebSocket!");
} function createWebSocket() {
try {
Socket = new WebSocket(wsUrl); init();
} catch(e) {
console.log('catch');
reconnect(wsUrl); //调用心跳
}
} function init() {
/*连接建立时触发*/
Socket.onopen = function () {
alert("连接已建立,可以进行通信"); heartCheck.start(); //调用心跳
};
/*客户端接收服务端数据时触发*/
Socket.onmessage = function (ev) {
var received_msg = ev.data; /*接受消息*/
var jopmsg = '';
try {
received_msg = JSON.parse(received_msg);
console.log(received_msg['msg']); if(received_msg['msg'] == '121')
jopmsg = '121'; received_msg = received_msg['key'] + received_msg['msg'];
}catch (e) { } //发送信息为121时为心跳,不记录到页面(只是个约定)
if(jopmsg !== '121'){
var aLi = "<li>" + received_msg + "</li>";
mes.innerHTML += aLi;
} heartCheck.start(); //调用心跳
};
/*连接关闭时触发*/
Socket.onclose = function () {
mes.innerHTML += "<br>连接已经关闭..."; reconnect(wsUrl); //关闭连接重新连接
};
} function WebSocketSend() {
/*form 里的Dom元素(input select checkbox textarea radio)都是value*/
var send_msg = document.getElementById('text').value;
//或者JQ中获取
// var send_msg = $("#text").val();
/*使用连接发送消息*/
Socket.send(send_msg);
$("#text").val('');
} var lockReconnect = false;//避免重复连接
function reconnect(url) {
if(lockReconnect) {
return true;
}; lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
setTimeout(function () {
createWebSocket(url);
lockReconnect = false;
}, 5000); } //心跳检测
var heartCheck = {
timeout: 10000, //每隔三秒发送心跳
num: 3, //3次心跳均未响应重连
timeoutObj: null,
serverTimeoutObj: null,
start: function(){
var _this = this;
var _num = this.num;
this.timeoutObj && clearTimeout(this.timeoutObj);
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
this.timeoutObj = setTimeout(function(){
//这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常
Socket.send("121"); // 心跳包
_num--;
//计算答复的超时次数
if(_num === 0) {
Socket.colse();
}
}, this.timeout)
}
} </script>
</body>
</html>
6、运行效果: 输入 http://127.0.0.1:8180

7、部署到线上参考:https://www.cnblogs.com/cj8988/p/11288892.html
注 :nginx需要添加一个配置 (在 server {} 里添加下面配置)
location /chat {
proxy_pass http://tornados;
proxy_http_version 1.1;
proxy_connect_timeout 4s; #配置点1
proxy_read_timeout 120s; #配置点2,如果没效,可以考虑这个时间配置长一点
proxy_send_timeout 120s;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
8、注意,由于nginx超时问题,过段时间websocket会自动断开,所有前端需要设置心跳。
前端 chat.html 中 :
//心跳检测
var heartCheck = {
timeout: 10000, //每隔三秒发送心跳
num: 3, //3次心跳均未响应重连
timeoutObj: null,
serverTimeoutObj: null,
start: function(){
var _this = this;
var _num = this.num;
this.timeoutObj && clearTimeout(this.timeoutObj);
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
this.timeoutObj = setTimeout(function(){
//这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常
Socket.send("121"); // 心跳包
_num--;
//计算答复的超时次数
if(_num === 0) {
Socket.colse();
}
}, this.timeout)
}
}
在需要的地方调用:
heartCheck.start(); 参考文档:
https://www.jianshu.com/p/93b1788f055c
https://www.lishuaishuai.com/html/759.html https://www.cnblogs.com/cj8988/p/11288892.html
Tornado WebSocket简单聊天的更多相关文章
- python tornado websocket 多聊天室(返回消息给部分连接者)
python tornado 构建多个聊天室, 多个聊天室之间相互独立, 实现服务器端将消息返回给相应的部分客户端! chatHome.py // 服务器端, 渲染主页 --> 聊天室建立web ...
- [tornado]websocket 最简单demo
想法 前两天想看看django 长轮询或者是websocket的方案,发现都不太好使. tornado很适合做这个工作,于是找了些资料,参照了做了个最简单demo,以便备用. 具体的概念就不说了,to ...
- tornado websocket聊天室
1.app.py #!/usr/bin/env python # -*- coding:utf-8 -*- import uuid import json import tornado.ioloop ...
- websocket简单入门
今天说起及时通信的时候,突然被问到时用推的方式,还是定时接受的方式,由于之前页面都是用传统的ajax处理,可能对ajax的定时获取根深蒂固了,所以一时之间没有相同怎么会出现推的方式呢?当被提及webs ...
- SpringBoot 搭建简单聊天室
SpringBoot 搭建简单聊天室(queue 点对点) 1.引用 SpringBoot 搭建 WebSocket 链接 https://www.cnblogs.com/yi1036943655/p ...
- 利用socket.io+nodejs打造简单聊天室
代码地址如下:http://www.demodashi.com/demo/11579.html 界面展示: 首先展示demo的结果界面,只是简单消息的发送和接收,包括发送文字和发送图片. ws说明: ...
- Ext JS学习第十六天 事件机制event(一) DotNet进阶系列(持续更新) 第一节:.Net版基于WebSocket的聊天室样例 第十五节:深入理解async和await的作用及各种适用场景和用法 第十五节:深入理解async和await的作用及各种适用场景和用法 前端自动化准备和详细配置(NVM、NPM/CNPM、NodeJs、NRM、WebPack、Gulp/Grunt、G
code&monkey Ext JS学习第十六天 事件机制event(一) 此文用来记录学习笔记: 休息了好几天,从今天开始继续保持更新,鞭策自己学习 今天我们来说一说什么是事件,对于事件 ...
- Websocket 简单对话:静态网页与pycharm对话
WebSocket websocket 是一种在单个Tcp连接上进行双全工通信的协议.websocket通信协议于2011年被IETF定为标准RFC6455,并 由RFc7936补充规范.WebSoc ...
- Tornado项目简单创建
Tornado是使用Python编写的一个强大的.可扩展的Web服务器.它在处理严峻的网络流量时表现得足够强健,但却在创建和编写时有着足够的轻量级,并能够被用在大量的应用和工具中. tornado技术 ...
随机推荐
- codeforces997C
Sky Full of Stars CodeForces - 997C On one of the planets of Solar system, in Atmosphere University, ...
- Django基础之Session
1. Session的由来 Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的 ...
- jQuery动画之显示隐藏动画
1. 显示动画 以下面一个代码示例: <!doctype html> <html lang="en"> <head> <meta char ...
- 微信小程序 图片裁剪
微信小程序 图片裁剪 分享一个微信小程序图片裁剪插件,很好用,支持旋转 文档:https://github.com/wyh19931106/image-cropper 1.json文件中添加image ...
- arcgis python 判断是数据库或是文件夹
import arcpy # Create a Describe object # desc = arcpy.Describe("C:/Data/chesapeake.gdb") ...
- EBS登录问题小结
1 网络问题 1)ping IP 2)telnet IP 端口 备注:如果端口能访问则直接跳转,如果不能访问则报错如下所示: 2)配置host文件 如果访问的服务器在内网,则需要配置host信息 3) ...
- mysql表的模糊查询
查询库下所有的表名 SELECT table_name FROM information_schema.tables WHERE table_schema='库名' 模糊表名查询 SELECT tab ...
- PHP 二维数组去重方法
php二维数组的去重策略,如果需要根据某字段去重(其他字段可能不一致),那么需要使用循环策略,如果去重的都是相同的(字段,值),那么可以用序列化方式. $allComments = array_map ...
- Django admin site应用
django自带的admin后台管理,可以实现对数据库表的增删改查,用起来十分方便.其使用和配置主要分为三个步骤: 1,创建超级用户 需要创建超级用户来登陆admin后台系统,在命令行中输入 pyth ...
- 微信小程序入门---记事本增---删
第一.如何获取input框的值(form表单提交除外) bindinput事件 <input type='text' placeholder="请输入内容" placehol ...