websocket的簡單使用


一 輪詢

 什么是輪詢:設置每一段時間去訪問一次服務器,然后服務器返回最新的數據。這樣服務器的壓力會非常的大,並且還會有延遲。適用於小型程序。

 實現:再客戶端的頁面設置一個定時發送請求的任務,每個這段時間就要發送一個請求,來獲取最新的數據

 實例:

  app.py

from flask import Flask,render_template


app = Flask(__name__)


@app.route('/index')
def index():
    return render_template('index.html')

@app.route('/message')
def message():
    return "信息"


if __name__ == '__main__':
    app.run()
View Code

  index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>投票系統</h1>


    <script src="{{ url_for('static',filename='jquery-3.3.1.min.js')}}"></script>
    <script>
        function getMsg() {
            $.ajax({
                url:'/message',
                success:function (data) {
                    console.log(data)
                }
            })
        }
        setInterval(getMsg,2000)

    </script>
</body>
</html>
View Code

二 長輪詢

 什么是長輪詢:就是客戶端發送一次請求,服務端接收到了請求,然后將請求hold住,直到其中某個請求發送數據過來,然后再挨個將最新的數據分享到每一個請求,客戶端接收到請求以后,繼續發送新的請求。在服務端設置了超時時間,如果超出這段時間,就會斷開連接,服務端端口連接以后,客戶端又會繼續發送新的請求。

 實現原理:隊列,就是服務端只有一接收到最新的數據,就會將書更新,然后再逐一發送給每一個請求。客戶端創建一個對列,將數據通過隊列發給了服務端,然后斷開隊列,然后服務器端在於每一個請求之間創建對列,只要客戶端接收到了數據就會斷開然后繼續發送請求。

 實例:

  app.py:

from flask import Flask,render_template,request,session,redirect,jsonify
import uuid
from queue import Queue,Empty

app = Flask(__name__)
app.secret_key = 'xfsdfqw'

USERS = {
    '1':{'name':'王旭','count':0},
    '2':{'name':'放景洪','count':0},
    '3':{'name':'六五','count':0},
}

QUEUE_DICT = {

}
"""
{
    強哥:queue()
    龍哥:queue()
}
"""

@app.before_request
def before_request():
    if request.path == '/login':
        return None
    user_info = session.get('user_info')
    if user_info:
        return None
    return redirect('/login')


@app.route('/login',methods=['GET','POST'])
def login():
    if request.method == "GET":
        return render_template('login.html')
    else:
        uid = str(uuid.uuid4())
        session['user_info'] = {'id':uid,'name':request.form.get('user')}
        QUEUE_DICT[uid] = Queue()
        return redirect('/index')


@app.route('/index')
def index():
    return render_template('index.html',users=USERS)

@app.route('/message')
def message():

    result = {'status':True,'msg':None}
    queue = QUEUE_DICT[session.get('user_info').get('id')]
    try:
        v = queue.get(timeout=10)
    except Empty as e:
        v = None
    result['msg'] = v
    return jsonify(result)

@app.route('/vote')
def vote():
    userid = request.args.get('id')
    old = USERS[userid]['count']
    new = old + 1
    USERS[userid]['count'] = new

    for k,v in QUEUE_DICT.items():
        v.put({'userid':userid,'count':new})

    return '投票成功'

if __name__ == '__main__':
    app.run(threaded=True)
View Code

  login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form method="post">
    <input type="text" name="user">
    <input type="submit" value="提交">
</form>
</body>
</html>
View Code

  index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>投票系統</h1>
    <ul>
        {% for k,v in users.items() %}
            <li id="user_{{k}}"  ondblclick="vote('{{k}}')">{{v.name}} <span>{{v.count}}</span> </li>
        {% endfor %}

    </ul>

    <script src="{{ url_for('static',filename='jquery-3.3.1.min.js')}}"></script>
    <script>
        $(function () {
            getMsg();
        });

        function getMsg() {
            $.ajax({
                url:'/message',
                success:function (data) {
                    if(data.msg){
                        var nid = "#user_"+data.msg.userid;
                        $(nid).find('span').text(data.msg.count);
                    }
                    getMsg();
                }
            })
        }

        function vote(id) {
            $.ajax({
                url:'/vote',
                data:{'id':id},
                success:function (data) {
                    console.log('投票成功');
                }
            })
        }
    </script>
