web聊天室

原理:

长轮询:

客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。

优点:在无消息的情况下不会频繁的请求,耗费资源小。兼容性好,所以目前为大多数

缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护

实例:Web微信

 


Websocket:

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。

浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

优点:真正的双向通信,未来的趋势

缺点:浏览器兼容性不好,IE9以下全部阵亡

 

实现:

1、Flask-WebSocket实现

 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()
app.py
 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>
ws.html

注意:这里Flask启动时不能使用pycharm启动,应使用命令行启动

Flask不能原生支持WebSocket,需要引入三方模块

 

2、Flask-长轮询实现

 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)
app.py
  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>
ws.html

 注意:这里Flask启动时不能使用pycharm启动,应使用命令行启动

 

3、Django-长轮询实现

 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>
HMTL
 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 }
CSS
 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))
python

 

4、Tornado-WebSocket实现

 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>
chat.html
 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()
chat.py

 由于Tornado本身是异步的,原生支持WebSocket

posted @ 2018-12-23 19:05  隔壁古二蛋  阅读(723)  评论(3编辑  收藏  举报