用Tornado实现web聊天室(前端采用vue+bootstrap)

用Tornado实现web聊天室(前端采用vue+bootstrap)作者 小小明文章目录 WebSocket 的介绍 Tornado 的 WebSocket 模块前端 JavaScript 编写 web 聊天室精简版后端代码 server py 前端代码 index htmlweb 聊天室 v0 1 地址项目结构生成随机昵称代码编码调用腾讯 AI 闲聊接口的核心代码服务端核心代码前端登陆页面实现头像选择的核心代码聊天室前端页面 vue 双向数据绑定代码示例 WebSocket 的介绍 WebSocket 是 HTML5 规范中新提出的客户端 服务器通讯协议 协议本身使用新的 ws URL 格式 WebSocke

作者:小小明

WebSocket的介绍

WebSocket是HTML5规范中新提出的客户端-服务器通讯协议,协议本身使用新的ws://URL格式。

WebSocket 是独立的、创建在 TCP 上的协议,和 HTTP 的唯一关联是使用 HTTP 协议的101状态码进行协议切换,使用的 TCP 端口是80,可以用于绕过大多数防火墙的限制。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端直接向客户端推送数据而不需要客户端进行请求,两者之间可以创建持久性的连接,并允许数据进行双向传送。

目前常见的浏览器如 Chrome、IE、Firefox、Safari、Opera 等都支持 WebSocket,同时需要服务端程序支持 WebSocket。

Tornado的WebSocket模块

Tornado提供支持WebSocket的模块是tornado.websocket,其中提供了一个WebSocketHandler类用来处理通讯。

WebSocketHandler.open()

当一个WebSocket连接建立后被调用。

WebSocketHandler.on_message(message)

当客户端发送消息message过来时被调用,注意此方法必须被重写

WebSocketHandler.on_close()

当WebSocket连接关闭后被调用。

WebSocketHandler.write_message(message, binary=False)

向客户端发送消息messagea,message可以是字符串或字典(字典会被转为json字符串)。若binary为False,则message以utf8编码发送;二进制模式(binary=True)时,可发送任何字节码。

WebSocketHandler.close()

关闭WebSocket连接。

WebSocketHandler.check_origin(origin)

判断源origin,对于符合条件(返回判断结果为True)的请求源origin允许其连接,否则返回403。可以重写此方法来解决WebSocket的跨域请求(如始终return True)。

前端JavaScript编写

在前端JS中使用WebSocket与服务器通讯的常用方法如下:

var ws = new WebSocket("ws://127.0.0.1:8888/websocket"); // 新建一个ws连接 ws.onopen = function() { 
    // 连接建立好后的回调 ws.send("Hello, world"); // 向建立的连接发送消息 }; ws.onmessage = function (evt) { 
    // 收到服务器发送的消息后执行的回调 alert(evt.data); // 接收的消息内容在事件参数evt的data属性中 }; 

web聊天室精简版

后端代码 server.py

# coding:utf-8 import tornado.web import tornado.ioloop import tornado.httpserver import tornado.options import os import datetime from tornado.web import RequestHandler from tornado.options import define, options from tornado.websocket import WebSocketHandler define("port", default=9000, type=int) class IndexHandler(RequestHandler): def get(self): self.render("index.html") class ChatHandler(WebSocketHandler): users = set() # 用来存放在线用户的容器 def open(self): self.users.add(self) # 建立连接后添加用户到容器中 for u in self.users: # 向已在线用户发送消息 u.write_message(u"[%s]-[%s]-进入聊天室" % (self.request.remote_ip, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) def on_message(self, message): for u in self.users: # 向在线用户广播消息 u.write_message(u"[%s]-[%s]-说:%s" % (self.request.remote_ip, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), message)) def on_close(self): self.users.remove(self) # 用户关闭连接后从容器中移除用户 for u in self.users: u.write_message(u"[%s]-[%s]-离开聊天室" % (self.request.remote_ip, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) def check_origin(self, origin): return True # 允许WebSocket的跨域请求 if __name__ == '__main__': tornado.options.parse_command_line() app = tornado.web.Application([ (r"/", IndexHandler), (r"/chat", ChatHandler), ], static_path = os.path.join(os.path.dirname(__file__), "static"), template_path = os.path.join(os.path.dirname(__file__), "template"), debug = True ) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(options.port) tornado.ioloop.IOLoop.current().start() 