</body>
</html>
View Code

三 websocket

 為什么要有websocket

  初次接觸 WebSocket 的人,都會問同樣的問題:我們已經有了 HTTP 協議,為什么還需要另一個協議?它能帶來什么好處?

  答案很簡單,因為 HTTP 協議有一個缺陷:通信只能由客戶端發起。

  舉例來說,我們想了解今天的天氣,只能是客戶端向服務器發出請求,服務器返回查詢結果。HTTP 協議做不到服務器主動向客戶端推送信息。

  

  這種單向請求的特點,注定了如果服務器有連續的狀態變化,客戶端要獲知就非常麻煩。我們只能使用"輪詢":每隔一段時候,就發出一個詢問,了解服務器有沒有新的信息。最典型的場景就是聊天室。

  輪詢的效率低,非常浪費資源(因為必須不停連接,或者 HTTP 連接始終打開)。因此,工程師們一直在思考,有沒有更好的方法。WebSocket 就是這樣發明的。

 websocket的介紹:

  服務器可以主動向客戶端推送信息,客戶端也可以主動向服務器發送信息,是真正的雙向平等對話,屬於服務器推送技術的一種。

  建立的socket創建TCP之上:與 HTTP 協議有着良好的兼容性。默認端口也是80和443,並且握手階段采用 HTTP 協議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務器。

  數據格式比較輕量,性能開銷小,通信高效

  可以發送文本,也可以發送二進制數據。

  沒有同源限制,客戶端可以與任意服務器通信

  協議標識符是ws(如果加密,則為wss),服務器網址就是 URL。

   HTTP協議:http://www.xxx.com    請求+響應+斷開  一次請求一次響應

   WebSocket:ws://example.com:80/some/path

    1. 瀏覽器:發送請求“隨機字符串”
    2. 服務端:加密,再返還
    3. 瀏覽器:檢測加密后的結果是否是正確的
    4. 正確:建立連接【再也不斷開】

    

  詳細信息:http://www.ruanyifeng.com/blog/2017/05/websocket.html

 websocket的本質:

  瀏覽器:發送鏈接

  服務器:接受鏈接

  瀏覽器:發送握手信息Sec-WebSocket-Key: dCp5MdkY90EIJ83Qdddpjw==\r\n

  服務器:base64(sha1(dCp5MdkY90EIJ83Qdddpjw== + magic string))

  瀏覽器:接收值,並在內部進行校驗

    校驗成功:on_open

    校驗失敗:驗證失敗

  相互之間進行收發消息:

   瀏覽器發送消息到服務器:

1. 獲取第二個字節的,前7位
2.
    7位 <= 125;+0;mask_key=4;數據
    7位 == 126;+2;mask_key=4;數據
    7位 == 127;+8;mask_key=4;數據
3. mask_key=4;數據
View Code

   服務器向瀏覽器推送消息:1. 數據頭+數據

0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+
View Code

 請求過程解析:

  啟動服務器:啟動Socket服務器過后,等待着用戶的鏈接,然后進行數據的收發

import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8002))
sock.listen(5)
# 等待用戶連接
conn, address = sock.accept()
...
...
...
View Code

  客戶端連接:當客戶端向服務器發送連接的請求的時候,不僅發送連接請求,還會發送握手的信息,並等待服務器的響應,至此鏈接才會創建成功

<script type="text/javascript">
    var socket = new WebSocket("ws://127.0.0.1:8002/xxoo");
    ...
</script>
View Code

  建立握手: 

import socket
 
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8002))
sock.listen(5)
# 獲取客戶端socket對象
conn, address = sock.accept()
# 獲取客戶端的【握手】信息
data = conn.recv(1024)
...
...
...
conn.send('響應【握手】信息')
View Code

   請求和響應的【握手】信息需要遵循規則:

    從請求【握手】信息中提取 Sec-WebSocket-Key
    利用magic_string 和 Sec-WebSocket-Key 進行hmac1加密,再進行base64加密
    將加密結果響應給客戶端

    請求握手的信息為:

