Mrli
别装作很努力,
因为结局不会陪你演戏。
Contacts:
QQ博客园

Python网络编程Websocket

2019/11/28 Python 网络知识
Word count: 5,949 | Reading time: 31min

网络知识——Websocket

  • TCP :面向连接—> 打电话(相互回复,一来一回),客户端向服务器端 拨号 , 三次握手 ,

  • UDP : 面向无连接 --> 寄快递(寄出去就不管了) e.g.直播

UDP四层结构

  • 网络访问层(链路层): 物理连接设备(网线)、MAC地址(物理地址)
  • 互联网层: IP地址(定位设备)
  • 传输层: port (端口号) : 表示通信进程,将数据交给哪个应用处理
  • 应用层: 自己定义的协议(处理字符串消息的方法)

套接字(socket) : 特殊的设备文件 , 写网络应用程序的接口,写入后就是发送,接收就是读取。 类似于 esp8266吧…

基本的socket操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
'''
参数说明:
AF_INET IPv4
----
STREAM TCP
SOCK_DGRAM UDP,无listen、accpet
'''

server_addr = ('127.0.0.1',8888) # 本地回环地址 , 端口
s.bind(server_addr) # 绑定、传入信息
data, cilent_addr = s.recvfrom(1024) # BUF_SIZE指定接收数据长度 , (数据内容,客户端地址)
s.sendto(data,cilent_addr) # 给客户端发送数据
1
2
3
4
5
6
7
8
9
10
import socket


buf = 1024
ADDR = ("127.0.0.1", 8999)
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcpSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcpSock.bind(ADDR)
tcpSock.listen(5)
conn, addr = tcpSock.accept()

什么是webscoket?

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。HTML5 WebSocket 设计出来的目的就是要取代轮询和 Comet 技术,使客户端浏览器具备像 C/S 架构下桌面系统的实时通讯能力。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输,其本质是保持TCP连接。

  • 客户端 : 发送数据、接收返回数据端
  • 服务端: 处理数据端

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import socket,base64,hashlib

def get_headers(data):
'''提取请求头,将请求头转换为字典'''
header_dict = {}
data = str(data,encoding="utf-8")

header,body = data.split("\r\n\r\n",1)
header_list = header.split("\r\n")
for i in range(0,len(header_list)):
if i == 0:
if len(header_list[0].split(" ")) == 3:
header_dict['method'],header_dict['url'],header_dict['protocol'] = header_list[0].split(" ")
else:
k,v=header_list[i].split(":",1)
header_dict[k]=v.strip()
return header_dict

sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 指定IP和侦听端口Port
sock.bind(("127.0.0.1",8888))
sock.listen(5)

#等待用户连接
conn,addr = sock.accept()
print("conn from ",conn,addr)

#获取握手消息,magic string ,sha1加密
#发送给客户端
#握手消息
data = conn.recv(8096)
headers = get_headers(data)

# 对请求头中的sec-websocket-key进行加密,需要返回的头
response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
"Upgrade:websocket\r\n" \
"Connection: Upgrade\r\n" \
"Sec-WebSocket-Accept: %s\r\n" \
"WebSocket-Location: ws://%s%s\r\n\r\n"

# magic_string为一个固定的字符串
magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'

# 加密
value = headers['Sec-WebSocket-Key'] + magic_string
ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())

# 返回的信息
response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url'])

# 响应【握手】信息
conn.send(bytes(response_str, encoding='utf-8'))
#复制代码

客户端

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
try:
import thread
except ImportError:
import _thread as thread
import time
# import websocket
import websocket


# ws = websocket.WebSocket()
ws = websocket.create_connection("ws://127.0.0.1:8999/")
# ws.connect("ws://127.0.0.1:8999/")
data = {"body":{"address":"陕西省"}}
ws.send(json.dumps(data)) #json转化为字符串,必须转化
while True:
data = ws.recv()
print(data)

Java Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>

</body>

<script>
ws =new WebSocket("ws://127.0.0.1:8888");
ws.onopen = function (ev) {
//若是连接成功,onopen函数会执行
console.log(ev)
}
ws.onmessage = function (ev) {
//若是连接成功,onopen函数会执行
console.log(ev)
}

