WebSocket相关

原文:http://www.cnblogs.com/jinjiangongzuoshi/p/5062092.html

前言

websocket是html5引入的一个新特性,传统的web应用是通过http协议来提供支持,如果要实时同步传输数据,需要轮询,效率低下 websocket是类似socket通信,web端连接服务器后,握手成功,一直保持连接,可以理解为长连接,这时服务器就可以主动给客户端发送数据,实现数据的自动更新。 使用websocket需要注意浏览器和当前的版本,不同的浏览器提供的支持不一样,因此设计服务器的时候,需要考虑。

进一步简述

 websocket是一个浏览器和服务器通信的新的协议,一般而言,浏览器和服务器通信最常用的是http协议,但是http协议是无状态的,每次浏览器请求信息,服务器返回信息后这个浏览器和服务器通信的信道就被关闭了,这样使得服务器如果想主动给浏览器发送信息变得不可能了,服务器推技术在http时代的解决方案一个是客户端去轮询,或是使用comet技术,而websocket则和一般的socket一样,使得浏览器和服务器建立了一个双工的通道。 具体的websocket协议在rfc6455里面有,这里简要说明一下。websocket通信需要先有个握手的过程,使得协议由http转变为webscoket协议,然后浏览器和服务器就可以利用这个socket来通信了。 首先浏览器发送握手信息,要求协议转变为websocket

GET / HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com

服务器接收到信息后,取得其中的Sec-WebSocket-Key,将他和一个固定的字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11做拼接,得到的字符串先用sha1做一下转换,再用base64转换一下,就得到了回应的字符串,这样服务器端发送回的消息是这样的:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