GET /chatsocket HTTP/1.1
Host: 127.0.0.1:8002
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://localhost:63342
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: mnwFxiOlctXFN/DeMt1Amg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
...
...
View Code

   提取Sec-WebSocket-Key的值並且加密:

import socket
import base64
import hashlib
 
def get_headers(data):
    """
    將請求頭格式化成字典
    :param data:
    :return:
    """
    header_dict = {}
    data = str(data, encoding='utf-8')
 
    for i in data.split('\r\n'):
        print(i)
    header, body = data.split('\r\n\r\n', 1)
    header_list = header.split('\r\n')
    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
 
 
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8002))
sock.listen(5)
 
conn, address = sock.accept()
data = conn.recv(1024)
headers = get_headers(data) # 提取請求頭信息
# 對請求頭中的sec-websocket-key進行加密
response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
      "Upgrade:websocket\r\n" \
      "Connection: Upgrade\r\n" \
      "Sec-WebSocket-Accept: %s\r\n" \
      "WebSocket-Location: ws://%s%s\r\n\r\n"
magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
value = headers['Sec-WebSocket-Key'] + magic_string
ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url'])
# 響應【握手】信息
conn.send(bytes(response_str, encoding='utf-8'))
...
...
...
View Code

  客戶端和服務端進行收發數據:收發數據時需要封包和解包,客戶端的javascript類的庫已經封裝好了封包和解包的過程,但是socket服務器需要手動實現

   獲取客戶端發送的數據進行解包:

info = conn.recv(8096)

    payload_len = info[1] & 127
    if payload_len == 126:
        extend_payload_len = info[2:4]
        mask = info[4:8]
        decoded = info[8:]
    elif payload_len == 127:
        extend_payload_len = info[2:10]
        mask = info[10:14]
        decoded = info[14:]
    else:
        extend_payload_len = None
        mask = info[2:6]
        decoded = info[6:]

    bytes_list = bytearray()
    for i in range(len(decoded)):
        chunk = decoded[i] ^ mask[i % 4]
        bytes_list.append(chunk)
    body = str(bytes_list, encoding='utf-8')
    print(body)
View Code

    解包的過程:

      

    The MASK bit simply tells whether the message is encoded. Messages from the client must be masked, so your server should expect this to be 1. (In fact, section 5.1 of the spec says that your server must disconnect from a client if that client sends an unmasked message.) When sending a frame back to the client, do not mask it and do not set the mask bit. We'll explain masking later. Note: You have to mask messages even when using a secure socket.RSV1-3 can be ignored, they are for extensions.

    The opcode field defines how to interpret the payload data: 0x0 for continuation, 0x1 for text (which is always encoded in UTF-8), 0x2 for binary, and other so-called "control codes" that will be discussed later. In this version of WebSockets, 0x3 to 0x7 and 0xB to 0xF have no meaning.

    The FIN bit tells whether this is the last message in a series. If it's 0, then the server will keep listening for more parts of the message; otherwise, the server should consider the message delivered. More on this later.

    Decoding Payload Length

    To read the payload data, you must know when to stop reading. That's why the payload length is important to know. Unfortunately, this is somewhat complicated. To read it, follow these steps:

        Read bits 9-15 (inclusive) and interpret that as an unsigned integer. If it's 125 or less, then that's the length; you're done. If it's 126, go to step 2. If it's 127, go to step 3.
        Read the next 16 bits and interpret those as an unsigned integer. You're done.
        Read the next 64 bits and interpret those as an unsigned integer (The most significant bit MUST be 0). You're done.

    Reading and Unmasking the Data

    If the MASK bit was set (and it should be, for client-to-server messages), read the next 4 octets (32 bits); this is the masking key. Once the payload length and masking key is decoded, you can go ahead and read that number of bytes from the socket. Let's call the data ENCODED, and the key MASK. To get DECODED, loop through the octets (bytes a.k.a. characters for text data) of ENCODED and XOR the octet with the (i modulo 4)th octet of MASK. In pseudo-code (that happens to be valid JavaScript):

     

    var DECODED = "";
    for (var i = 0; i < ENCODED.length; i++) {
        DECODED[i] = ENCODED[i] ^ MASK[i % 4];
    }

     

    Now you can figure out what DECODED means depending on your application.