前端代码index.html

 <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>聊天室 
     title>  
      head> <body> <div id="contents" style="height:500px;overflow:auto;"> 
       div> <div> <textarea id="msg"> 
        textarea> <a href="javascript:;" onclick="sendMsg()">发送 
         a>  
          div> <script src="{ 
          {static_url('js/jquery.min.js')}}"> 
           script> <script type="text/javascript"> var ws = new WebSocket("ws://192.168.43.153:9000/chat"); ws.onmessage = function(e) { 
            $("#contents").append("

"

+ e.data + ""); } function sendMsg() { var msg = $("#msg").val(); ws.send(msg); $("#msg").val(""); }
script> body> html>

注意:192.168.43.153改成你自己电脑的ip.

这样一个简单的web聊天室就出来,效果如下:

image-20200527141434691

image-20200527141502570

在同一局域网下的手机和电脑对聊,主要的是界面太丑!

web聊天室v0.1

现在呢,写一个界面好看点的聊天室,最终长这个样:

image-20200527141828500

第一次使用时,还可以选择头像和昵称:

image-20200527142005046

地址

代码地址:

https://gitee.com/wangzhouming/websocket_chat

体验地址:

http://xiaoxiaoming.xyz:8088/

项目结构

│  bot_util.py
│  random_name.py
│  server.py
│
├─conf
│  │  bot_conf.py
│  │  config.ini
│  │  config.py
│  └─ __init__.py
│
├─static
│  ├─css
│  │      bootstrap-theme.css
│  │      bootstrap-theme.css.map
│  │      bootstrap-theme.min.css
│  │      bootstrap-theme.min.css.map
│  │      bootstrap.css
│  │      bootstrap.css.map
│  │      bootstrap.min.css
│  │      bootstrap.min.css.map
│  │      style.css
│  │
│  ├─fonts
│  │      glyphicons-halflings-regular.eot
│  │      glyphicons-halflings-regular.svg
│  │      glyphicons-halflings-regular.ttf
│  │      glyphicons-halflings-regular.woff
│  │      glyphicons-halflings-regular.woff2
│  │
│  ├─images
│  │      0.jpg
│  │      1.jpg
│  │      10.jpg
│  │      11.jpg
│  │      12.jpg
│  │      13.jpg
│  │      14.jpg
│  │      15.jpg
│  │      16.jpg
│  │      17.jpg
│  │      18.jpg
│  │      19.jpg
│  │      2.jpg
│  │      20.jpg
│  │      21.jpg
│  │      22.jpg
│  │      23.jpg
│  │      24.jpg
│  │      25.jpg
│  │      26.jpg
│  │      3.jpg
│  │      4.jpg
│  │      5.jpg
│  │      6.jpg
│  │      7.jpg
│  │      8.jpg
│  │      9.jpg
│  │      code.jpg
│  │
│  └─js
│          bootstrap.js
│          bootstrap.min.js
│          jquery.min.js
│          vue.min.js
│
└─template
      base.html
       room.html
      signin.html

生成随机昵称代码编码

