571
社区成员
发帖
与我相关
我的任务
分享使用tornado框架实现了一个实时聊天室
HTTP 协议有一个缺陷,通信只能由客户端发起。为了实现推送,很多网站采用轮询。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
在这种情况下,HTML5规范中新提出了客户端-服务器通讯协议——Websocket,其协议本身使用新的ws://URL格式。
WebSocket 是独立的、创建在 TCP 上的协议,和 HTTP 的唯一关联是使用 HTTP 协议的101状态码进行协议切换,使用的 TCP 端口是80,可以用于绕过大多数防火墙的限制。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端直接向客户端推送数据而不需要客户端进行请求,两者之间可以创建持久性的连接,并允许数据进行双向传送。
目前常见的浏览器如 Chrome、IE、Firefox、Safari、Opera 等都支持 WebSocket,同时需要服务端程序支持 WebSocket。
tornado是python编写的web服务器兼轻量级web应用框架,功能少而精,注重性能上的优越。
tornado提供支持WebSocket的模块是tornado.websocket,其中提供了一个WebSocketHandler类用来处理通讯。
基于tornado实现。时间以及自身技术原因,前端做的比较粗糙,还请谅解。
环境
python3.8
pycharm
vscode
实现功能
用户的注册登录
用户创建自己的聊天室,所有人用户都可以查找到该聊天室,且可以进入聊天
聊天窗口显示用户进入退出
主要分为登录窗口、注册窗口、用户主页以及聊天室窗口
核心代码
# -*-coding:utf-8-*-
import json
import tornado.web
import tornado.websocket
import tornado.httpserver
import tornado.ioloop
import tornado.options
import random
import time
ls = ['*', '**', '***', '#', '##', '###', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j']
users_info = {'ABC': '12345678', 'hhm': '123456'} # 保存所有用户的账户和密码信息
users_data = {'1': {}, '2': {}} # 保存所有聊天室下、所有用户在线时所记录的各种聊天信息,用于保存历史记录
online = [] # 以用户名来保存当前登录的用户
users_nums = {} # 记录每个聊天室在线的成员,用于显示在线人数。
class LoginHandler(tornado.web.RequestHandler):
'''
登录:
用户选择直接登录或者注册新账户;
用户信息验证过程;
出错信息和跳转;
'''
def get(self, *args, **kwargs):
self.render("login.html", type_="login") # 登陆页面
def post(self, *args, **kwargs):
# 信息验证和出错管理
username = str(self.get_argument("id"))
passwd = str(self.get_argument("passwd"))
if username in users_info:
if username in online:
online.remove(username)
self.render('error.html',
info={'status': False,
'errorcontnet': '友情提示!',
'info': "该用户已经在线!将自动跳转界面",
'second': 2,
'url': '/basic/?u=%s' % (username),
}
)
else:
if passwd == users_info[username]:
online.append(username)
self.render(
'basic.html',
id=username,
session=username,
rooms=users_data,
) # 进入
else:
self.render('error.html',
info={'status': False,
'errorcontnet': '出错了》~《',
'info': "用户名密码错误!将自动跳转注册界面",
'second': 3,
'url': '/login',
}
)
else:
self.render(
"error.html",
info={'status': False,
'errorcontnet': '出错了》~《',
'info': "该用户没有注册,请先注册!将自动跳转注册界面",
'second': 2,
'url': '/register/',
},
)
class RegisterHandler(tornado.web.RequestHandler):
'''
注册:
新用户注册信息注册和验证;
出错信息和跳转;
'''
def get(self, *args, **kwargs):
self.render('register.html') # 注册页面
def post(self, *args, **kwargs):
# 信息验证和出错管理
username = self.get_argument('id')
passwd = self.get_argument('passwd')
passwd1 = self.get_argument('passwd1')
if username not in users_info:
if passwd == passwd1:
users_info[username] = passwd
self.render(
'success.html',
info={
'status': True,
'info': "注册成功!自动跳转登录界面",
'second': 2,
'url': '/login',
}
)
else:
self.render(
'error.html',
info={
'status': False,
'errorcontnet': '出错了》~《',
'info': "密码不一致!自动跳转注册界面",
'second': 2,
'url': '/register/',
}
)
else:
self.render(
'error.html',
info={
'status': False,
'errorcontnet': '出错了》~《',
'info': "用户已经注册!",
'second': 5,
'url': '/login',
}
)
class chatBasicHandler(tornado.web.RequestHandler):
'''
主页:
选择进入聊天室;
创建聊天室;
'''
def get(self, *args, **kwargs):
u = self.get_argument("u")
# 进入用户聊天室主页面
self.render('basic.html',
id=u,
session=u,
rooms=users_data,
)
def post(self, *args, **kwargs):
# 用户聊天室创建
room_name = str(self.get_argument("new_room"))
u = self.get_argument("u")
if room_name in users_data:
room_name = room_name + '**'
users_data[room_name] = {}
self.redirect('/basic')
else:
users_data[room_name] = {}
self.redirect('/basic')
class CreateHandler(tornado.web.RequestHandler):
'''
创建聊天室:
设置聊天室名字
'''
def get(self, *args, **kwargs):
u = self.get_argument('u')
self.render(
'create.html',
id=u,
)
def post(self, *args, **kwargs):
u = self.get_argument('u')
room_name = str(self.get_argument("new_room"))
if room_name in users_data:
#v = np.random.choice(ls, 1, False)
v=[]
l=len(ls)
ll=random.randint(1,l)
v.append(ls[ll-1])
room_name = room_name + str(v[0])
users_data[room_name] = {}
self.redirect('/basic/?u=%s' % (u))
else:
users_data[room_name] = {}
self.redirect('/basic/?u=%s' % (u))
class homeHandler(tornado.web.RequestHandler):
'''
聊天室, 获取主页选择聊天室跳转的get信息渲染页面
'''
def get(self, *args, **kwargs):
n = self.get_argument('n') # 聊天室
u = self.get_argument('u') # 用户
self.render('home.html', n=n, u=u, id=u)
class chushiHandler(tornado.web.RequestHandler):
'''
聊天室, 获取主页选择聊天室跳转的get信息渲染页面
'''
def get(self, *args, **kwargs):
self.render('chushi.html')
class baibanHandler(tornado.web.RequestHandler):
'''
聊天室, 获取主页选择聊天室跳转的get信息渲染页面
'''
def get(self, *args, **kwargs):
self.render('baiban.html')
class newChatStatus(tornado.websocket.WebSocketHandler):
'''
websocket, 记录客户端连接,删除客户端连接,接收最新消息
'''
users = {} # 在线的用户(以类信息保存)
time = 0 # 记录时间
# 进入聊天室
def open(self):
# 显示时间
self.time = time.time()
self.write_message(json.dumps({'from': 'sys', 'message': '%s' % (time.strftime('%Y-%m-%d %H:%M:%S'))}))
n = str(self.get_argument('n')) # 聊天室
u = str(self.get_argument('u')) # 用户
# 记录在线的用户(全局保存,且以用户名保存)
if u in online:
pass
else:
online.append(u)
# 记录每个房间时在线人数
if n in users_nums:
users_nums[n].append(u)
else:
users_nums[n] = []
users_nums[n].append(u)
# 向新加入用户发送首次消息
self.write_message(json.dumps({'from': 'sys', 'message': '欢迎%s来到 聊天室(%s)' % (u, n)}))
# 显示历史记录
if n in users_data:
if u in users_data[n]:
for m in users_data[n][u]:
self.write_message(json.dumps({'from': m[0], 'message': m[1]}))
# 添加在线用户(类信息)
if n in self.users:
self.users[n].append(self)
else:
self.users[n] = [self]
message = {
'from': 'sys', 'message': '%s加入聊天室%s(当前%d人在线)' % (u, n, len(users_nums[n]))
}
# 向每个聊天室成员发送消息
for user in self.users[n]:
user.write_message(json.dumps(message))
# 离开聊天室
def on_close(self):
n = str(self.get_argument('n')) # 聊天室
u = str(self.get_argument('u')) # 用户
online.remove(u) # 删除离开聊天室的用户
# 人数减1
if n in users_nums:
if u in users_nums[n]:
users_nums[n].remove(u)
# 删除用户(类信息
self.users[n].remove(self)
if self.users[n]:
message = {
'from': 'sys',
'message': '%s离开了聊天室%s(当前%d人在线)' % (u, n, len(users_nums[n]))
}
# 向聊天室里的每个成员发送消息
for user in self.users[n]:
user.write_message(json.dumps(message))
# 用户发送消息
def on_message(self, message):
t1 = time.time()
# 定时显示消息
if t1 - self.time > 120:
self.time = t1
self.write_message(json.dumps({'from': 'sys', 'message': '%s' % (time.strftime('%Y-%m-%d %H:%M:%S'))}))
n = str(self.get_argument('n'))
u = str(self.get_argument('u'))
message = {'from': u, 'message': message}
# 保存用户发送的消息,只要在线的都要保存
if n in users_data:
for user in users_nums[n]:
if user in users_data[n]:
users_data[n][user].append((u, message['message']))
else:
users_data[n][user] = []
users_data[n][user].append((u, message['message']))
else:
users_data[n] = {}
for user in users_nums[n]:
users_data[n][user] = []
users_data[n][user].append((u, message['message']))
# 向每个在线人员发送新消息
for user in self.users[n]:
user.write_message(json.dumps(message))
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r'/', chushiHandler),
(r'/baiban', baibanHandler),
(r'/login', LoginHandler),
(r'/register/', RegisterHandler),
(r'/home/', homeHandler),
(r'/basic/', chatBasicHandler),
(r'/newChatStatus/', newChatStatus),
(r'/create/', CreateHandler),
]
settings = {
'template_path': '登录\\templates',
'static_path': '登录\\static'
}
tornado.web.Application.__init__(self, handlers, **settings)
if __name__ == '__main__':
tornado.options.parse_command_line()
server = tornado.httpserver.HTTPServer(Application())
# server = tornado.web.Application(
# handlers = [(r'/login', LoginHandler),(r'/register/', RegisterHandler),(r'/home/', homeHandler),(r'/basic/', chatBasicHandler),(r'/newChatStatus/', newChatStatus),(r'/create/', CreateHandler)], template_path=os.path.join(os.path.dirname(__file__),"templates"))
server.listen(8000) # 监听端口
tornado.ioloop.IOLoop.instance().start()
效果展示
注册后可以登录并创建自己的聊天室


进入聊天室后注册另一个账户测试

作者:NP128 + 陈孟卿