View Code

   向客戶端發送數據進行封包:

def send_msg(conn, msg_bytes):
    """
    WebSocket服務端向客戶端發送消息
    :param conn: 客戶端連接到服務器端的socket對象,即: conn,address = socket.accept()
    :param msg_bytes: 向客戶端發送的字節
    :return: 
    """
    import struct

    token = b"\x81"
    length = len(msg_bytes)
    if length < 126:
        token += struct.pack("B", length)
    elif length <= 0xFFFF:
        token += struct.pack("!BH", 126, length)
    else:
        token += struct.pack("!BQ", 127, length)

    msg = token + msg_bytes
    conn.send(msg)
    return True
View Code

 實例1:websocket本質

  s1.py:

import socket
import base64
import hashlib
import redis

# conn = redis.Redis()
# conn.blpop()

def get_headers(data):
    """
    將請求頭格式化成字典
    :param data:
    :return:
    """
    header_dict = {}
    data = str(data, encoding='utf-8')
    header, body = data.split('\r\n\r\n', 1)
    header_list = header.split('\r\n')
    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

def send_msg(conn, msg_bytes):
    """
    WebSocket服務端向客戶端發送消息
    :param conn: 客戶端連接到服務器端的socket對象,即: conn,address = socket.accept()
    :param msg_bytes: 向客戶端發送的字節
    :return:
    """
    import struct

    token = b"\x81"
    length = len(msg_bytes)
    if length < 126:
        token += struct.pack("B", length)
    elif length <= 0xFFFF:
        token += struct.pack("!BH", 126, length)
    else:
        token += struct.pack("!BQ", 127, length)

    msg = token + msg_bytes
    conn.send(msg)
    return True


sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8002))
sock.listen(5)
# 等待用戶連接
conn, address = sock.accept()
print('有用戶來連接了',conn,address)

data = conn.recv(8096)

headers = get_headers(data) # 提取請求頭信息
print('用戶發送過來的握手信息',headers['Sec-WebSocket-Key'])

magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
value = headers['Sec-WebSocket-Key'] + magic_string
ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())


response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
      "Upgrade:websocket\r\n" \
      "Connection: Upgrade\r\n" \
      "Sec-WebSocket-Accept: %s\r\n" \
      "WebSocket-Location: ws://%s%s\r\n\r\n"

response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url'])

conn.send(bytes(response_str, encoding='utf-8'))

while True:
    info = conn.recv(8096)
    # 1. 獲取第2個字節 content[1] & 127
    payload_len = info[1] & 127
    if payload_len == 126:
        extend_payload_len = info[2:4]
        mask = info[4:8]
        decoded = info[8:]
    elif payload_len == 127:
        extend_payload_len = info[2:10]
        mask = info[10:14]
        decoded = info[14:]
    else:
        extend_payload_len = None
        mask = info[2:6]
        decoded = info[6:]

    bytes_list = bytearray()
    for i in range(len(decoded)):
        chunk = decoded[i] ^ mask[i % 4]
        bytes_list.append(chunk)
    body = str(bytes_list, encoding='utf-8')
    print(body)
    body = body + ' sb'


    send_msg(conn,body.encode('utf-8'))
View Code

  s1.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script type="text/javascript">
    /*
    1. 創建socket
    2. 發送【握手(驗證)信息】
     */
    var socket = new WebSocket("ws://127.0.0.1:8002/xxoo");
    socket.onopen = function () {
        /* 與服務器端連接成功后,自動執行 */
        console.log('服務端加密規則正確,連接成功');
    }
    socket.onmessage = function (event) {
        /* 服務器端向客戶端發送數據時,自動執行 */
        var response = event.data;
        console.log('獲取websocekt推送的消息:',response)
    };
</script>
</body>
</html>
View Code

 實例2:

  app.py:

from flask import Flask,render_template,request,session,redirect,jsonify
import uuid
from geventwebsocket.handler import WebSocketHandler
from gevent.pywsgi import WSGIServer
import json