</script>
</html>

大端和小端模式

网络通信时 通常使用大端

解析websocket协议

使用、创建demo:

长连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import websocket
try:
import thread
except ImportError:
import _thread as thread
import time

def on_message(ws, message):
print(message)

def on_error(ws, error):
print(error)

def on_close(ws):
print("### closed ###")

def on_open(ws):
def run(*args):
ws.send("hello1")
time.sleep(1)
ws.close()
thread.start_new_thread(run,())

if __name__ == "__main__":
websocket.enableTrace(True)
ws = websocket.WebSocketApp("ws://echo.websocket.org/",
on_message = on_message,
on_error = on_error,
on_close = on_close)
ws.on_open = on_open
ws.run_forever(ping_interval=60,ping_timeout=5)

▲ Python的websocket代码是仿js websocket写法的,重新写了一遍脚本,流畅接受消息,自动重连发送指令,连接时间明显减少,基本做到无遗漏数据,与网站js的ws连接实现一样。

短链接:

1
2
3
4
5
6
7
8
9
10
from websocket import create_connection
ws = create_connection("ws://echo.websocket.org/")
print("Sending 'Hello, World'...")
ws.send(json.dumps({"op":"unconfirmed_sub"})) # 不能使用str(),要以json格式输出
ws.send("Hello, World")
print("Sent")
print("Receiving...")
result = ws.recv()
print("Received '%s'" % result)
ws.close()

一步一步分析请求过程!!

  • 服务端(socket服务端)
    *1.服务端开启socket服务监听IP和端口
    *3.允许连接
    *5.服务端接收到特殊值【加密sha1,特殊值,migic string=“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”】
    *6.加密后的值发送给客户端

  • 客户端(浏览器)

*2.客户端发起连接请求(IP和端口)
*4.客户端生成一个xxx,【加密sha1,特殊值,migic string=“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”】,向服务端发送一段特殊值
*7.客户端接收到加密的值

1. 启动服务端
1
2
3
4
5
6
7
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8002))
sock.listen(5)
# 等待用户连接
conn, address = sock.accept()
2. 客户端连接
1
2
3
4
<script type="text/javascript">
var socket = new WebSocket("ws://127.0.0.1:8002/xxoo");
...
</script>
3. 建立连接【握手】
获取请求信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8002))
sock.listen(5)
# 获取客户端socket对象
conn, address = sock.accept()

# 获取客户端的【握手】信息
data = conn.recv(1024)

b'''
GET /chatsocket HTTP/1.1
Host: 127.0.0.1:8002
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://localhost:63342
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: mnwFxiOlctXFN/DeMt1Amg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
'''

请求和响应的【握手】信息需要遵循规则:

  • 从请求【握手】信息中提取 Sec-WebSocket-Key
  • 利用magic_string 和 Sec-WebSocket-Key 进行hmac1加密,再进行base64加密
  • 将加密结果响应给客户端

注:magic string为:258EAFA5-E914-47DA-95CA-C5AB0DC85B11

提取Sec-WebSocket-Key值并加密:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import socket
import base64
import hashlib

def get_headers(data):
"""
将请求头格式化成字典
:param data:
:return:
"""
header_dict = {}
data = str(data, encoding='utf-8')

for i in data.split('\r\n'):
print(i)
header, body = data.split('\r\n\r\n', 1)
header_list = header.split('\r\n')
for i in range(0, len(header_list)):
if i == 0:
if len(header_list[i].split(' ')) == 3:
header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
else:
k, v = header_list[i].split(':', 1)
header_dict[k] = v.strip()
return header_dict


sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8002))
sock.listen(5)

conn, address = sock.accept()
data = conn.recv(1024)
headers = get_headers(data) # 提取请求头信息
# 对请求头中的sec-websocket-key进行加密
response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
"Upgrade:websocket\r\n" \
"Connection: Upgrade\r\n" \
"Sec-WebSocket-Accept: %s\r\n" \
"WebSocket-Location: ws://%s%s\r\n\r\n"
magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
value = headers['Sec-WebSocket-Key'] + magic_string
ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url'])
# 响应【握手】信息
conn.send(bytes(response_str, encoding='utf-8'))

