WebSockets 允许浏览器和服务器之间进行 双向通信
server端:
class WebSocketHandler(WebBaseHandler): ''' websocket ''' users = {} # {u'liubei': <handlers.message.message_handler.WebSocketHandler object at 0xb620b34c>, # u'rock': <handlers.message.message_handler.WebSocketHandler object at 0xb61794ec>} # ------------------提高部分 开始------------------
# 提高部分只是细分发送信息的对象,不是很重要。完成提高部分结束后的 open,on_message, on_close即可。
@classmethod def send_system_message(cls, self, content, send_type): """ :param self: 继承过websocket的base类的实例化对象,主要是self初始化了操作redis,mysql以及user对象的属性 :param content: 储存进redis的list data :param send_type: 发送的类型(发给boss,ceo,员工...),由前端传过来 :return: 系统消息,发送给每个人 """ target = 'system' redis_msg = cls.dict_to_json(self, content, send_type, target) self.conn.rpush('message:%s' % send_type, redis_msg) for f, v in WebSocketHandler.users.items(): v.write_message(redis_msg) @classmethod def dict_to_json(cls, self, content, send_type, target): """ :param self: 继承过websocket的base类的实例化对象,主要是self初始化了操作redis,mysql以及user对象的属性 :param content: 储存进redis的list data :param send_type: 发送的类型(发给boss,ceo,员工...),由前端传过来 :param target: 相当与用来区分缓存key的名字,比如 key_name="cache_list:%s"%target1, ...取对应分类的历史数据 :return: """ msg = { "content": content, "send_type": send_type, "sender": self.current_user.name, "target": target, "datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } return tornado.escape.json_encode(msg) @classmethod def send_role_message(cls, self, content, send_type, roleid): """ :param self: :param content: :param send_type: :param roleid: 通过角色id,反查出属于该角色id的用户 :return: 发送信息给 该角色的所用用户 """ role = Role.by_id(roleid) redis_msg = cls.dict_to_json(self, content, send_type, role.name) self.conn.rpush('message:%s' % send_type, redis_msg) role_users = role.users # [zhangsan, lishi , wangwu] [zhangsan, lishi] for user in role_users: if WebSocketHandler.users.get(user.name, None) is not None: # user.name ['rock':self] WebSocketHandler.users[user.name].write_message(redis_msg) else: # self.conn.lpush("ws:role_off_line",message) pass @classmethod def send_user_message(cls, self, content, send_type, user): """ :param self: :param content: :param send_type: :param user: 发送的对象 :return: 发送信息给改用户 user """ redis_msg = cls.dict_to_json(self, content, send_type, user) self.conn.rpush('message:%s' % send_type, redis_msg) self.conn.rpush('message:%s' % user, redis_msg) # 为了显示未读消息条数 if cls.users.get(user, None) is not None: cls.users[user].write_message(redis_msg) else: # self.conn.lpush("ws:user_off_line",message) pass # ------------------提高部分 结束------------------ def open(self): ''' 有用户进来后,存储该用户 {usernaem: self} self是每个登录用户的实例化类 (用该实例化对象发送是那个消息) ''' WebSocketHandler.users[self.current_user.name] = self pass def on_message(self, message): # 改方法获取消息,前端通过 ws对象.send(msg)发送 # print message # {"content_html":"聊天框输入的内容"} json # {"content_html":"afaf<img src="/static/images/face/nm_thumb.gif" title="[怒骂]">"} msg = tornado.escape.json_decode(message) # 解码 json字符串 --> 字符串 msg.update({ "name": self.current_user.name, "datetime": datetime.now().strftime("%Y-%m-%d %H-%M-%S") }) message = tornado.escape.json_encode(msg) # 转成json self.conn.rpush('message:list', message) # 存储消息为了显示历史消息 # self.write_message(msg) # 就算不转成json,write_message也能自己编码 # WebSocketHandler.users['liubei'].write_message(message) # 这是将不管谁的message只发给用户liubei for f, v in WebSocketHandler.users.items(): v.write_message(message) def on_close(self): pass
前端:和后端一样,都需要完成open,on_message,on_close三个方法
<script type="text/javascript"> $(document).ready(function(){ //与服务器建立websocket链接请求 var url="ws://" + location.host + "/ws"; //是open, on_message, on_close所在类映射的路由。是通过ws协议,而不是http var ws= new WebSocket(url);//在浏览器打开一个socket 服务器打开一个socket ws.onopen=function(){ $('#status').text('已经建立链接'); var tishi = $('.tishi'); var name = tishi.attr('username'); tishi.append("<div>"+name+"加入了聊天室...</div>") }; ws.onclose=function () { $('#status').text('已经断开链接') }; //当收到服务器向浏览器推送消息时调用这个函数 ws.onmessage=function(event){ message = JSON.parse(event.data); if(message.content_html){ append(message); }else{ append_m(message); } }; //点出头像函数 $('.t_gif').click(function(){ $('.t_box').toggle(300); }); // 点击表情时把点击的表情添加到文本框中 $('#q_ul li').click(function(){ var img = $(this).find("img").clone(); $(".t_input").append(img); $(".t_input").focus(); }); // 点击发布按钮时调用wsbsocket.send()函数向服务器发送数据 $(".t_btn").click(function(){ var content_html = $('.t_input').html(); console.log(content_html); var massage = { "content_html":content_html }; ws.send(JSON.stringify(massage)); }); // 动态添加发布消息的函数 function append(msg){ //向留言中添加消息 $(".t_all").prepend(function(n){ var content_html=''; var useravatar=''; var datetime1=''; if(msg){ content_html = msg.content_html; var useravatar = 'defaut_avatar.jpeg'; datetime1 = msg.datetime; } return "<div class='t_list animated bounceIn'>"+ "<div class='t_header'>"+ "<img src='/static/images/useravatars/" + useravatar +"' alt='' width='64' height='64' />"+ "</div>"+ "<div class='t_icon'></div>"+ "<div class='t_msg'>"+"<p style='font-size:8px;'>"+ "<a class='name' href='#'>" +msg.name+ " </a>"+ datetime1+"</p>"+content_html+"</div>"+ "<div class='clear'></div>"+ "</div>" }); $('.t_box').hide(400); $('.t_input').text(''); $('.t_input').focus(); } // 发布系统消息的函数 function append_m(msg){ $(".system_all").html(''); var target = ""; if(msg.target == "system"){ target = "全体人员" }else{ target = msg.target } var messages = "消息内容:"+msg.content+ " 消息类型:"+ msg.send_type+" 发送者:"+ msg.sender +" 接收者:"+ target+" 时间:"+ msg.datetime; $(".system_all").html(messages); } }) </script>
参考中文文档:https://tornado-zh.readthedocs.io/zh/latest/websocket