这里,介绍如何使用 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 中的处理代码
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
socket
import
threading
import
sys
import
os
import
MySQLdb
import
base64
import
hashlib
import
struct
# ====== config ======
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"
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客户 端
?
1
2
3
4
5
6
7
8
<script>
var
socket =
new
WebSocket(
'ws://localhost:3368'
);
ws.onmessage =
function
(result,nTime){
alert(
"从服务端收到的数据:"
);
alert(
"最近一次发送数据到现在接收一共使用时间:"
+ nTime);
console.log(result);
}
</script>