只有在服务端又发回"响应"的握手信息后,才算建立了链接。握手的作用是保证通信双方使用的协议相同

4.客户端和服务端收发数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
数据报内容,第一行为字节,第二行为相应的每一位。
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 ... |
+---------------------------------------------------------------+

官网给出的解析规则

The MASK bit simply tells whether the message is encoded. Messages from the client must be masked, so your server should expect this to be 1. (In fact, section 5.1 of the spec says that your server must disconnect from a client if that client sends an unmasked message.) When sending a frame back to the client, do not mask it and do not set the mask bit. We’ll explain masking later. *Note: You have to mask messages even when using a secure socket.*RSV1-3 can be ignored, they are for extensions.

The opcode field defines how to interpret the payload data: 0x0 for continuation, 0x1 for text (which is always encoded in UTF-8), 0x2 for binary, and other so-called “control codes” that will be discussed later. In this version of WebSockets, 0x3 to 0x7 and 0xB to 0xF have no meaning.

The FIN bit tells whether this is the last message in a series. If it’s 0, then the server will keep listening for more parts of the message; otherwise, the server should consider the message delivered. More on this later.

Decoding Payload Length

To read the payload data, you must know when to stop reading. That’s why the payload length is important to know. Unfortunately, this is somewhat complicated. To read it, follow these steps:

  1. Read bits 9-15 (inclusive) and interpret that as an unsigned integer. If it’s 125 or less, then that’s the length; you’re done. If it’s 126, go to step 2. If it’s 127, go to step 3.
  2. Read the next 16 bits and interpret those as an unsigned integer. You’re done.
  3. Read the next 64 bits and interpret those as an unsigned integer (The most significant bit MUST be 0). You’re done.

Reading and Unmasking the Data

If the MASK bit was set (and it should be, for client-to-server messages), read the next 4 octets (32 bits); this is the masking key. Once the payload length and masking key is decoded, you can go ahead and read that number of bytes from the socket. Let’s call the data ENCODED, and the key MASK. To get DECODED, loop through the octets (bytes a.k.a. characters for text data) of ENCODED and XOR the octet with the (i modulo 4)th octet of MASK. In pseudo-code (that happens to be valid JavaScript):

var DECODED = “”;
for (var i = 0; i < ENCODED.length; i++) {
DECODED[i] = ENCODED[i] ^ MASK[i % 4];
}

Now you can figure out what DECODED means depending on your application.

获取客户端发送的数据【解包】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def analyze_info(info):
# 处理数据头
# mask占4个字节,在确定数据头的首部需要占多少字节后,再向后推4个字节
# mask之后的就是数据的真正内容,但需要根据规则进行解析
payload_len = info[1] & 127
if payload_len == 126:
# payload_len==126首部信息还要拓展2个字节(16 bits)
# 见上文的 Decoding Payload Length-2
extend_payload_len = info[2:4]
mask = info[4:8]
decoded = info[8:]
elif payload_len == 127:
# payload_len==127那么需要再拓展8个字节(64 bits)
# 见上文的 Decoding Payload Length-3
extend_payload_len = info[2:10]
mask = info[10:14]
decoded = info[14:]
else:
extend_payload_len = None
mask = info[2:6]
decoded = info[6:]
'''
var DECODED = "";
for (var i = 0; i < ENCODED.length; i++) {
DECODED[i] = ENCODED[i] ^ MASK[i % 4];
}
'''
bytes_list = bytearray()
for i in range(len(decoded)):
chunk = decoded[i] ^ mask[i % 4]
bytes_list.append(chunk)
content = str(bytes_list, encoding='utf-8')
return content
向客户端发送数据【封包】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def send_msg(conn, msg_bytes):
"""
WebSocket服务端向客户端发送消息
:param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept()
:param msg_bytes: 向客户端发送的字节
:return:
"""
import struct

token = b"\x81"
length = len(msg_bytes)
if length < 126:
token += struct.pack("B", length)
elif length <= 0xFFFF:
token += struct.pack("!BH", 126, length)
else:
token += struct.pack("!BQ", 127, length)