def random_name(): xing = '赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻柏水窦章云苏潘葛' \ '奚范彭郎鲁韦昌马苗凤花方俞任袁柳酆鲍史唐费廉岑薛雷贺倪汤滕殷罗毕郝邬安常乐于时傅皮卞齐康' \ '伍余元卜顾孟平黄和穆萧尹姚邵湛汪祁毛禹狄米贝明臧计伏成戴谈宋茅庞熊纪舒屈项祝董梁杜阮蓝闵' \ '席季麻强贾路娄危江童颜郭梅盛林刁钟徐邱骆高夏蔡田樊胡凌霍虞万支柯昝管卢莫经房裘缪干解应宗' \ '丁宣贲邓郁单杭洪包诸左石崔吉钮龚程嵇邢滑裴陆荣翁荀羊於惠甄曲家封芮羿储靳汲邴糜松井段富巫' \ '乌焦巴弓牧隗山谷车侯宓蓬全郗班仰秋仲伊宫宁仇栾暴甘钭厉戎祖武符刘景詹束龙叶幸司韶郜黎蓟薄' \ '印宿白怀蒲邰从鄂索咸籍赖卓蔺屠蒙池乔阴鬱胥能苍双闻莘党翟谭贡劳逄姬申扶堵冉宰郦雍卻璩桑桂' \ '濮牛寿通边扈燕冀郏浦尚农温别庄晏柴瞿阎充慕连茹习宦艾鱼容向古易慎戈廖庾终暨居衡步都耿满弘' \ '匡国文寇广禄阙东欧殳沃利蔚越夔隆师巩厍聂晁勾敖融冷訾辛阚那简饶空曾毋沙乜养鞠须丰巢关蒯相' \ '查后荆红游竺权逯盖益桓公万俟司马上官欧阳夏侯诸葛闻人东方赫连皇甫尉迟公羊澹台公冶宗政濮阳' \ '淳于单于太叔申屠公孙仲孙轩辕令狐钟离宇文长孙慕容鲜于闾丘司徒司空丌官司寇仉督子车颛孙端木' \ '巫马公西漆雕乐正壤驷公良拓跋夹谷宰父谷梁晋楚闫法汝鄢涂钦段干百里东郭南门呼延归海羊舌微生' \ '岳帅缑亢况郈有琴梁丘左丘东门西门商牟佘佴伯赏南宫墨哈谯笪年爱阳佟第五言福' ming = '伟刚勇毅俊峰强军平保东文辉力明永健世广志义兴良海山仁波宁贵福生龙元全国胜学祥才发武新利清' \ '飞彬富顺信子杰涛昌成康星光天达安岩中茂进林有坚和彪博诚先敬震振壮会思群豪心邦承乐绍功松善' \ '厚庆磊民友裕河哲江超浩亮政谦亨奇固之轮翰朗伯宏言若鸣朋斌梁栋维启克伦翔旭鹏泽晨辰士以建家' \ '致树炎德行时泰盛秀娟英华慧巧美娜静淑惠珠翠雅芝玉萍红娥玲芬芳燕彩春菊兰凤洁梅琳素云莲真环' \ '雪荣爱妹霞香月莺媛艳瑞凡佳嘉琼勤珍贞莉桂娣叶璧璐娅琦晶妍茜秋珊莎锦黛青倩婷姣婉娴瑾颖露瑶' \ '怡婵雁蓓纨仪荷丹蓉眉君琴蕊薇菁梦岚苑筠柔竹霭凝晓欢霄枫芸菲寒欣滢伊亚宜可姬舒影荔枝思丽秀' \ '飘育馥琦晶妍茜秋珊莎锦黛青倩婷宁蓓纨苑婕馨瑗琰韵融园艺咏卿聪澜纯毓悦昭冰爽琬茗羽希' result = [] result.append(random.choice(xing)) for i in range(random.randrange(1, 4)): result.append(random.choice(ming)) return "".join(result) 

调用腾讯AI闲聊接口的核心代码

