博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WebSocket
阅读量:6570 次
发布时间:2019-06-24

本文共 9914 字,大约阅读时间需要 33 分钟。

WebSocket协议是基于TCP的一种新的协议。WebSocket最初在HTML5规范中被引用为TCP连接,作为基于TCP的套接字API的占位符。它实现了浏览器与服务器全双工(full-duplex)通信。其本质是保持TCP连接,在浏览器和服务端通过Socket进行通信。用代码来深深的理解一下更嗨。

  

1. 启动服务端:

import socketsock = 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().........启动Socket服务器后,等待用户【连接】,然后进行收发数据。

2. 客户端连接

  

当客户端向服务端发送连接请求时,不仅连接还会发送【握手】信息,并等待服务端响应,至此连接才创建成功!

3. 建立连接【握手】

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('响应【握手】信息')

  

请求和响应的【握手】信息需要遵循规则:

  • 从请求【握手】信息中提取 Sec-WebSocket-Key
  • 利用magic_string 和 Sec-WebSocket-Key 进行hmac1加密,再进行base64加密
  • 将加密结果响应给客户端

注:magic string为:258EAFA5-E914-47DA-95CA-C5AB0DC85B11

请求【握手】信息为:

GET /chatsocket HTTP/1.1Host: 127.0.0.1:8002Connection: UpgradePragma: no-cacheCache-Control: no-cacheUpgrade: websocketOrigin: http://localhost:63342Sec-WebSocket-Version: 13Sec-WebSocket-Key: mnwFxiOlctXFN/DeMt1Amg==Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits......

  

提取Sec-WebSocket-Key值并加密:

import socketimport base64import 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_stringac = 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')).........

  

4.客户端和服务端收发数据

客户端和服务端传输数据时,需要对数据进行【封包】和【解包】。

客户端的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)
基于Python实现解包过程(未实现长内容)

 

解包详细过程:

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

第二步:向客户端发送数据【封包】

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

  

5. 基于Python实现简单示例

a. 基于Python socket实现的WebSocket服务端:

#!/usr/bin/env python# -*- coding:utf-8 -*-import socketimport base64import hashlib  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  def run():    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)    sock.bind(('127.0.0.1', 8003))    sock.listen(5)     conn, address = sock.accept()    data = conn.recv(1024)    headers = get_headers(data)    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"     value = headers['Sec-WebSocket-Key'] + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'    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'))     while True:        try:            info = conn.recv(8096)        except Exception as e:            info = None        if not info:            break        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')        send_msg(conn,body.encode('utf-8'))     sock.close() if __name__ == '__main__':    run()
View Code

b. 利用JavaScript类库实现客户端

    

  

 

6. 基于Tornado框架实现Web聊天室

Tornado是一个支持WebSocket的优秀框架,其内部原理正如1~5步骤描述,当然Tornado内部封装功能更加完整。

以下是基于Tornado实现的聊天室示例:

#!/usr/bin/env python# -*- coding:utf-8 -*-import uuidimport jsonimport tornado.ioloopimport tornado.webimport tornado.websocketclass 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()
app.py
    
Python聊天室
index.html
we chat

 

 

 

 

  

 

转载于:https://www.cnblogs.com/adamans/articles/7603705.html

你可能感兴趣的文章
暂时不想读研的几点理由
查看>>
增加临时表空间组Oracle11g单实例
查看>>
Diff Two Arrays
查看>>
浅谈java垃圾回收机制
查看>>
关于svn和maven结合使用的讨论
查看>>
前端第五天
查看>>
shell脚本学习之for循环
查看>>
MFC用CFile写文件
查看>>
stark组件(1):动态生成URL
查看>>
169. Majority Element
查看>>
Django Form表单学习总结
查看>>
大整数加法
查看>>
下拉菜单
查看>>
C/C++中extern关键字详解
查看>>
[清华集训2014]玛里苟斯
查看>>
Doctype作用?严格模式与混杂模式如何区分?它们有何意义
查看>>
jquery选择器(可见对象,不可见对象) +判断,对象(逆序)
查看>>
0029-求最小的数
查看>>
【MVC+EasyUI实例】对数据网格的增删改查(上)
查看>>
Socket编程
查看>>