app = Flask(__name__)
app.secret_key = 'xfsdfqw'

USERS = {
    '1':{'name':'王旭','count':0},
    '2':{'name':'放景洪','count':0},
    '3':{'name':'六五','count':0},
}


@app.before_request
def before_request():
    if request.path == '/login':
        return None
    user_info = session.get('user_info')
    if user_info:
        return None
    return redirect('/login')


@app.route('/login',methods=['GET','POST'])
def login():
    if request.method == "GET":
        return render_template('login.html')
    else:
        uid = str(uuid.uuid4())
        session['user_info'] = {'id':uid,'name':request.form.get('user')}
        return redirect('/index')


@app.route('/index')
def index():
    return render_template('index.html',users=USERS)

WS_DICT = {

}

@app.route('/message')
def message():
    if request.environ.get('wsgi.websocket'):
        ws = request.environ['wsgi.websocket']
        # 1. 剛連接成功
        uid = session.get('user_info').get('id')
        WS_DICT[uid] = ws

        from geventwebsocket.websocket import WebSocket
        while True:
            # 2. 等待用戶發送消息,並接受
            message = ws.receive()
            # 關閉:message=None
            if not message:
                del WS_DICT[uid]
                break

            old = USERS[message]['count']
            new = old + 1
            USERS[message]['count'] = new

            data = {'user':message,'count':new}

            for k,v in WS_DICT.items():
                # 3. 向客戶端推送消息
                v.send(json.dumps(data))

    return "Connected!"

if __name__ == '__main__':
    http_server = WSGIServer(('127.0.0.1', 5000), app, handler_class=WebSocketHandler)
    http_server.serve_forever()    
View Code

  login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form method="post">
    <input type="text" name="user">
    <input type="submit" value="提交">
</form>
</body>
</html>
View Code

  index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>投票系統</h1>
    <a onclick="closeConn();">關閉連接</a>
    <a onclick="createConn();">創建連接</a>
    <ul>
        {% for k,v in users.items() %}
            <li id="user_{{k}}"  ondblclick="vote('{{k}}')">{{v.name}} <span>{{v.count}}</span> </li>
        {% endfor %}

    </ul>

    <script src="{{ url_for('static',filename='jquery-3.3.1.min.js')}}"></script>
    <script>

        var socket = null;

        function socketInit() {
            socket.onopen = function () {
            /* 與服務器端連接成功后,自動執行 */
        };

            socket.onmessage = function (event) {
                /* 服務器端向客戶端發送數據時,自動執行 */
                var response = JSON.parse(event.data); // {'user':1,'count':new}
                var nid = '#user_' + response.user;
                $(nid).find('span').text(response.count)
            };

            socket.onclose = function (event) {
                /* 服務器端主動斷開連接時,自動執行 */
            };

        }

        /*
        我要投票
        id:帥哥id
         */
        function vote(id) {

            socket.send(id);
        }

        function closeConn() {
            socket.close()
        }
        function createConn() {
            socket = new WebSocket("ws://127.0.0.1:5000/message");
            socketInit();
        }
    </script>
