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简单聊天的更多相关文章

  1. python tornado websocket 多聊天室(返回消息给部分连接者)

    python tornado 构建多个聊天室, 多个聊天室之间相互独立, 实现服务器端将消息返回给相应的部分客户端! chatHome.py // 服务器端, 渲染主页 --> 聊天室建立web ...

  2. [tornado]websocket 最简单demo

    想法 前两天想看看django 长轮询或者是websocket的方案,发现都不太好使. tornado很适合做这个工作,于是找了些资料,参照了做了个最简单demo,以便备用. 具体的概念就不说了,to ...

  3. tornado websocket聊天室

    1.app.py #!/usr/bin/env python # -*- coding:utf-8 -*- import uuid import json import tornado.ioloop ...

  4. websocket简单入门

    今天说起及时通信的时候,突然被问到时用推的方式,还是定时接受的方式,由于之前页面都是用传统的ajax处理,可能对ajax的定时获取根深蒂固了,所以一时之间没有相同怎么会出现推的方式呢?当被提及webs ...

  5. SpringBoot 搭建简单聊天室

    SpringBoot 搭建简单聊天室(queue 点对点) 1.引用 SpringBoot 搭建 WebSocket 链接 https://www.cnblogs.com/yi1036943655/p ...

  6. 利用socket.io+nodejs打造简单聊天室

    代码地址如下:http://www.demodashi.com/demo/11579.html 界面展示: 首先展示demo的结果界面,只是简单消息的发送和接收,包括发送文字和发送图片. ws说明: ...

  7. Ext JS学习第十六天 事件机制event(一) DotNet进阶系列(持续更新) 第一节:.Net版基于WebSocket的聊天室样例 第十五节:深入理解async和await的作用及各种适用场景和用法 第十五节:深入理解async和await的作用及各种适用场景和用法 前端自动化准备和详细配置(NVM、NPM/CNPM、NodeJs、NRM、WebPack、Gulp/Grunt、G

    code&monkey   Ext JS学习第十六天 事件机制event(一) 此文用来记录学习笔记: 休息了好几天,从今天开始继续保持更新,鞭策自己学习 今天我们来说一说什么是事件,对于事件 ...

  8. Websocket 简单对话:静态网页与pycharm对话

    WebSocket websocket 是一种在单个Tcp连接上进行双全工通信的协议.websocket通信协议于2011年被IETF定为标准RFC6455,并 由RFc7936补充规范.WebSoc ...

  9. Tornado项目简单创建

    Tornado是使用Python编写的一个强大的.可扩展的Web服务器.它在处理严峻的网络流量时表现得足够强健,但却在创建和编写时有着足够的轻量级,并能够被用在大量的应用和工具中. tornado技术 ...

随机推荐

  1. python 绘制五角星

    code import turtle n = eval(input("请输入五角星的长度")) turtle.begin_fill() #开始填充颜色 i = : turtle.f ...

  2. ./与sh区别

    1   ./需要执行权限,使用脚本文件中第一行#!指定的shell(解释器)来执行命令(譬如常见的/bin/bash),不指定系统会调用默认shell程序 2   sh不需要执行权限,是使用sh这个s ...

  3. 深入学习golang中new与make区别

    Go语言中的内建函数new和make是两个用于内存分配的原语(allocation primitives).对于初学者,这两者的区别也挺容易让人迷糊的.简单的说,new只分配内存,make用于slic ...

  4. postgresql数据库的 to_date 和 to_timestamp 将 字符串转换为时间格式

    数据库中:字符串 转换为 时间格式 二者区别: to_data 转换为 普通的时间格式        to_timestamp 转换可为 时间戳格式出错场景: 比较同一天 日期大小的时候,很容易出错 ...

  5. arcgis python desc.dataType

    desc = arcpy.Describe(r"C:\Users\dell\Documents\ArcGIS\ddd.shp") 是ShapeFile desc = arcpy.D ...

  6. 使用arcpy.mapping 更新和修复数据源

    来自:https://blog.csdn.net/gisinfo/article/details/6675390 在许多情况下,您都可能需要修复数据源或重定向数据源至其他位置.然而,如果是在每个相关的 ...

  7. R语言:怎么进行异常检测

    a <- try(as.Date('2017-02-30'),silent = T) 当silent为F是,错误消息还是会返回 怎么检测a是否出错呢:if('try-error' %in% cl ...

  8. mongodb 的云数据库产品 atlas 的使用

    前言:最近发现 mlab 被mongodb 收购以后,不再支持新用户,推荐使用 MongoDB Atlas 第一步:注册或登陆 在MongoDB atlas首页,如果有账号,那就点击登陆.否则点击Ge ...

  9. Handler处理消息

    UI主线程通过Looper循环查询消息队列UI_MQ,当发现有消息存在时会将消息从消息队列中取出.首先分析消息,通过消息的参数判断该消息对应的Handler,然后将消息分发到指定的Handler进行处 ...

  10. 处理输入为非对角阵的Clustering by fast search and find of density peak代码

    Clustering by fast search and find of density peak. Alex Rodriguez, Alessandro Laio 是发表在Science上的一篇很 ...