msg = token + msg_bytes
conn.send(msg)
return True

转自武沛齐你真的了解WebSocket吗?

完整DEMO:

Python服务端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import base64
import hashlib


def get_headers(data):
"""
将请求头格式化成字典
:param data:
:return:
"""
header_dict = {}
data = str(data, encoding='utf-8')

header, body = data.split('\r\n\r\n', 1)
header_list = header.split('\r\n')
for i in range(0, len(header_list)):
if i == 0:
if len(header_list[i].split(' ')) == 3:
header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
else:
k, v = header_list[i].split(':', 1)
header_dict[k] = v.strip()
return header_dict


def send_msg(conn, msg_bytes):
"""
WebSocket服务端向客户端发送消息
:param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept()
:param msg_bytes: 向客户端发送的字节
:return:
"""
import struct

token = b"\x81"
length = len(msg_bytes)
if length < 126:
token += struct.pack("B", length)
elif length <= 0xFFFF:
token += struct.pack("!BH", 126, length)
else:
token += struct.pack("!BQ", 127, length)

msg = token + msg_bytes
conn.send(msg)
return True


def init_socket():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8003))
sock.listen(5)

conn, address = sock.accept()
data = conn.recv(1024)
headers = get_headers(data)
response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
"Upgrade:websocket\r\n" \
"Connection:Upgrade\r\n" \
"Sec-WebSocket-Accept:%s\r\n" \
"WebSocket-Location:ws://%s%s\r\n\r\n"

value = headers['Sec-WebSocket-Key'] + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url'])
conn.send(bytes(response_str, encoding='utf-8'))
return conn, sock


def analyze_info(info):
payload_len = info[1] & 127
if payload_len == 126:
extend_payload_len = info[2:4]
mask = info[4:8]
decoded = info[8:]
elif payload_len == 127:
extend_payload_len = info[2:10]
mask = info[10:14]
decoded = info[14:]
else:
extend_payload_len = None
mask = info[2:6]
decoded = info[6:]

bytes_list = bytearray()
for i in range(len(decoded)):
chunk = decoded[i] ^ mask[i % 4]
bytes_list.append(chunk)
content = str(bytes_list, encoding='utf-8')
return content


def run():
conn, sock = init_socket()
while True:
try:
info = conn.recv(8096)
except Exception as e:
info = None
if not info:
break
content = analyze_info(info)
send_msg(conn, content.encode('utf-8'))
sock.close()


if __name__ == '__main__':
run()
html的客户端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div>
<input type="text" id="txt"/>
<input type="button" id="btn" value="提交" onclick="sendMsg();"/>
<input type="button" id="close" value="关闭连接" onclick="closeConn();"/>
</div>
<div id="content"></div>

<script type="text/javascript">
var socket = new WebSocket("ws://127.0.0.1:8003/chatsocket");

socket.onopen = function () {
/* 与服务器端连接成功后,自动执行 */

var newTag = document.createElement('div');
newTag.innerHTML = "【连接成功】";
document.getElementById('content').appendChild(newTag);
};

socket.onmessage = function (event) {
/* 服务器端向客户端发送数据时,自动执行 */
var response = event.data;
var newTag = document.createElement('div');
newTag.innerHTML = response;
document.getElementById('content').appendChild(newTag);
};

socket.onclose = function (event) {
/* 服务器端主动断开连接时,自动执行 */
var newTag = document.createElement('div');
newTag.innerHTML = "【关闭连接】";
document.getElementById('content').appendChild(newTag);
};

function sendMsg() {
var txt = document.getElementById('txt');
socket.send(txt.value);
txt.value = "";
}
function closeConn() {
socket.close();
var newTag = document.createElement('div');
newTag.innerHTML = "【关闭连接】";
document.getElementById('content').appendChild(newTag);
}

</script>
</body>
</html>

tornado初探

1
2
3
4
5
6
ws = websocket.WebSocketApp("ws://echo.websocket.org/",
on_message = on_message,
on_error = on_error,
on_close = on_close)
ws.on_open = on_open
ws.run_forever()

长连接,参数介绍:

(1)url: websocket的地址。

