网上的好多教程都是基于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,即表示目的地址 目的端口
websocket
将上述代码保存问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..)操作的逆操作
详见python3.6.0 struct官方文档
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
窘境,思路已经十分的清楚,可是代码上的欠缺导致功能不能实现,还望请读者耐下心来,一点一点的做,笔者也是花了好长时间才做出来的。实在做不出来,可以放下,过几天接着做,相信自己,总会做出来的。