网络知识——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 sockets = 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 ) s.sendto(data,cilent_addr)
1 2 3 4 5 6 7 8 9 10 import socketbuf = 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,hashlibdef 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 ) sock.bind(("127.0.0.1" ,8888 )) sock.listen(5 ) conn,addr = sock.accept() print("conn from " ,conn,addr) data = conn.recv(8096 ) 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" 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 timeimport websocketws = websocket.create_connection("ws://127.0.0.1:8999/" ) data = {"body" :{"address" :"陕西省" }} ws.send(json.dumps(data)) 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) { console .log(ev) } ws.onmessage = function (ev) { 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 websockettry : import thread except ImportError: import _thread as thread import timedef 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_connectionws = create_connection("ws://echo.websocket.org/" ) print("Sending 'Hello, World'..." ) ws.send(json.dumps({"op" :"unconfirmed_sub" })) ws.send("Hello, World" ) print("Sent" ) print("Receiving..." ) result = ws.recv() print("Received '%s'" % result) ws.close()
一步一步分析请求过程!!
*2.客户端发起连接请求(IP和端口)
*4.客户端生成一个xxx,【加密sha1,特殊值,migic string=“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”】,向服务端发送一段特殊值
*7.客户端接收到加密的值
1. 启动服务端
1 2 3 4 5 6 7 import socketsock = 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 ) 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 socketimport base64import 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) 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:
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.
Read the next 16 bits and interpret those as an unsigned integer. You’re done .
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) : 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 :] ''' 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 import socketimport base64import hashlibdef 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
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.ioloopimport tornado.webclass IndexHandler (RequestHandler) : def set_default_headers (self) : print "调用了set_default_headers()" def initialize (self) : print "调用了initialize()" def prepare (self) : print "调用了prepare()" def get (self) : print "调用了get()" def post (self) : print "调用了post()" self.send_error(200 ) def write_error (self, status_code, **kwargs) : print "调用了write_error()" def on_finish (self) : print "调用了on_finish()" class MainHandler (tornado.web.RequestHandler) : def initialize (self, subject) : self.subject = subject def get (self) : self.write("Hello, world" ) def make_app () : settings = { 'template_path' : 'templates' , 'static_path' : 'static' , } return tornado.web.Application([ (r"/main" , MainHandler, {"subject" :"c++" }), (r"/" , IndexHandler), ], debug=True , **settings) if __name__ == "__main__" : app = make_app() app.listen(8888 ) tornado.ioloop.IOLoop.current().start()
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 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()
项目结构
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 import uuidimport jsonimport tornado.ioloopimport tornado.webimport tornado.websocketclass 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); } }; 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 >
▲ 整个执行流程:
按下提交后调用html.sendMsg–>
数据message–>在后自动调用on_message,会将message传给massage.html渲染,将渲染结果(字符串 )返回给用户(write_message)–>
客户端收到消息后自动执行html.on_message会调用html.showMessage,把content即字符串 (event.data)用jquery追加显示index.html页面上$('#container').append(content);
附录文献:
坑点记录:
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) { 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 ) { 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 jsonfrom ws4py.client.threadedclient import WebSocketClientimport multiprocessingfrom core import Gameclass GamePlayer (WebSocketClient) : def opened (self) : print("连接成功" ) def closed (self, code, reason=None) : print("连接中断" ) def received_message (self, resp) : resp = json.loads(str(resp)) print(resp) if __name__ == '__main__' : ws = None try : ws = GamePlayer('ws://127.0.0.1:23456/game' ) ws.connect() ws.run_forever() except KeyboardInterrupt: ws.close()