</body>
</html>
View Code

  highchart的使用:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>投票系統</h1>
    <ul>
        {% for k,v in users.items() %}
            <li id="user_{{k}}"  ondblclick="vote('{{k}}')">{{v.name}} <span>{{v.count}}</span> </li>
        {% endfor %}

    </ul>

    <div id="container" style="width: 400px;height:300px;"></div>


    <script src="{{ url_for('static',filename='jquery-3.3.1.min.js')}}"></script>
    <script src="{{ url_for('static',filename='js/highcharts.js')}}"></script>
    <script src="{{ url_for('static',filename='js/highcharts-3d.js')}}"></script>
    <script src="{{ url_for('static',filename='highcharts-zh_CN.js')}}"></script>
    <script>
        var chart = null;
        var options = {
            chart: {
                type: 'column',
                options3d: {
                    enabled: true,
                    alpha: 15,
                    beta: 15,
                    viewDistance: 25,
                    depth: 40
                },
                marginTop: 80,
                marginRight: 40
            },
            title: {
                text: '全棧7期最帥的男人'
            },
            xAxis: {
                visible:false
            },
            yAxis: {
                allowDecimals: false,
                min: 0,
                title: {
                    text: '得票'
                }
            },
            tooltip: {
                headerFormat: '<b>{point.key}</b><br>',
                pointFormat: '<span style="color:{series.color}">\u25CF</span> {series.name}: {point.y} '
            },
            plotOptions: {
                column: {
                    //stacking: 'normal',
                    //depth: 40
                }
            },
            series: [{
                    name: '王旭',
                    data: [5]
                }, {
                    name: '放景洪',
                    data: [3]
                }, {
                    name: '劉武',
                    data: [2]
                }
            ]
        };

        $(function () {
            chart = Highcharts.chart('container', options);
            getMsg();
        });

        function getMsg() {
            $.ajax({
                url:'/message',
                success:function (data) {
                    if(data.msg){
                        var nid = "#user_"+data.msg.userid;
                        $(nid).find('span').text(data.msg.count);
                        // chart.series[0].data[0].update(18);
                    }
                    getMsg();
                }
            })
        }

        function vote(id) {
            $.ajax({
                url:'/vote',
                data:{'id':id},
                success:function (data) {
                    console.log('投票成功');
                }
            })
        }
    </script>
</body>
</html>
View Code

 實例3:flaskchat

  服務端:安裝支持websocket的組件   使用的是gevent-websocket  django使用的是:channel。而Tornado原生支持

  客戶端:支持js創建socket

  x1.py:  

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from geventwebsocket.handler import WebSocketHandler
from gevent.pywsgi import WSGIServer
from flask import Flask, request, render_template, abort

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('chat.html')


@app.route('/message')
def message():
    if request.environ.get('wsgi.websocket'):
        ws = request.environ['wsgi.websocket']

        from geventwebsocket.websocket import WebSocket
        while True:
            message = ws.receive()
            if not message:
                break
            print(message)
            ws.send(message)

    return "Connected!"


if __name__ == '__main__':
    http_server = WSGIServer(('', 5000), app, handler_class=WebSocketHandler)
    http_server.serve_forever()
View Code

  chat.py:

from __future__ import print_function

import json

from gevent import monkey
monkey.patch_all()

from flask import Flask, render_template
from werkzeug.debug import DebuggedApplication

from geventwebsocket import WebSocketServer, WebSocketApplication, Resource

flask_app = Flask(__name__)
flask_app.debug = True


class ChatApplication(WebSocketApplication):
    def on_open(self):
        print("Some client connected!")

    def on_message(self, message):
        if message is None:
            return

        message = json.loads(message)

        if message['msg_type'] == 'message':
            self.broadcast(message)
        elif message['msg_type'] == 'update_clients':
            self.send_client_list(message)

    def send_client_list(self, message):
        current_client = self.ws.handler.active_client
        current_client.nickname = message['nickname']

        self.ws.send(json.dumps({
            'msg_type': 'update_clients',
            'clients': [
                getattr(client, 'nickname', 'anonymous')
                for client in self.ws.handler.server.clients.values()
            ]
        }))

    def broadcast(self, message):
        for client in self.ws.handler.server.clients.values():
            client.ws.send(json.dumps({
                'msg_type': 'message',
                'nickname': message['nickname'],
                'message': message['message']
            }))

    def on_close(self, reason):
        print("Connection closed!")


@flask_app.route('/')
def index():
    return render_template('index.html')

WebSocketServer(
    ('0.0.0.0', 8000),

    Resource([
        ('^/chat', ChatApplication),
        ('^/.*', DebuggedApplication(flask_app))
    ]),

    debug=False
).serve_forever()
View Code

  chat.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Python聊天室</title>
</head>
<body>
<div>
    <input type="text" id="txt"/>
    <input type="button" id="btn" value="提交" onclick="sendMsg();"/>
    <input type="button" id="close" value="關閉連接" onclick="closeConn();"/>
</div>
<div id="container" style="border: 1px solid #dddddd;margin: 20px;min-height: 500px;">

</div>

