好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

python websocket学习使用

? ?

? ?python websocket学习使用 ? ?

2015-12-20 22:31?狂师? 阅读(33036)? 评论(1)?编辑?收藏 ? ?

前言

今天看了一些资料,记录一下心得。

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测试数据
Upgrade:?websocket
Connection:?Upgrade
Sec-WebSocket-Key:?dGhlIHNhbXBsZSBub25jZQ==
Origin:?http://example测试数据
服务器接收到信息后,取得其中的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来实现这段握手过程的话就是下面这样。

?def?handshake(conn):
????key?=None
????data?=?conn.recv(8192)
????if?not?len(data):
???????return?False
????for?line?in?data.split('\r\n\r\n')[0].split('\r\n')[1:]:
????????k,?v?=?line.split(':?')
????????if?k?=='Sec-WebSocket-Key':
????????????key?=base64.b64encode(hashlib.sha1(v?+'258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest())
????if?not?key:
????????conn.close()
????????return?False
????response?='HTTP/1.1?101?Switching?Protocols\r\n'???????????????'Upgrade:?websocket\r\n'???????????????'Connection:?Upgrade\r\n'???????????????'Sec-WebSocket-Accept:'+?key?+'\r\n\r\n'
????conn.send(response)
????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测试数据 Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example测试数据 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 中的处理代码:

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

握手完整代码

js 端

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

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

Python 端

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

HOST = 'localhost' PORT = 3368 MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' HANDSHAKE_STRING = "HTTP/1.1 101 Switching Protocols\r\n" \ ?????? "Upgrade:websocket\r\n" \ ?????? "Connection: Upgrade\r\n" \ ?????? "Sec-WebSocket-Accept: {1}\r\n" \ ?????? "WebSocket-Location: ws://{2}/chat\r\n" \ ?????? "WebSocket-Protocol:chat\r\n\r\n" ? ? def handshake(con): #con为用socket,accept()得到的socket #这里省略监听,accept的代码,具体可见blog:http://blog.csdn.net/ice110956/article/details/29830627 ? headers = {} ? shake = con.recv( 1024 ) ? ? ? if not len (shake): ?? return False ? ? ? header, data = shake.split( '\r\n\r\n' , 1 ) ? for line in header.split( '\r\n' )[ 1 :]: ?? key, val = line.split( ': ' , 1 ) ?? headers[key] = val ? ? ? if 'Sec-WebSocket-Key' not in headers: ?? print ( 'This socket is not websocket, client close.' ) ?? con.close() ?? return False ? ? ? sec_key = headers[ 'Sec-WebSocket-Key' ] ? res_key = base64.b64encode(hashlib.sha1(sec_key + MAGIC_STRING).digest()) ? ? ? str_handshake = HANDSHAKE_STRING.replace( '{1}' , res_key).replace( '{2}' , HOST + ':' + str (PORT)) ? print str_handshake ? con.send(str_handshake) 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 在兄弟数据中的索引。

?

完整代码

def recv_data( self , num): ? try : ?? all_data = self .con.recv(num) ?? if not len (all_data): ??? return False ? except : ?? return False ? else : ?? code_len = ord (all_data[ 1 ]) & 127 ?? if code_len = = 126 : ??? masks = all_data[ 4 : 8 ] ??? data = all_data[ 8 :] ?? elif code_len = = 127 : ??? masks = all_data[ 10 : 14 ] ??? data = all_data[ 14 :] ?? else : ??? masks = all_data[ 2 : 6 ] ??? data = all_data[ 6 :] ?? raw_str = "" ?? i = 0 ?? for d in data: ??? raw_str + = chr ( ord (d) ^ ord (masks[i % 4 ])) ??? i + = 1 ?? return raw_str

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

ws.send(str)

?

Python 发送

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

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

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

def send_data( self , data): ? if data: ?? data = str (data) ? else : ?? return False ? token = "\x81" ? length = len (data) ? if length < 126 : ?? token + = struct.pack( "B" , length) ? elif length < = 0xFFFF : ?? token + = struct.pack( "!BH" , 126 , length) ? else : ?? token + = struct.pack( "!BQ" , 127 , length) ? #struct为Python中处理二进制数的模块,二进制流为C,或网络流的形式。 ? data = '%s%s' % (token, data) ? self .con.send(data) ? return True ?js 端通过回调函数 ws.onmessage() 接受数据

?

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

最终代码

Python服务端

#?_*_?coding:utf-8?_*___author__?=?'Patrick'
?import?socketimport?threadingimport?sysimport?osimport?MySQLdbimport?base64import?hashlibimport?struct??
#?======?config?======HOST?=?'localhost'PORT?=?3368MAGIC_STRING?=?'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'HANDSHAKE_STRING?=?"HTTP/1.1?101?Switching?Protocols\r\n"?\??????"Upgrade:websocket\r\n"?\??????"Connection:?Upgrade\r\n"?\??????"Sec-WebSocket-Accept:?{1}\r\n"?\??????"WebSocket-Location:?ws://{2}/chat\r\n"?\??????"WebSocket-Protocol:chat\r\n\r\n"
??class?Th(threading.Thread):?def?__init__(self,?connection,):
??threading.Thread.__init__(self)
??self.con?=?connection??
?def?run(self):??while?True:???try:?????pass
??self.con.close()??
?def?recv_data(self,?num):??try:
???all_data?=?self.con.recv(num)???if?not?len(all_data):????return?False??except:???return?False??else:
???code_len?=?ord(all_data[1])?&?127???if?code_len?==?126:
????masks?=?all_data[4:8]
????data?=?all_data[8:]???elif?code_len?==?127:
????masks?=?all_data[10:14]
????data?=?all_data[14:]???else:
????masks?=?all_data[2:6]
????data?=?all_data[6:]
???raw_str?=?""
???i?=?0???for?d?in?data:
????raw_str?+=?chr(ord(d)?^?ord(masks[i?%?4]))
????i?+=?1???return?raw_str??
?#?send?data
?def?send_data(self,?data):??if?data:
???data?=?str(data)??else:???return?False
??token?=?"\x81"
??length?=?len(data)??if?length?<?126:
???token?+=?struct.pack("B",?length)??elif?length?<=?0xFFFF:
???token?+=?struct.pack("!BH",?126,?length)??else:
???token?+=?struct.pack("!BQ",?127,?length)??#struct为Python中处理二进制数的模块,二进制流为C,或网络流的形式。
??data?=?'%s%s'?%?(token,?data)
??self.con.send(data)??return?True??
??
?#?handshake
?def?handshake(con):
??headers?=?{}
??shake?=?con.recv(1024)??
??if?not?len(shake):???return?False
??
??header,?data?=?shake.split('\r\n\r\n',?1)??for?line?in?header.split('\r\n')[1:]:
???key,?val?=?line.split(':?',?1)
???headers[key]?=?val??
??if?'Sec-WebSocket-Key'?not?in?headers:???print?('This?socket?is?not?websocket,?client?close.')
???con.close()???return?False
??
??sec_key?=?headers['Sec-WebSocket-Key']
??res_key?=?base64.b64encode(hashlib.sha1(sec_key?+?MAGIC_STRING).digest())
??
??str_handshake?=?HANDSHAKE_STRING.replace('{1}',?res_key).replace('{2}',?HOST?+?':'?+?str(PORT))??print?str_handshake
??con.send(str_handshake)??return?True??
def?new_service():?"""start?a?service?socket?and?listen
?when?coms?a?connection,?start?a?new?thread?to?handle?it"""
??
?sock?=?socket.socket(socket.AF_INET,?socket.SOCK_STREAM)?try:
??sock.bind(('localhost',?3368))
??sock.listen(1000)??#链接队列大小
??print?"bind?3368,ready?to?use"
?except:??print("Server?is?already?running,quit")
??sys.exit()??
?while?True:
??connection,?address?=?sock.accept()??#返回元组(socket,add),accept调用时会进入waite状态
??print?"Got?connection?from?",?address??if?handshake(connection):???print?"handshake?success"
???try:
????t?=?Th(connection,?layout)
????t.start()????print?'new?thread?for?client?...'
???except:????print?'start?new?thread?error'
????connection.close()??
??
if?__name__?==?'__main__':
?new_service()

js客户 端

var?socket?=?new?WebSocket('ws://localhost:3368');
ws.onmessage?=?function(result,nTime){
alert("从服务端收到的数据:");
alert("最近一次发送数据到现在接收一共使用时间:"?+?nTime);
console.log(result);
}"?_ue_custom_node_="true">

?

? 分类 ? ? ? ? ? ?Python ?

查看更多关于python websocket学习使用的详细内容...

  阅读:34次