web聊天室
原理:
长轮询:
客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
优点:在无消息的情况下不会频繁的请求,耗费资源小。兼容性好,所以目前为大多数
缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护
实例:Web微信
Websocket:WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
优点:真正的双向通信,未来的趋势
缺点:浏览器兼容性不好,IE9以下全部阵亡
实现:
1、Flask-WebSocket实现
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #coding=utf-8 2 3 from flask import Flask, request, render_template 4 from geventwebsocket.websocket import WebSocket 5 from geventwebsocket.handler import WebSocketHandler 6 from gevent.pywsgi import WSGIServer 7 import json 8 9 10 app = Flask(__name__) 11 12 @app.route("/index/") 13 def index(): 14 return render_template("ws.html") 15 16 17 user_socket_dict = {} 18 @app.route("/ws/<username>") 19 def ws(username): 20 user_socket = request.environ.get("wsgi.websocket") # type: 21 if not user_socket: 22 return "使用WebSocket方式连接" 23 user_socket_dict[username] = user_socket 24 print(user_socket_dict) 25 while True: 26 try: 27 # 接收客户端传入数据 28 user_msg = user_socket.receive() 29 # 将以用户名为键,连接为值的字典迭代出所有的键值对 30 for k,v in user_socket_dict.items(): 31 # 将当前发送数据的用户以及数据放在一起组成字典 32 # print(v) 33 who_send_msg = { 34 "send_user": username, 35 "send_msg": user_msg 36 } 37 # print(who_send_msg) 38 # 如果当前连接与迭代出来的连接相同,就跳过本次循环 39 if user_socket == v: 40 continue 41 # 否则 将用户以及用户数据以json格式发送出去 42 v.send(json.dumps(who_send_msg)) 43 except Exception as e: 44 # 当捕捉到异常的时候就将当前用户从字典中删除 45 user_socket_dict.pop(username) 46 47 if __name__ == '__main__': 48 http_serv = WSGIServer(("0.0.0.0",5000),app,handler_class=WebSocketHandler) 49 http_serv.serve_forever()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> 7 8 </head> 9 <body> 10 <div class="container-fluid"> 11 <div class="row"> 12 <div class="col-md-4"> 13 <h2 style="text-align: center">激情群聊</h2> 14 <div class="form-group"> 15 <label for="username">你是谁:</label> 16 <input class="form-control" type="text" id="username"> 17 </div> 18 <button id="create_ws" onclick="go_to()" class="btn btn-warning">创建ws连接</button> 19 <div style="width: 100%; height: 300px; border: thick;background-color: cadetblue" id="chat_window" class="input-group"> 20 </div> 21 22 <div class="input-group"> 23 <input type="text" class="form-control" placeholder="" id="send_msg"> 24 <span class="input-group-btn"> 25 <button class="btn btn-default" type="button" id="btn_send">发送消息</button> 26 </span> 27 </div> 28 </div> 29 </div> 30 </div> 31 </div> 32 33 <script type="application/javascript"> 34 35 var ws_url="ws://192.168.31.218:5000/ws/"; 36 var ws =null; 37 38 function go_to() { 39 var username = document.getElementById('username'); 40 ws = new WebSocket(ws_url+username.value); 41 ws.onmessage=function(serv_msg){ 42 msg=JSON.parse(serv_msg.data); 43 //console.log(serv_msg.data); 44 create_chart('y',msg) 45 }; 46 } 47 48 function create_chart(self,content) { 49 if (self == "w"){ 50 self = "right"; 51 var spantag = document.createElement("span"); 52 spantag.innerText= content.send_msg; 53 var spantag1 = document.createElement("span"); 54 spantag1.innerText=':我'; 55 }else{ 56 self = "left"; 57 var spantag = document.createElement("span"); 58 spantag.innerText=content.send_user+':'; 59 60 var spantag1 = document.createElement("span"); 61 spantag1.innerText=content.send_msg; 62 63 } 64 var divtag = document.createElement("div"); 65 divtag.style="text-align:"+self; 66 divtag.appendChild(spantag); 67 divtag.appendChild(spantag1); 68 var char_window = document.getElementById('chat_window'); 69 char_window.appendChild(divtag); 70 71 } 72 document.getElementById("btn_send").addEventListener("click",function () { 73 74 var send_msg=document.getElementById("send_msg"); 75 ws.send(send_msg.value); 76 77 var s_msg = {send_msg:send_msg.value}; 78 create_chart('w',s_msg); 79 send_msg.value=''; 80 }) 81 82 </script> 83 </body> 84 </html>
注意:这里Flask启动时不能使用pycharm启动,应使用命令行启动
Flask不能原生支持WebSocket,需要引入三方模块
2、Flask-长轮询实现
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #-*- coding: utf-8 -*- 2 from flask import Flask,render_template,session,request,jsonify 3 import queue,json 4 5 app = Flask(__name__) 6 app.secret_key = '1' 7 8 @app.route('/') 9 def hello_world(): 10 return 'Hello World!' 11 12 user_dict = {} 13 14 @app.route('/web_chat',methods=['GET','POST']) 15 def web_chat(): 16 #前端发送过来信息,将信息群发给除发送用户外的所有用户 17 if request.method == 'POST': 18 msg = request.form.get('msg') 19 username = request.form.get('username') 20 msg = {'send_msg':msg,'send_user':username} 21 current_username = session['current_username'] 22 for user,q in user_dict.items(): 23 if user!=current_username: 24 q.put(msg) 25 return jsonify({'status': True}) 26 else: 27 return render_template('ws.html') 28 29 @app.route('/get_msg',methods=['GET','POST']) 30 def get_msg(): 31 #前端长轮询,后端等待数据,把username存入session中 32 if request.method == 'POST': 33 username = session['current_username'] 34 ret = {'status': True, 'data': None} 35 q = user_dict[username] 36 try: 37 msg = q.get(timeout=50) 38 ret['data'] = msg 39 except: 40 ret['status'] = False 41 return jsonify(ret) 42 43 @app.route('/web_chat_join/<username>',methods=['GET','POST']) 44 def web_chat_join(username): 45 #加入聊天室,把用户加入user_dict,并为用户创建消息队列 46 if request.method == 'POST': 47 user_dict[username] = queue.Queue() 48 session['current_username'] = username 49 return jsonify({'status': True}) 50 51 if __name__ == '__main__': 52 #使用长轮询时,由于消息队列会阻塞,所以必须使用多线程 53 app.run(host='0.0.0.0',threaded=True)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> 7 8 </head> 9 <body> 10 <div class="container-fluid"> 11 <div class="row"> 12 <div class="col-md-4"> 13 <h2 style="text-align: center">激情群聊</h2> 14 <div class="form-group"> 15 <label for="username">你是谁:</label> 16 <input class="form-control" type="text" id="username"> 17 </div> 18 <button id="join_chat" onclick="go_to()" class="btn btn-warning">加入聊天室</button> 19 <div style="width: 100%; height: 300px; border: thick;background-color: cadetblue" id="chat_window" class="input-group"> 20 </div> 21 22 <div class="input-group"> 23 <input type="text" class="form-control" placeholder="" id="send_msg"> 24 <span class="input-group-btn"> 25 <button class="btn btn-default" type="button" id="btn_send">发送消息</button> 26 </span> 27 </div> 28 </div> 29 </div> 30 </div> 31 </div> 32 33 <script type="application/javascript"> 34 35 //加入聊天室 36 function go_to() { 37 var username = document.getElementById('username'); 38 // 新建XMLHttpRequest对象 39 var req = new XMLHttpRequest(); 40 // 状态发生变化时,函数被回调 41 req.onreadystatechange=function () { 42 //状态码为4代表请求完成 43 if (req.readyState==4&&req.status==200) { 44 data = req.responseText; 45 data = JSON.parse(data); 46 get_msg(); 47 } 48 }; 49 //post方式建立连接 50 req.open('post','http://127.0.0.1:5000/web_chat_join/'+username.value,true); 51 //post请求的编码方式,这是浏览器的原生 form 表单的默认编码方式 52 req.setRequestHeader("Content-type","application/x-www-form-urlencoded"); 53 //发送请求 54 req.send("chat_join"); 55 } 56 57 //长轮询递归获取数据 58 function get_msg() { 59 // 新建XMLHttpRequest对象 60 var req = new XMLHttpRequest(); 61 // 状态发生变化时,函数被回调 62 req.onreadystatechange=function () { 63 //状态码为4代表请求完成 64 if (req.readyState==4&&req.status==200) { 65 data = req.responseText; 66 data = JSON.parse(data); 67 console.log(data); 68 //将收到的数据显示在前端 69 create_chart('',data.data); 70 //长轮询递归获取数据 71 get_msg(); 72 } 73 }; 74 //post方式建立连接 75 req.open('post','http://127.0.0.1:5000/get_msg',true); 76 //post请求的编码方式,这是浏览器的原生 form 表单的默认编码方式 77 req.setRequestHeader("Content-type","application/x-www-form-urlencoded"); 78 //发送请求 79 req.send("get_msg"); 80 } 81 //将数据显示在页面,我发的在右边,别人的在左边 82 function create_chart(self,content) { 83 if (self == "w"){ 84 self = "right"; 85 var spantag = document.createElement("span"); 86 spantag.innerText= content.send_msg; 87 var spantag1 = document.createElement("span"); 88 spantag1.innerText=':我'; 89 }else{ 90 self = "left"; 91 var spantag = document.createElement("span"); 92 spantag.innerText=content.send_user+':'; 93 94 var spantag1 = document.createElement("span"); 95 spantag1.innerText=content.send_msg; 96 97 } 98 var divtag = document.createElement("div"); 99 divtag.style="text-align:"+self; 100 divtag.appendChild(spantag); 101 divtag.appendChild(spantag1); 102 var char_window = document.getElementById('chat_window'); 103 char_window.appendChild(divtag); 104 105 } 106 //点击发送按钮,发送数据 107 document.getElementById("btn_send").addEventListener("click",function () { 108 109 var send_msg=document.getElementById("send_msg"); 110 var username = document.getElementById('username'); 111 // 新建XMLHttpRequest对象 112 var req = new XMLHttpRequest(); 113 // 状态发生变化时,函数被回调 114 req.onreadystatechange=function () { 115 //状态码为4代表请求完成 116 if (req.readyState==4&&req.status==200) { 117 data = req.responseText; 118 data = JSON.parse(data); 119 } 120 }; 121 //post方式建立连接 122 req.open('post','http://127.0.0.1:5000/web_chat',true); 123 //post请求的编码方式,这是浏览器的原生 form 表单的默认编码方式 124 req.setRequestHeader("Content-type","application/x-www-form-urlencoded"); 125 //发送请求 126 req.send('msg='+send_msg.value+"&username="+username.value); 127 var s_msg = {send_msg:send_msg.value}; 128 129 create_chart('w',s_msg); 130 send_msg.value=''; 131 }) 132 133 </script> 134 </body> 135 </html>
注意:这里Flask启动时不能使用pycharm启动,应使用命令行启动
3、Django-长轮询实现
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="static/jquery-3.3.1.js"></script> 7 <link href="static/chatbar.css" rel="stylesheet" type="text/css"> 8 </head> 9 <body> 10 <div class="chat"> 11 <div class="chat-title"> 12 <div class="chat-title-font">聊天室</div> 13 </div> 14 <div class="chat-left"> 15 <div class="chat-content"> 16 17 </div> 18 <div class="chat-input"> 19 <div>聊天栏</div> 20 <div><input id="input_contnet" type="text" style="width: 500px;height: 50px"></div> 21 <div><input id="username" type="text"></div> 22 <div><input id="input_btn" type="button" value="发送信息" style="margin-left: 490px"></div> 23 </div> 24 </div> 25 <div class="chat-right"> 26 27 </div> 28 <div class="clear"></div> 29 </div> 30 31 <script> 32 $('#input_btn').click(function () { 33 var input_contnet = document.getElementById('input_contnet'); 34 var date = new Date(); 35 $('.chat-content').append('<div>'+input_contnet.value + ' ' + 'Time:' + date +'</div>'); 36 $('#input_contnet').animate({scrollTop:$('#input_contnet').scrollHeight},500); 37 $.ajax( 38 { 39 type: 'post', 40 url: 'chatbar', 41 data : { 42 'from':$('#username').val(), 43 'input_contnet':input_contnet.value, 44 }, 45 success: function (data) { 46 console.log(data); 47 } 48 } 49 ) 50 }); 51 $(document).ready(function () { 52 get_msg() 53 }); 54 function get_msg() { 55 $.ajax( 56 { 57 type:'post', 58 url:'get_msg', 59 dataType:'json', 60 data:{'from':$('#username').val(),}, 61 success:function (data) { 62 console.log(data); 63 get_msg(); 64 } 65 } 66 ) 67 } 68 </script> 69 </body> 70 </html>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 .chat{ 2 border: 4px solid black; 3 margin-top: 50px; 4 margin-left: 200px; 5 margin-right: 200px; 6 height: 450px; 7 } 8 9 .chat-title{ 10 border: 1px solid red; 11 margin-left: 20px; 12 margin-top: 20px; 13 margin-right: 20px; 14 height: 30px; 15 16 } 17 18 .chat-title-font{ 19 text-align: center; 20 margin-top: 1px; 21 font-weight: bolder; 22 color: darkblue; 23 font-size: larger; 24 } 25 26 .chat-left{ 27 border: 1px solid blue; 28 margin-left: 20px; 29 margin-top: 10px; 30 height: 350px; 31 width: 600px; 32 float: left; 33 } 34 35 .chat-content{ 36 border: 1px solid red; 37 margin-left: 20px; 38 margin-top: 10px; 39 margin-right: 20px; 40 overflow: auto; 41 height: 200px; 42 } 43 44 .chat-input{ 45 border: 1px solid red; 46 margin-left: 20px; 47 margin-top: 10px; 48 margin-right: 20px; 49 height: 100px; 50 } 51 52 .clear{ 53 clear: both; 54 display: none; 55 } 56 .chat-right{ 57 border: 1px solid rebeccapurple; 58 margin-left: 640px; 59 margin-top: 10px; 60 height: 350px; 61 width: 280px; 62 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 # -*- coding: utf-8 -*- 2 from __future__ import unicode_literals 3 from django.shortcuts import render,HttpResponse 4 import json,Queue 5 6 # Create your views here. 7 8 def ajax(req): 9 if req.method == 'POST': 10 print(req.POST) 11 data = req.POST.get('text1') 12 data_dic = {'data':data} 13 return HttpResponse(json.dumps(data_dic)) 14 else: 15 return render(req, 'test.html') 16 17 def jsonp(req): 18 callback = req.GET.get('callback') 19 data={'1':'a','2':'b','3':'c'} 20 return HttpResponse('%s(%s)'%(callback,json.dumps(data))) 21 22 global_msg_dict={} 23 24 def chatbar(req): 25 if req.method == 'POST': 26 from_user = req.POST.get('from') 27 json_data_dic=json.dumps(req.POST) 28 if not global_msg_dict.get(from_user): 29 global_msg_dict[from_user] = Queue.Queue() 30 global_msg_dict[from_user].put(json_data_dic) 31 32 return HttpResponse('----收到消息----') 33 else: 34 return render(req,'chatbar.html') 35 36 37 def get_msg(req): 38 if req.method=='POST': 39 msg_list = [] 40 from_user = 'chenxin' 41 if not global_msg_dict.get(from_user): 42 global_msg_dict[from_user] = Queue.Queue() 43 msg_count = global_msg_dict[from_user].qsize() 44 if msg_count>0: 45 for msg in range(msg_count): 46 msg_list.append(global_msg_dict[from_user].get()) 47 else: 48 try: 49 print('try') 50 msg_list.append(global_msg_dict[from_user].get(timeout=100)) 51 print(msg_list) 52 except Queue.Empty: 53 pass 54 return HttpResponse(json.dumps(msg_list))
4、Tornado-WebSocket实现
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <div id="contents" style="height:500px;overflow:auto;background-color: darkgray;"></div> 9 <div> 10 <textarea id="msg"></textarea> 11 <input type="button" value="发送" onclick="sendmsg()"> 12 </div> 13 </body> 14 <script> 15 var ws = new WebSocket("ws://"+location.host+"/chat"); 16 var contents = document.getElementById("contents"); 17 var msg = document.getElementById("msg"); 18 ws.onmessage = function (e) { 19 var msg1 = "<p>"+e.data+"</p>"; 20 contents.innerHTML+=msg1; 21 } 22 function sendmsg() { 23 ws.send(msg.value); 24 msg.value = ""; 25 } 26 </script> 27 </html>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 # coding:utf-8 2 3 import tornado.web 4 import tornado.ioloop 5 import tornado.httpserver 6 import tornado.options 7 import os 8 import datetime 9 10 from tornado.web import RequestHandler 11 from tornado.options import define, options 12 from tornado.websocket import WebSocketHandler 13 14 define("port", default=8000, type=int) 15 16 class IndexHandler(RequestHandler): 17 def get(self): 18 self.render("chat.html") 19 20 class ChatHandler(WebSocketHandler): 21 22 users = set() # 用来存放在线用户的容器 23 24 def open(self): 25 self.users.add(self) # 建立连接后添加用户到容器中 26 for u in self.users: # 向已在线用户发送消息 27 u.write_message(u"[%s]-[%s]-进入聊天室" % (self.request.remote_ip, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) 28 29 def on_message(self, message): 30 for u in self.users: # 向在线用户广播消息 31 u.write_message(u"[%s]-[%s]-说:%s" % (self.request.remote_ip, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), message)) 32 33 def on_close(self): 34 self.users.remove(self) # 用户关闭连接后从容器中移除用户 35 for u in self.users: 36 u.write_message(u"[%s]-[%s]-离开聊天室" % (self.request.remote_ip, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) 37 38 def check_origin(self, origin): 39 return True # 允许WebSocket的跨域请求 40 41 if __name__ == '__main__': 42 tornado.options.parse_command_line() 43 app = tornado.web.Application([ 44 (r"/", IndexHandler), 45 (r"/chat", ChatHandler), 46 ], 47 #static_path = os.path.join(os.path.dirname(__file__), "static"), 48 #template_path = os.path.join(os.path.dirname(__file__), "template"), 49 debug = True 50 ) 51 http_server = tornado.httpserver.HTTPServer(app) 52 http_server.listen(options.port,address="0.0.0.0") 53 tornado.ioloop.IOLoop.current().start()
由于Tornado本身是异步的,原生支持WebSocket