import hashlib import random import time from urllib import parse import requests from conf.bot_conf import app_id, app_key nonce_max = 16  30 def get_nonce_str(): '获取随机字符串,用于保证签名不可预测' return hex(random.randrange(nonce_max))[2:] def get_textchat_params(question) -> dict: params = { 
    'app_id': app_id, 'time_stamp': str(int(time.time())), 'nonce_str': get_nonce_str(), 'session': '10000', 'question': question, } sign = gen_sign_string(params.items()) params["sign"] = sign return params def gen_sign_string(params) -> str: p = list(sorted(params)) p.append(('app_key', app_key)) sign_str = parse.urlencode(p) # 对字符串sign_before进行MD5运算,得到接口请求签名 sign = hashlib.md5(sign_str.encode('UTF-8')).hexdigest().upper() return sign # 聊天的API地址 textchat_url = "https://api.ai..com/fcgi-bin/nlp/nlp_textchat" def textchat(question): params = get_textchat_params(question.encode('utf-8')) r = requests.post(textchat_url, params) json = r.json() return json['data']['answer'] 

服务端核心代码

def parseUser(r: Optional[str]) -> Optional[dict]: if r: return json.loads(base64.b64decode(r).decode("utf-8")) return None class IndexHandler(RequestHandler): def get_current_user(self): """在此完成用户的认证逻辑""" user: dict = parseUser(self.get_secure_cookie("user")) return user @tornado.web.authenticated def get(self): current_user = self.current_user logging.info(f"当前登陆的用户:{current_user}") self.render("room.html", title="web聊天室", user=current_user, host=options.host, port=options.port) class SigninHandler(RequestHandler): index = 0 def get(self): self.render("signin.html", title="web聊天室", name=random_name(), user=None, maximgid=27, random_img_id=random.randrange(27)) def post(self): SigninHandler.index += 1 logging.info(f"注册接口被调用,当前索引:{SigninHandler.index}") name = self.get_body_argument("name") imgid = self.get_body_argument("imgid") user = { 
    "id": SigninHandler.index, "name": name, "imageid": imgid } logging.info(f"写入cookie的对象:{user}") value = base64.b64encode(json.dumps(user).encode("utf-8")) self.set_secure_cookie("user", value) self.redirect("/") class SignoutHandler(RequestHandler): def get(self): self.clear_cookie('user') self.redirect('/signin') bot_ = { 
    "id": 0, "name": "机器人小小鸟", "imageid": "bot" } class ChatHandler(WebSocketHandler): users = set() # 用来存放在线用户的容器 user = None # 当前用户 users_json = [bot_] # 准备发给客户端的用户列表数据 messageIndex = 0 def createMessage(self, type, user, data): ChatHandler.messageIndex += 1 # print(self.messageIndex) message = { 
    "id": ChatHandler.messageIndex, "type": type, "user": user, "data": data } logging.info(f"创建一条消息:{message}") return json.dumps(message) def open(self): self.user = parseUser(self.get_secure_cookie("user")) if self.user is None: self.close(4001, 'Invalid user') return logging.info(f"{self.user}连接了WebSocket服务端...") self.users.add(self) # 建立连接后添加用户到容器中 self.users_json.append(self.user) # message = self.createMessage('join', self.user, self.user) message = self.createMessage('join', self.user, f"{self.user['name']}进入了聊天室.") for u in self.users: # 向已在线用户发送消息 u.write_message(message) # 通知当前上线的用户,在线用户列表 self.write_message(self.createMessage('list', self.user, self.users_json)) def on_message(self, message): msg = self.createMessage('chat', self.user, message.strip()) self.send_msg(msg) msg = textchat(message.strip()) if msg: msg = self.createMessage('chat', bot_, msg) self.send_msg(msg) def send_msg(self, msg): for u in self.users: # 向在线用户广播消息 u.write_message(msg) def on_close(self): self.users.remove(self) # 用户关闭连接后从容器中移除用户 self.users_json.remove(self.user) msg = self.createMessage('left', self.user, f"{self.user['name']}离开了聊天室.") self.send_msg(msg) def check_origin(self, origin): logging.info(origin) # http://192.168.40.1:9000 return True # 允许WebSocket的跨域请求 