<script src="{{ url_for('static', filename='jquery-2.1.4.min.js') }}"></script>
<script type="text/javascript">
    $(function () {
        wsUpdater.start();
    });

    var wsUpdater = {
        socket: null,
        uid: null,
        start: function () {
            var url = "ws://127.0.0.1:5000/message";
            wsUpdater.socket = new WebSocket(url);
            wsUpdater.socket.onmessage = function (event) {
                console.log(event);
                if (wsUpdater.uid) {
                    wsUpdater.showMessage(event.data);
                } else {
                    wsUpdater.uid = event.data;
                }
            }
        },
        showMessage: function (content) {
            $('#container').append(content);
        }
    };

    function sendMsg() {
        var msg = {
            uid: wsUpdater.uid,
            message: $("#txt").val()
        };
        wsUpdater.socket.send(JSON.stringify(msg));
    }
    
    function closeConn() {
        wsUpdater.socket.close();
    }
</script>

</body>
</html>
View Code

四 Tornado框架

 Tornado是一個支持WebSocket的優秀框架,其內部原理正如1~5步驟描述,當然Tornado內部封裝功能更加完整。

 服務端:自己原生支持websocket

 瀏覽器:支持使用js創建socket

 框架介紹:

            Tornado:短小精悍+第三方庫的支持+異步非阻塞
              Flask:短小精悍+第三方庫的支持
             Django:重武器
         自定義框架:...
             bottle: 微型(1000)
View Code

 Tornado安裝:pip install tornado

 Tornado的詳細信息:http://www.cnblogs.com/wupeiqi/tag/Tornado/

 實例:以下是基於Tornado實現的聊天室示例:

  app.py:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import uuid
import json
import tornado.ioloop
import tornado.web
import tornado.websocket


class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.render('index.html')


class ChatHandler(tornado.websocket.WebSocketHandler):
    # 用戶存儲當前聊天室用戶
    waiters = set()
    # 用於存儲歷時消息
    messages = []

    def open(self):
        """
        客戶端連接成功時,自動執行
        :return: 
        """
        ChatHandler.waiters.add(self)
        uid = str(uuid.uuid4())
        self.write_message(uid)

        for msg in ChatHandler.messages:
            content = self.render_string('message.html', **msg)
            self.write_message(content)

    def on_message(self, message):
        """
        客戶端連發送消息時,自動執行
        :param message: 
        :return: 
        """
        msg = json.loads(message)
        ChatHandler.messages.append(message)

        for client in ChatHandler.waiters:
            content = client.render_string('message.html', **msg)
            client.write_message(content)

    def on_close(self):
        """
        客戶端關閉連接時,,自動執行
        :return: 
        """
        ChatHandler.waiters.remove(self)


def run():
    settings = {
        'template_path': 'templates',
        'static_path': 'static',
    }
    application = tornado.web.Application([
        (r"/", IndexHandler),
        (r"/chat", ChatHandler),
    ], **settings)
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()


if __name__ == "__main__":
    run()
View Code

  index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Python聊天室</title>
</head>
<body>
    <div>
        <input type="text" id="txt"/>
        <input type="button" id="btn" value="提交" onclick="sendMsg();"/>
        <input type="button" id="close" value="關閉連接" onclick="closeConn();"/>
    </div>
    <div id="container" style="border: 1px solid #dddddd;margin: 20px;min-height: 500px;">

    </div>

    <script src="/static/jquery-2.1.4.min.js"></script>
    <script type="text/javascript">
        $(function () {
            wsUpdater.start();
        });

        var wsUpdater = {
            socket: null,
            uid: null,
            start: function() {
                var url = "ws://127.0.0.1:8888/chat";
                wsUpdater.socket = new WebSocket(url);
                wsUpdater.socket.onmessage = function(event) {
                    console.log(event);
                    if(wsUpdater.uid){
                        wsUpdater.showMessage(event.data);
                    }else{
                        wsUpdater.uid = event.data;
                    }
                }
            },
            showMessage: function(content) {
                $('#container').append(content);
            }
        };

        function sendMsg() {
            var msg = {
                uid: wsUpdater.uid,
                message: $("#txt").val()
            };
            wsUpdater.socket.send(JSON.stringify(msg));
        }

</script>

</body>
</html>
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM