zoukankan      html  css  js  c++  java
  • websocket 实现简单网页版wechat

    1.群聊

    • web - socket--基于TCP/UDP
    • http - 无状态的短链接
    • 长连接:客户端和服务器保持永久性的链接,除非有一方主动断开,
    • 轮询:客户端和服务端不断连接,然后断开,请求响应;不能保证数据的实时性.
    • 长轮询:长轮询:客户端发起请求至server,服务端不响应,服务端一直等待,链接一直建立,等待http链接自动超时(默认15s),主动断开链接

    1.1 服务端

    # 安装模块 gevent-websocket,基于websocket 长连接实现群聊
    from flask import Flask, request, render_template
    from geventwebsocket.handler import WebSocketHandler  # 请求处理WSGI HTTP
    from geventwebsocket.server import WSGIServer  # 替换Flask原来的wsgi服务
    from geventwebsocket.websocket import WebSocket  # 语法提示
    
    app = Flask(__name__)
    socket_lsit = []  # 建立连接的用户存在列表中
    
    
    @app.route('/ws')  # 不再需要methods
    def my_ws():
        # print(request.environ)  # 输出原始请求信息
        ws_socket = request.environ.get('wsgi.websocket')  # type:WebSocket #语法提示  #获取连接
        socket_lsit.append(ws_socket)  # 获取到的连接保存到列表中
        print(len(socket_lsit))  # 查看连接数
        while True:
            msg = ws_socket.receive()  # 基于长连接socket 接收用户传递的数据
            print(msg)  # 查看数据
            for usocket in socket_lsit:  # 群聊遍历所有用户
                if usocket == ws_socket:  # 如果地址等于发送消息的客户端地址,不用自己发给自己
                    continue
                try:  # 处理异常
                    usocket.send(msg)  # 将消息发送给所有有效连接
                except:
                    continue
    
    @app.route('/wechat')   # 客户端访问地址
    def wechat():
        return render_template('ws_we.html') 
    
    
    if __name__ == '__main__':
        # app.run()
        http_serv = WSGIServer(('0.0.0.0', 9527), 
                               app, 
                               handler_class=WebSocketHandler  #websocket替换http
                              ) 
        http_serv.serve_forever()
    
    

    1.2 客户端(html文件)

    • 状态码status:
      • 1 当前连接处于可用状态
      • 3 由服务器主动发起断开
      • 0 正在建立连接或连接建立失败
      • 2 客户端主动发起断开
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="x-ua-compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Title</title>
    </head>
    <body>
    <input type="text" id="content"><button onclick="send_msg()">发送消息</button>
    <div id="content_list">
    
    </div>
    
    </body>
    <script type="application/javascript">
        var ws = new WebSocket("ws://192.168.12.10:9527/ws");  
        ws.onmessage = function (messageEvent) {
            console.log(messageEvent.data);
            var my_div = document.getElementById("content_list");
            var ptag = document.createElement("p");
            ptag.innerText = messageEvent.data;
            my_div.appendChild(ptag);
        };
        function send_msg() {
            var msg = document.getElementById("content").value;
            ws.send(msg);
        }
    
    </script>
    </html>
    

    2.单聊

    2.1服务端

    # 基于websocket 实现群聊
    import json
    
    from flask import Flask, request, render_template
    from geventwebsocket.handler import WebSocketHandler  # 请求处理WSGI HTTP
    from geventwebsocket.server import WSGIServer  # 替换Flask原来的wsgi服务
    from geventwebsocket.websocket import WebSocket  # 语法提示
    
    app = Flask(__name__)
    # socket_dict = {'xiaobangzhu':'abc','shangjia':'adcd'}  #
    socket_dict = {}  # 字段存储登录人员信息{用户的唯一标识:websocket连接}
    
    
    @app.route('/ws/<username>')  # 不再需要methods
    def my_ws(username):
        # print(request.environ)  # 输出原始请求信息
        ws_socket = request.environ.get('wsgi.websocket')  # type:WebSocket #语法提示  #获取连接
        print(ws_socket, username)
        socket_dict[username] = ws_socket  # 获取到的连接保存到列表中
        print(len(socket_dict), socket_dict)  # 查看连接数
        while True:
            msg = ws_socket.receive()  # 基于长连接socket 接收用户传递的数据
            msg_dict = json.loads(msg)  # msg_dict={receiver: receiver,sender: sender,data: msg,}
            receiver = msg_dict.get('receiver')  # 获取接收者的username
            receiver_socket = socket_dict.get(receiver)  # 根据receiver的username获取接收者的websocket地址
            receiver_socket.send(msg)  # 发送接收者的消息
    
    
    @app.route('/wechat')
    def wechat():
        return render_template('ws_one.html')  #
    
    
    if __name__ == '__main__':
        # app.run()
        http_serv = WSGIServer(('0.0.0.0', 9527), app, handler_class=WebSocketHandler)
        http_serv.serve_forever()
    
    

    2.2客户端(html文件)

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    基于JavaScript 实现Websocket客户端
    <body>
    <p>你的昵称<input type="text" id="username">
        <button onclick="login()">登录聊天室</button>
    </p>
    <p>给<input type="text" id="receiver">发送</p>
    
    <input type="text" id="content">
    <button onclick="send_msg()">发送消息</button>
    <div id="content_list" style=" 300px">
    
    </div>
    
    <script type="application/javascript">
        var ws = null;  //ws的路由地址
    
        function send_msg() {
            var msg = document.getElementById('content').value; //获取要发送的消息
            var receiver = document.getElementById('receiver').value;  //获取接收者的username
            var sender = document.getElementById('username').value;  //获取发送者的username
            var send_str = {    // 封装数据结构和要发送信息
                receiver: receiver,
                sender: sender,
                data: msg,
            };
    
            ws.send(JSON.stringify(send_str));
    
            // 显示我的信息
            var my_div = document.getElementById('content_list');
            var ptag = document.createElement('p');
            ptag.innerText = msg + " : " + '我';
            ptag.style.cssText = 'text-align:right';
            my_div.appendChild(ptag);
        }
            //接收消息
        function login() {
            var username = document.getElementById('username').value;
            ws = new WebSocket('ws://192.168.12.10:9527/ws/' + username);
            ws.onmessage = function (messageEvent) {
                //ws.onmessage  当ws客户端收到消息时执行回调函数
                //ws.onopen  当ws客户端建立完成连接时,status==1 时,执行的回调函数
                //ws.onclose 当ws客户端关闭中,或者关闭时,执行的回调函数status==2,3
                //ws.onerror 当ws客户端出现错误时,执行回调函数.
                console.log(messageEvent.data);
                var obj = JSON.parse(messageEvent.data);
                var my_div = document.getElementById('content_list');
                var ptag = document.createElement('p');
                ptag.innerText = obj.sender + " : " + obj.data;
                my_div.appendChild(ptag);
            };
    
        }
    
    
    </script>
    </body>
    </html>
    

    3. websocket 握手原理

    import socket, base64, hashlib
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('127.0.0.1', 9527))
    sock.listen(5)
    # 获取客户端socket对象
    conn, address = sock.accept()
    # 获取客户端的【握手】信息
    data = conn.recv(1024)
    print(data)
    """
    b'GET /ws HTTP/1.1
    
    Host: 127.0.0.1:9527
    
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0
    
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    
    Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
    
    Accept-Encoding: gzip, deflate
    
    Sec-WebSocket-Version: 13
    
    Origin: http://localhost:63342
    
    Sec-WebSocket-Extensions: permessage-deflate
    
    Sec-WebSocket-Key: jocLOLLq1BQWp0aZgEWL5A==
    
    Cookie: session=6f2bab18-2dc4-426a-8f06-de22909b967b
    
    Connection: keep-alive, Upgrade
    
    Pragma: no-cache
    
    Cache-Control: no-cache
    
    Upgrade: websocket
    
    '
    """
    
    # magic string为:258EAFA5-E914-47DA-95CA-C5AB0DC85B11
    magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
    
    
    def get_headers(data):
        header_dict = {}
        header_str = data.decode("utf8")
        for i in header_str.split("
    "):
            if str(i).startswith("Sec-WebSocket-Key"):
                header_dict["Sec-WebSocket-Key"] = i.split(":")[1].strip()
    
        return header_dict
    
    
    def get_header(data):
        """
         将请求头格式化成字典
         :param data:
         :return:
         """
        header_dict = {}
        data = str(data, encoding='utf-8')
    
        header, body = data.split('
    
    ', 1)
        header_list = header.split('
    ')
        for i in range(0, len(header_list)):
            if i == 0:
                if len(header_list[i].split(' ')) == 3:
                    header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
            else:
                k, v = header_list[i].split(':', 1)
                header_dict[k] = v.strip()
        return header_dict
    
    
    headers = get_headers(data)  # 提取请求头信息
    # 对请求头中的sec-websocket-key进行加密
    response_tpl = "HTTP/1.1 101 Switching Protocols
    " 
                   "Upgrade:websocket
    " 
                   "Connection: Upgrade
    " 
                   "Sec-WebSocket-Accept: %s
    " 
                   "WebSocket-Location: ws://127.0.0.1:9527
    
    "
    
    value = headers['Sec-WebSocket-Key'] + magic_string
    print(value,"magic+websocketkey")
    ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
    print(ac,"accept")
    response_str = response_tpl % (ac.decode('utf-8'))
    # 响应【握手】信息
    conn.send(response_str.encode("utf8"))
    
    while True:
        msg = conn.recv(8096)
        print(msg)
    
    
  • 相关阅读:
    May 1 2017 Week 18 Monday
    April 30 2017 Week 18 Sunday
    April 29 2017 Week 17 Saturday
    April 28 2017 Week 17 Friday
    April 27 2017 Week 17 Thursday
    April 26 2017 Week 17 Wednesday
    【2017-07-04】Qt信号与槽深入理解之一:信号与槽的连接方式
    April 25 2017 Week 17 Tuesday
    April 24 2017 Week 17 Monday
    为什么丑陋的UI界面却能创造良好的用户体验?
  • 原文地址:https://www.cnblogs.com/bigox/p/11355964.html
Copyright © 2011-2022 走看看