赞
踩
网上的好多教程都是基于Python2.X的,虽然差不多,但是对于我们这些刚刚听说过webSocket的小白来说,微小的差异也会让我们debug半天,所以以此博客做我实现的记录,仅供后来者参考
需要用到的知识:
python模块:socket, struct,hashlib, threading
JavaScript websocket简单使用
chrome开发者工具(对于websocket的报错更加详细,利于debug)
格式如下:
GET / HTTP/1.1\r\n
/省略不相关信息/
Sec-WebSocket-Key: G4cZeCrg+0Znd6MLvVJSTg==\r\n
Connection: keep-alive, Upgrade\r\n
Upgrade: websocket\r\n\r\n
Magic_string = 258EAFA5-E914-47DA-95CA-C5AB0DC85B11(固定)
combined_string = Sec-WebSocket-Key + Magic_string
对 combined_String 取sha1数字摘要,然后进行base64编码,得到Sec-WebSocket-Accept_str
返回格式
HTTP/1.1 101 Web Socket Protocol Handshake
/省略不相关信息/
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: Sec-WebSocket-Accept_str
向客户端发送的websocket报文分为3部分:
固定部分 ‘\x81’
报文内容长度
报文内容
将三部分有序组装即可使用socket.send()发送给哭护短
客户端发送至server的websocket报文分为四部分:
固定部分 ‘\x81’
报文内容长度(同上文”报文内容长度”)
掩码mask
报文内容content
获得掩码mask和content,注意报文内容长度不同会影响mask和content在websocket报文中的起始位置
对content进行按字节循环与处理(python描述):
result = ""
i = 0
for d in content:
result += d ^ chr(d ^ ord(mask[i%4]))
i += 1
得到result即为client发送到server的数据
为了能够使大家先体验一把websocket的乐趣,同时也可以为后面server构建过程中能够有debug参照,首先实现基于JavaScript的websocket的客户端
简单理解就是,无阻塞,当发生A事件时,自动调用B函数,处理A事件。在js中,实现这一机制的就是回调函数的使用。样例:
var ws = new websocket("ws://127.0.0.1:8124");
ws.on("error", function(e){
console.log(e.message);
}):
样例第一行表示创建websocket对象
样例第二行至第四行,error为关键字,function(e){…}即为回调函数。表示,当ws发生错误时,调用function(e){…}对错误进行处理
var ws = new websocket("ws://127.0.0.1:8124");
解释url字段
ws 表示使用websocket协议,与http/https相似
url,即表示目的地址 目的端口
<!DOCTYPE html> <html> <head> <title>websocket</title> </head> <body> <script type="text/javascript"> var ws; function startWS() { console.log('start once again'); // ws = new WebSocket("ws://127.0.0.1:8124"); ws = new WebSocket("ws://echo.websocket.org"); ws.onopen = function (msg) { console.log('webSocket opened'); }; ws.onmessage = function (message) { console.log('receive message : ' + message.data); }; ws.onerror = function (error) { console.log('error :' + error.name + error.number); }; ws.onclose = function () { console.log('webSocket closed'); }; } function sendMessage () { console.log("sending a message"); ws.send("websocket from js"); } </script> <button onclick="startWS()">createWebsocket</button><br> <button onclick="sendMessage()">sendMessage</button> </body> </html>
将上述代码保存问xxx.html文件,即可使用浏览器打开。可在浏览器“开发者工具”->控制台console中进行查看client运行情况
fmt为由特定字符组成的字符串,函数功能为,将python数据类型value1,value2转化为C数据类型
fmt字符类型:
Format C Type Python type Standard size x pad byte no value c char bytes of length 1 b signed char integer 1 B unsigned char integer 1 ? _Bool bool 1 h short integer 2 H unsigned short integer 2 i int integer 4 I unsigned int integer 4 l long integer 4 L unsigned long integer 4 q long long integer 8 Q unsigned long long integer 8 n ssize_t integer N size_t integer e (7) float 2 f float float 4 d double float 8 s char[] bytes p char[] bytes P void * integer (6)
即为struct.pack(fmt, value..)操作的逆操作
python分片
字符串:替换,子字符串,查找
str <=> bytes
if __name__ == "__main__": serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = ("127.0.0.1", 8124) serverSocket.bind(host) serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) serverSocket.listen(5) print("server running") while True: print("getting connection") clientSocket, addressInfo = serverSocket.accept() print("get connected") receivedData = str(clientSocket.recv(2048)) # print(receivedData) entities = receivedData.split("\\r\\n") Sec_WebSocket_Key = entities[11].split(":")[1].strip() + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' print("key ", Sec_WebSocket_Key) response_key = base64.b64encode(hashlib.sha1(bytes(Sec_WebSocket_Key, encoding="utf8")).digest()) response_key_str = str(response_key) response_key_str = response_key_str[2:30] # print(response_key_str) response_key_entity = "Sec-WebSocket-Accept: " + response_key_str +"\r\n" clientSocket.send(bytes("HTTP/1.1 101 Web Socket Protocol Handshake\r\n", encoding="utf8")) clientSocket.send(bytes("Upgrade: websocket\r\n", encoding="utf8")) clientSocket.send(bytes(response_key_entity, encoding="utf8")) clientSocket.send(bytes("Connection: Upgrade\r\n\r\n", encoding="utf8")) print("send the hand shake data")
强调多次调用clientSocket.send():因为socket.send()认为”\r\n”即为结束标记,所以对于websocket报文中要求的换行”\r\n”,我们要多次调用clientSocket.send()方法将报文一行一行的发送出去,这也是与python2.x中构建websocket server中很重要的一点,笔者在此处踩坑
下文代码是笔者根据浏览器发送的handshake请求获得Sec_WebSocket_Key的方法,可能在不同的环境中会有差异,调试是可全部打印出websocket请求报文,即*取消注释 “print(receivedData)”*
Sec_WebSocket_Key = entities[11].split(“:”)[1].strip()
如何验证自己生成的Sec_WebSocket_Accept是正确的。上文提到构建websocket client。可打开“开发者工具”->“网络network”,然后点击”createWebsocket”按钮,得到浏览器发送的报文与回复报文,可以找到一对正确的(Sec_WebSocket_Key, Sec_WebSocket_Accpet)。使用自己的Sec_WebSocket_Accept生成代码将Sec_WebSocket_Key加密,得到结果与正确Sec_WebSocket_Accept相比较,即可确认自己的Sec_WebSocket_Accept生成是否错误
笔者为了简单,就做了回显,即将收到的报文内容自动返回给client
如上文所述,webscoket client 报文由四部分组成
固定head, 报文长度L, 掩码M, 报文内容C
解析步骤:
根据报文的第二个字节L确定报文长度所占的字节(1字节=8bit)数B
L < 126, B = 1
L == 126, B = 2
L == 127, B = 4
掩码M长度为四字节,紧跟在字节长度之后,使用python字符串分片即可获得
对报文内容C和掩码M进行按字节循环与操作(见上文)
#解析报文部分 def parse_data(self, data): v = data[1] & 0x7f if v == 0x7e: p = 4 elif v == 0x7f: p = 10 else: p = 2 mask = data[p: p+4] data = data[p+4:] print(data) i = 0 raw_str = "" for d in data: raw_str += chr(d ^ mask[i%4]) i += 1 return raw_str
如上文所述,webscoket server 报文由三部分组成
固定head, 报文长度L, 报文内容C
报文长度小于126时,L占一个字节,L = hex(报文长度)
报文长度小于2^16-1时,L占两个字节,L = hex(126,报文长度)
报文长度小于2^64-1时,L占九个字节,L = hex(126,报文长度)
#发送websocket server报文部分 def sendMessage(self, message): msgLen = len(message) backMsgList = [] backMsgList.append(struct.pack('B', 129)) if msgLen <= 125: backMsgList.append(struct.pack('b', msgLen)) elif msgLen <=65535: backMsgList.append(struct.pack('b', 126)) backMsgList.append(struct.pack('>h', msgLen)) elif msgLen <= (2^64-1): backMsgList.append(struct.pack('b', 127)) backMsgList.append(struct.pack('>h', msgLen)) else : print("the message is too long to send in a time") return message_byte = bytes() print(type(backMsgList[0])) for c in backMsgList: # if type(c) != bytes: # print(bytes(c, encoding="utf8")) message_byte += c message_byte += bytes(message, encoding="utf8") #print("message_str : ", str(message_byte)) # print("message_byte : ", bytes(message_str, encoding="utf8")) # print(message_str[0], message_str[4:]) # self.connection.send(bytes("0x810x010x63", encoding="utf8")) self.connection.send(message_byte)
python websocket server + javascript websocket client
窘境,思路已经十分的清楚,可是代码上的欠缺导致功能不能实现,还望请读者耐下心来,一点一点的做,笔者也是花了好长时间才做出来的。实在做不出来,可以放下,过几天接着做,相信自己,总会做出来的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。