(2)header: 客户发送websocket握手请求的请求头,{‘head1:value1’,‘head2:value2’}。

(3)on_open:在建立Websocket握手时调用的可调用对象,这个方法只有一个参数,就是该类本身。

(4)on_message:这个对象在接收到服务器返回的消息时调用。有两个参数,一个是该类本身,一个是我们从服务器获取的字符串(utf-8格式)。

(5)on_error:这个对象在遇到错误时调用,有两个参数,第一个是该类本身,第二个是异常对象。

(6)on_close:在遇到连接关闭的情况时调用,参数只有一个,就是该类本身。

(7)on_cont_message:这个对象在接收到连续帧数据时被调用,有三个参数,分别是:类本身,从服务器接受的字符串(utf-8),连续标志。

(8)on_data:当从服务器接收到消息时被调用,有四个参数,分别是:该类本身,接收到的字符串(utf-8),数据类型,连续标志。

(9)keep_running:一个二进制的标志位,如果为True,这个app的主循环将持续运行,默认值为True。

(10)get_mask_key:用于产生一个掩码。

(11)subprotocols:一组可用的子协议,默认为空。

tornado.hello_world

  • Http
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import tornado.ioloop
import tornado.web

class IndexHandler(RequestHandler):
# 正常1,抛错1、5
def set_default_headers(self):
print "调用了set_default_headers()"
# 正常2,抛错2
def initialize(self):
print "调用了initialize()"
# 正常3,抛错3
def prepare(self):
print "调用了prepare()"
# 正常4,抛错4
def get(self):
print "调用了get()"
# 正常4,抛错4
def post(self):
print "调用了post()"
self.send_error(200) # 注意此出抛出了错误

# 正常无、抛错6
def write_error(self, status_code, **kwargs):
print "调用了write_error()"

# 正常5、抛错7
def on_finish(self):
print "调用了on_finish()"

# 定义Http处理类型
class MainHandler(tornado.web.RequestHandler):
# 处理路由参数
def initialize(self, subject):
self.subject = subject

# 添加一个处理get请求方式的方法
def get(self):
# write方法是写到缓冲区的
self.write("Hello, world")
# write会自动检测json类型,进行包装,并设Content-Type设置为application/json; charset=UTF-8。


def make_app():
settings = {
'template_path': 'templates',
'static_path': 'static',
}
return tornado.web.Application([
(r"/main", MainHandler, {"subject":"c++"}), # 指定路由信息,路由参数会传入initialize()中
(r"/", IndexHandler),
], debug=True, **settings)

# debug=True时,有自动重启、提供追踪信息等功能


if __name__ == "__main__":
app = make_app() # 创建一个应用对象,得返回Application对象
app.listen(8888) # 设置端口
tornado.ioloop.IOLoop.current().start() # 启动web程序,开始监听端口的连接
  • Websocket
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# ▲继承的类为WebSocketHandler
class ChatHandler(tornado.websocket.WebSocketHandler):
# 用户存储当前聊天室用户
waiters = set()
# 用于存储历时消息
messages = []

def open(self):
"""
客户端连接成功时,自动执行
:return:
"""
ChatHandler.waiters.add(self)
uid = str(uuid.uuid4())
self.write_message(uid)

for msg in ChatHandler.messages:
content = self.render_string('message.html', **msg)
self.write_message(content)

def on_message(self, message):
"""
客户端连发送消息时,自动执行
:param message:
:return:
"""
msg = json.loads(message)
ChatHandler.messages.append(message)

for client in ChatHandler.waiters:
content = client.render_string('message.html', **msg)
client.write_message(content)

def on_close(self):
"""
客户端关闭连接时,,自动执行
:return:
"""
ChatHandler.waiters.remove(self)



def run():
settings = {
'template_path': 'templates',
'static_path': 'static',
}
application = tornado.web.Application([
(r"/", IndexHandler),
(r"/chat", ChatHandler),
], **settings)
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()


if __name__ == "__main__":
run()

基于Tornado——聊天室

项目结构

1
2
3
4
5
6
7
8
│   app.py

├───static
│ jquery-2.1.4.min.js