前端登陆页面实现头像选择的核心代码

<form action="/signin" method="post"> <div class="form-group"> <label>头像: <select name=imgid class="form-control" onchange="document.images['idface'].src='/static/images/'+options[selectedIndex].value+'.jpg';"> {% for imgid in range(maximgid) %} 

聊天室前端页面vue双向数据绑定代码示例

<div id="message-list"> <div style="margin-bottom:25px;" v-for="msg in messages"> <div v-if="msg.type === 'join' || msg.type === 'left'"> <div class="media-left"> <img class="media-object mini" v-bind:src="'/static/images/' + msg.user.imageid + '.jpg'">  
     div> <div class="media-body"> <h4 class="media-heading" v-text="msg.data"> 
      h4>  
       div>  
        div> <div v-if="msg.type === 'chat'"> <div class="media"> <div class="media-left"> <img class="media-object" style="width:48px; height:48px;" v-bind:src="'/static/images/' + msg.user.imageid + '.jpg'">  
         div> <div class="media-body"> <h4 class="media-heading" v-text="msg.user.name"> 
          h4> <span v-text="msg.data"> 
           span>  
            div>  
             div>  
              div>  
               div>  
                div> 

js部分:

vmMessageList = new Vue({ 
    el: '#message-list', data: { 
    messages: [] } }); function addMessage(list, msg) { 
    list.push(msg); $('#message-list').parent().animate({ 
    scrollTop: $('#message-list').height() }, 1000); } 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/198586.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午2:52
下一篇 2026年3月26日 下午2:52


相关推荐

  • 走近代码之Python–爬虫框架Portia | 艾伯特

    走近代码之Python–爬虫框架Portia | 艾伯特Portia nbsp 基于 Scrapy 的可视化数据采集框架走近代码之 Python 爬虫框架 Scrapy1 框架特性基于 scrapy 内核可视化爬取内容 不需要任何开发专业知识动态匹配相同模板的内容 2 安装 Windows 推荐使用 Docker 安装安装 DockerToolBo 启动 nbsp dockerrun v F pywp portia app data projects

    2026年3月18日
    3
  • 插入一个数并排序「建议收藏」

    插入一个数并排序「建议收藏」插入一个数并排序

    2022年4月24日
    55
  • springboot到底是什么_Springboot注解

    springboot到底是什么_Springboot注解SpringBoot是干哈的介绍:springboot是由Pivotal团队提供的全新框架。spring的出现是为了解决企业级开发应用的复杂性,spring的通过注册bean的方式来管理类,但是随着业务的增加,使用xml配置bean的方式也显得相当繁琐,所以springboot就是为了解决spring配置繁琐的问题而诞生的,并且近几年来非常流行开启我的第一个HelloSpringBoot!开启方式根据https://start.spring.io网址创建一个springboot项目

    2026年4月13日
    5
  • 用UltraISO制作支持windows 7的U盘启动盘建议收藏

    用UltraISO制作U盘启动盘,有人写过,我也看过,不过依照网上的那些文章,成功的并不多,经过几次试验,在不同的主板环境下成功概率高的方法应该如下:1.UltraISO建议9.3以上2.制作要点

    2021年12月20日
    50
  • 秦九韶计算多项式的方法_秦九韶

    秦九韶计算多项式的方法_秦九韶本人水平有限,题解不到为处,请多多谅解本蒟蒻谢谢大家观看秦九韶公式:可不断的提取x作公因式,写成如上形式不断将x=4由里往外扩展,大大方便了计算

    2022年8月3日
    7
  • css固定定位_css定位布局

    css固定定位_css定位布局html>htmllang=”en”>head>metacharset=”UTF-8″>title>css固定定位title>style>#back{width:100px;height:100px;background-color:#FF6500;

    2025年8月19日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号