在tornado框架下实现基于websocket的实时聊天室

__忘川 2022-01-18 21:05:18

使用tornado框架实现了一个实时聊天室

Websocket

HTTP 协议有一个缺陷,通信只能由客户端发起。为了实现推送,很多网站采用轮询。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

在这种情况下,HTML5规范中新提出了客户端-服务器通讯协议——Websocket,其协议本身使用新的ws://URL格式。

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

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

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

 

tornado 

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 + 陈孟卿

...全文
485 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

571

社区成员

发帖
与我相关
我的任务
社区描述
软件工程教学新范式,强化专项技能训练+基于项目的学习PBL。Git仓库:https://gitee.com/mengning997/se
软件工程 高校
社区管理员
  • 码农孟宁
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