这样握手就完成了,用python来实现这段握手过程的话就是下面这样:

 1 def handshake(conn):
 2     key =None
 3     data = conn.recv(8192)
 4     if not len(data):
 5        return False
 6     for line in data.split('

')[0].split('
')[1:]:
 7         k, v = line.split(': ')
 8         if k =='Sec-WebSocket-Key':
 9             key =base64.b64encode(hashlib.sha1(v +'258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest())
10     if not key:
11         conn.close()
12         return False
13     response ='HTTP/1.1 101 Switching Protocols
'
14                'Upgrade: websocket
'
15                'Connection: Upgrade
'
16                'Sec-WebSocket-Accept:'+ key +'

'
17     conn.send(response)
18     return True

握手过程完成之后就是信息传输了,websocket的数据信息格式是这样的:

+-+-+-+-+-------+-+-------------+-------------------------------+
 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 ...                |
+---------------------------------------------------------------+

值得注意的是payload len这项,表示数据的长度有多少,如果小于126,那么payload len就是数据的长度,如果是126那么接下来2个字节是数据长度,如果是127表示接下来8个字节是数据长度,然后后面会有四个字节的mask,真实数据要由payload data和mask做异或才能得到,这样就可以得到数据了。发送数据的格式和接受的数据类似,具体细节可以去参考rfc6455,这里就不过多赘述了。

Python的Websocket客户端:Websocket-Client

Websocket-Client 是 Python 上的 Websocket 客户端。它只支持 hybi-13,且所有的 Websocket API 都支持同步。

Installation
This module is tested on Python 2.7 and Python 3.x.

Type "python setup.py install" or "pip install websocket-client" to install.

Caution!

from v0.16.0, we can install by "pip install websocket-client" for python 3.

This module depend on

six
backports.ssl_match_hostname for Python 2.x

Python通过websocket与js客户端通信示例分析

这里,介绍如何使用 Python 与前端 js 进行通信。

websocket 使用 HTTP 协议完成握手之后,不通过 HTTP 直接进行 websocket 通信。

于是,使用 websocket 大致两个步骤:使用 HTTP 握手,通信。

js 处理 websocket 要使用 ws 模块; Python 处理则使用 socket 模块建立 TCP 连接即可,比一般的 socket ,只多一个握手以及数据处理的步骤。

包格式

js 客户端先向服务器端 python 发送握手包,格式如下:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

服务器回应格式:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

其中, Sec-WebSocket-Key 是随机的,服务器用这些数据构造一个 SHA-1 信息摘要。

方法为: key+migic , SHA-1  加密, base-64 加密

Python 中的处理代码:

1 MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
2 res_key = base64.b64encode(hashlib.sha1(sec_key + MAGIC_STRING).digest())

握手完整代码

js 端

js 中有处理 websocket 的类,初始化后自动发送握手包,如下:

1 var socket = new WebSocket('ws://localhost:3368');

 Python 端

Python 用 socket 接受得到握手字符串,处理后发送

 1 HOST = 'localhost'
 2 PORT = 3368
 3 MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
 4 HANDSHAKE_STRING = "HTTP/1.1 101 Switching Protocols
" 
 5                    "Upgrade:websocket
" 
 6                    "Connection: Upgrade
" 
 7                    "Sec-WebSocket-Accept: {1}
" 
 8                    "WebSocket-Location: ws://{2}/chat
" 
 9                    "WebSocket-Protocol:chat

"
10 
11 
12 def handshake(con):
13     # con为用socket,accept()得到的socket
14     # 这里省略监听,accept的代码,具体可见blog:http://blog.csdn.net/ice110956/article/details/29830627
15     headers = {}
16     shake = con.recv(1024)
17 
18     if not len(shake):
19         return False
20 
21     header, data = shake.split('

', 1)
22     for line in header.split('
')[1:]:
23         key, val = line.split(': ', 1)
24         headers[key] = val
25 
26     if 'Sec-WebSocket-Key' not in headers:
27         print ('This socket is not websocket, client close.')
28         con.close()
29         return False
30 
31     sec_key = headers['Sec-WebSocket-Key']
32     res_key = base64.b64encode(hashlib.sha1(sec_key + MAGIC_STRING).digest())
33 
34     str_handshake = HANDSHAKE_STRING.replace('{1}', res_key).replace('{2}',
35                                                                      HOST + ':' + str(
36                                                                          PORT))
37     print str_handshake
38     con.send(str_handshake)
39 
40 
41 return True

通信

不同版本的浏览器定义的数据帧格式不同, Python 发送和接收时都要处理得到符合格式的数据包,才能通信。

Python 接收

Python 接收到浏览器发来的数据,要解析后才能得到其中的有用数据。

 固定字节:

( 1000 0001 或是 1000 0002 )这里没用,忽略

包长度字节:

第一位肯定是 1 ,忽略。剩下 7 个位可以得到一个整数 (0 ~ 127) ,其中

( 1-125 )表此字节为长度字节,大小即为长度;

(126)表接下来的两个字节才是长度;

(127)表接下来的八个字节才是长度;

用这种变长的方式表示数据长度,节省数据位。

mark 掩码:

mark 掩码为包长之后的 4 个字节,之后的兄弟数据要与 mark 掩码做运算才能得到真实的数据。

兄弟数据:

得到真实数据的方法:将兄弟数据的每一位 x ,和掩码的第 i%4 位做 xor 运算,其中 i 是 x 在兄弟数据中的索引。

完整代码:

 1 def recv_data(self, num):
 2     try:
 3         all_data = self.con.recv(num)
 4         if not len(all_data):
 5             return False
 6     except:
 7         return False
 8     else:
 9         code_len = ord(all_data[1]) & 127
10         if code_len == 126:
11             masks = all_data[4:8]
12             data = all_data[8:]
13         elif code_len == 127:
14             masks = all_data[10:14]
15             data = all_data[14:]
16         else:
17             masks = all_data[2:6]
18             data = all_data[6:]
19         raw_str = ""
20         i = 0
21         for d in data:
22             raw_str += chr(ord(d) ^ ord(masks[i % 4]))
23             i += 1
24         return raw_str

js 端的 ws 对象,通过 ws.send(str) 即可发送

1 ws.send(str)

Python 发送

Python 要包数据发送,也需要处理

固定字节:固定的 1000 0001( ‘ x81 ′ )

包长:根据发送数据长度是否超过 125 , 0xFFFF(65535) 来生成 1 个或 3 个或 9 个字节,来代表数据长度。

 1 def send_data(self, data):
 2     if data:
 3         data = str(data)
 4     else:
 5         return False
 6     token = "x81"
 7     length = len(data)
 8     if length < 126:
 9         token += struct.pack("B", length)
10     elif length <= 0xFFFF:
11         token += struct.pack("!BH", 126, length)
12     else:
13         token += struct.pack("!BQ", 127, length)
14     #struct为Python中处理二进制数的模块,二进制流为C,或网络流的形式。
15     data = '%s%s' % (token, data)
16     self.con.send(data)
17     return True

js 端通过回调函数 ws.onmessage() 接受数据 

1 ws.onmessage = function(result,nTime){
2 alert("从服务端收到的数据:");
3 alert("最近一次发送数据到现在接收一共使用时间:" + nTime);
4 console.log(result);
5 }

最终代码:

Python服务端

  1 # _*_ coding:utf-8 _*_
  2 __author__ = 'Patrick'
  3 
  4 import socket
  5 import threading
  6 import sys
  7 import os
  8 import MySQLdb
  9 import base64
 10 import hashlib
 11 import struct
 12 
 13 # ====== config ======
 14 HOST = 'localhost'
 15 PORT = 3368
 16 MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
 17 HANDSHAKE_STRING = "HTTP/1.1 101 Switching Protocols
" 
 18                    "Upgrade:websocket
" 
 19                    "Connection: Upgrade
" 
 20                    "Sec-WebSocket-Accept: {1}
" 
 21                    "WebSocket-Location: ws://{2}/chat
" 
 22                    "WebSocket-Protocol:chat

"
 23 
 24 
 25 class Th(threading.Thread):
 26     def __init__(self, connection, ):
 27         threading.Thread.__init__(self)
 28         self.con = connection
 29 
 30     def run(self):
 31         while True:
 32             try:
 33                 pass
 34         self.con.close()
 35 
 36     def recv_data(self, num):
 37         try:
 38             all_data = self.con.recv(num)
 39             if not len(all_data):
 40                 return False
 41         except:
 42             return False
 43         else:
 44             code_len = ord(all_data[1]) & 127
 45             if code_len == 126:
 46                 masks = all_data[4:8]
 47                 data = all_data[8:]
 48             elif code_len == 127:
 49                 masks = all_data[10:14]
 50                 data = all_data[14:]
 51             else:
 52                 masks = all_data[2:6]
 53                 data = all_data[6:]
 54             raw_str = ""
 55             i = 0
 56             for d in data:
 57                 raw_str += chr(ord(d) ^ ord(masks[i % 4]))
 58                 i += 1
 59             return raw_str
 60 
 61     # send data
 62     def send_data(self, data):
 63         if data:
 64             data = str(data)
 65         else:
 66             return False
 67         token = "x81"
 68         length = len(data)
 69         if length < 126:
 70             token += struct.pack("B", length)
 71         elif length <= 0xFFFF:
 72             token += struct.pack("!BH", 126, length)
 73         else:
 74             token += struct.pack("!BQ", 127, length)
 75         # struct为Python中处理二进制数的模块,二进制流为C,或网络流的形式。
 76         data = '%s%s' % (token, data)
 77         self.con.send(data)
 78         return True
 79 
 80     # handshake
 81     def handshake(con):
 82         headers = {}
 83         shake = con.recv(1024)
 84 
 85         if not len(shake):
 86             return False
 87 
 88         header, data = shake.split('

', 1)
 89         for line in header.split('
')[1:]:
 90             key, val = line.split(': ', 1)
 91             headers[key] = val
 92 
 93         if 'Sec-WebSocket-Key' not in headers:
 94             print ('This socket is not websocket, client close.')
 95             con.close()
 96             return False
 97 
 98         sec_key = headers['Sec-WebSocket-Key']
 99         res_key = base64.b64encode(
100             hashlib.sha1(sec_key + MAGIC_STRING).digest())
101 
102         str_handshake = HANDSHAKE_STRING.replace('{1}', res_key).replace('{2}',
103                                                                          HOST + ':' + str(
104                                                                              PORT))
105         print str_handshake
106         con.send(str_handshake)
107         return True
108 
109 
110 def new_service():
111     """start a service socket and listen
112     when coms a connection, start a new thread to handle it"""
113 
114     sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
115     try:
116         sock.bind(('localhost', 3368))
117         sock.listen(1000)
118         # 链接队列大小
119         print "bind 3368,ready to use"
120     except:
121         print("Server is already running,quit")
122         sys.exit()
123 
124     while True:
125         connection, address = sock.accept()
126         # 返回元组(socket,add),accept调用时会进入waite状态
127         print "Got connection from ", address
128         if handshake(connection):
129             print "handshake success"
130             try:
131                 t = Th(connection, layout)
132                 t.start()
133                 print 'new thread for client ...'
134             except:
135                 print 'start new thread error'
136                 connection.close()
137 
138 
139 if __name__ == '__main__':
140     new_service()

js客户端

1 <script>
2 var socket = new WebSocket('ws://localhost:3368');
3 ws.onmessage = function(result,nTime){
4 alert("从服务端收到的数据:");
5 alert("最近一次发送数据到现在接收一共使用时间:" + nTime);
6 console.log(result);
7 }
8 </script>
原文地址:https://www.cnblogs.com/DI-DIAO/p/9246344.html