└───templates
index.html
message.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# app.py
import uuid
import json
import tornado.ioloop
import tornado.web
import tornado.websocket


class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html')


class ChatHandler(tornado.websocket.WebSocketHandler):
# 用户存储当前聊天室用户
waiters = set()
# 用于存储历时消息
messages = []

def open(self):
"""
客户端连接成功时,自动执行
:return:
"""
ChatHandler.waiters.add(self)
uid = str(uuid.uuid4())
self.write_message(uid)

for msg in ChatHandler.messages:
content = self.render_string('message.html', **msg)
self.write_message(content)

def on_message(self, message):
"""
客户端连发送消息时,自动执行
:param message:
:return:
"""

msg = json.loads(message)
ChatHandler.messages.append(message)

# 向当前在线的每个用户发送最新的消息界面
for client in ChatHandler.waiters:
content = client.render_string('message.html', **msg)
client.write_message(content)

def on_close(self):
"""
客户端关闭连接时,,自动执行
:return:
"""
ChatHandler.waiters.remove(self)


def run():
settings = {
'template_path': 'templates',
'static_path': 'static',
}
application = tornado.web.Application([
(r"/", IndexHandler),
(r"/chat", ChatHandler),
], **settings)
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()


if __name__ == "__main__":
run()

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Python聊天室</title>
</head>
<body>
<div>
<input type="text" id="txt"/>
<input type="button" id="btn" value="提交" onclick="sendMsg();"/>
<input type="button" id="close" value="关闭连接" onclick="closeConn();"/>
</div>
<div id="container" style="border: 1px solid #dddddd;margin: 20px;min-height: 500px;">

</div>

<script src="/static/jquery-2.1.4.min.js"></script>
<script type="text/javascript">
$(function () {
wsUpdater.start();
});

var wsUpdater = {
socket: null,
uid: null,
start: function() {
var url = "ws://127.0.0.1:8888/chat";
wsUpdater.socket = new WebSocket(url);
wsUpdater.socket.onmessage = function(event) {
console.log(event);
if(wsUpdater.uid){
wsUpdater.showMessage(event.data);
}else{
wsUpdater.uid = event.data;
}
}
},
showMessage: function(content) {
$('#container').append(content);
}
};
// 按下提交按钮后,客户端向服务端发送信息,服务端再将数据给message.html渲染
function sendMsg() {
var msg = {
uid: wsUpdater.uid,
message: $("#txt").val()
};
console.log(JSON.stringify(msg));
wsUpdater.socket.send(JSON.stringify(msg));
}

</script>

</body>
</html>

message.html

1
2
3
4
<div style="border: 1px solid #dddddd;margin: 10px;">
<div>游客{{uid}}</div>
<div style="margin-left: 20px;">{{message}}</div>
</div>

▲ 整个执行流程:

  1. 按下提交后调用html.sendMsg–>
  2. 数据message–>在后自动调用on_message,会将message传给massage.html渲染,将渲染结果(字符串)返回给用户(write_message)–>
  3. 客户端收到消息后自动执行html.on_message会调用html.showMessage,把content即字符串(event.data)用jquery追加显示index.html页面上$('#container').append(content);

附录文献:

websocket获取实时数据的几种常见链接方式

python使用websocket的几种方式

Python Web 框架:Tornado初探

▲Python学习笔记——Tornado深入

完整的websocket使用——聊天室

坑点记录:

1.create_connection导入失败

Q:ImportError: cannot import name 'create_connection' from 'websocket' (unknown location)

  • A:在使用create_connection之前要安装websocket_client ,即 pip install websocket-client

个人的HTML客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>2048客户端</title>
</head>

<body>
<div align="center">
<input type="button" value="↑" name="↑" onclick="up()" style="height:50px; width:50px;">
<input type="button" value="↓" name="↓" onclick="down()" style="height:50px; width:50px;">
<input type="button" value="←" name="←" onclick="left()" style="height:50px; width:50px;">
<input type="button" value="→" name="→" onclick="right()" style="height:50px; width:50px;">
</div>
</body>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script type="text/javascript">
$(function () {
wsUpdater.start();
});

