? ?
? ?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_strjs 端的 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学习使用的详细内容...