var wsUpdater = {
socket: null,
uid: null,

start: function () {
var url = "ws://127.0.0.1:8888/game";
wsUpdater.socket = new WebSocket(url);
wsUpdater.socket.onmessage = function (event) {

};
wsUpdater.socket.onopen = function (ev) {
//若是连接成功,onopen函数会执行
console.log(ev);
msg = 'success';
wsUpdater.socket.send(JSON.stringify(msg));
};
wsUpdater.socket.onclose = function () {
console.log("Client断开连接!");
};
}
};


function up() {
wsUpdater.socket.send(0);
}

function down() {
wsUpdater.socket.send(1);
}

function left() {
wsUpdater.socket.send(2);
}
function right() {
wsUpdater.socket.send(3);
}

</script>
</html>

js客户端

先用npm install ws,再输入node doit.js运行代码

doit.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var WebSocket = require('ws');

console.log('hello');
var wsUpdater = {
socket: null,
uid: null,

start: function () {
var url = "ws://127.0.0.1:8888/game";
wsUpdater.socket = new WebSocket(url);
wsUpdater.socket.onmessage = function (ev) {
console.log(ev);
game = JSON.parse(ev.data);
console.log(game);
};
wsUpdater.socket.onopen = function (ev) {
//若是连接成功,onopen函数会执行
console.log(ev);
msg = 'success';
wsUpdater.socket.send(JSON.stringify(msg));
};
wsUpdater.socket.onclose = function () {
console.log("Client断开连接!");
};
}
};

wsUpdater.start();

Python代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import json
from ws4py.client.threadedclient import WebSocketClient
import multiprocessing
from core import Game


class GamePlayer(WebSocketClient):

def opened(self):
# req = '{"event":"subscribe", "channel":"eth_usdt.deep"}'
# self.send(req)
print("连接成功")

def closed(self, code, reason=None):
# print("Closed down:", code, reason)
print("连接中断")

def received_message(self, resp):
resp = json.loads(str(resp))
print(resp)

if __name__ == '__main__':
# in_pipe, out_pipe = multiprocessing.Pipe(True)
ws = None
try:
ws = GamePlayer('ws://127.0.0.1:23456/game')
ws.connect()
ws.run_forever()
except KeyboardInterrupt:
ws.close()

Author: Mrli

Link: https://nymrli.top/2019/11/24/Python网络编程Websocket/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
MinMax和Alpha-beta剪枝分析[转]
NextPost >
第二十八届“和巨耀通杯”南京邮电大学在线测评系统程序设计邀请赛--
CATALOG
  1. 1. 网络知识——Websocket
    1. 1.0.1. 什么是webscoket?
    2. 1.0.2. 解析websocket协议
      1. 1.0.2.1. 一步一步分析请求过程!!
        1. 1.0.2.1.1. 1. 启动服务端
        2. 1.0.2.1.2. 2. 客户端连接
        3. 1.0.2.1.3. 3. 建立连接【握手】
          1. 1.0.2.1.3.1. 获取请求信息
          2. 1.0.2.1.3.2. 提取Sec-WebSocket-Key值并加密:
        4. 1.0.2.1.4. 4.客户端和服务端收发数据
          1. 1.0.2.1.4.1. 获取客户端发送的数据【解包】
          2. 1.0.2.1.4.2. 向客户端发送数据【封包】
      2. 1.0.2.2. 完整DEMO:
        1. 1.0.2.2.1. Python服务端代码
        2. 1.0.2.2.2. html的客户端代码
    3. 1.0.3. tornado初探
      1. 1.0.3.1. tornado.hello_world
      2. 1.0.3.2. 基于Tornado——聊天室
  2. 1.1. 附录文献:
    1. 1.1.1. websocket获取实时数据的几种常见链接方式
    2. 1.1.2. python使用websocket的几种方式
    3. 1.1.3. Python Web 框架:Tornado初探
    4. 1.1.4. ▲Python学习笔记——Tornado深入
    5. 1.1.5. 完整的websocket使用——聊天室
  3. 1.2. 坑点记录:
    1. 1.2.1. 1.create_connection导入失败
    2. 1.2.2. 个人的HTML客户端代码
    3. 1.2.3. js客户端