WebSocket介绍
WebSocket協(xié)議是基于TCP的一種新的協(xié)議。WebSocket最初在HTML5規(guī)范中被引用為TCP連接,作為基于TCP的套接字API的占位符。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工(full-duplex)通信。其本質(zhì)是保持TCP連接,在瀏覽器和服務(wù)端通過(guò)Socket進(jìn)行通信。
?本文將使用Python編寫Socket服務(wù)端,一步一步分析請(qǐng)求過(guò)程!!!
1. 啟動(dòng)服務(wù)端
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() ... ... ...啟動(dòng)Socket服務(wù)器后,等待用戶【連接】,然后進(jìn)行收發(fā)數(shù)據(jù)。
2. 客戶端連接
<script type="text/javascript">var socket = new WebSocket("ws://127.0.0.1:8002/xxoo");... </script>當(dāng)客戶端向服務(wù)端發(fā)送連接請(qǐng)求時(shí),不僅連接還會(huì)發(fā)送【握手】信息,并等待服務(wù)端響應(yīng),至此連接才創(chuàng)建成功!
3. 建立連接【握手】
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) # 獲取客戶端socket對(duì)象 conn, address = sock.accept() # 獲取客戶端的【握手】信息 data = conn.recv(1024) ... ... ... conn.send('響應(yīng)【握手】信息')請(qǐng)求和響應(yīng)的【握手】信息需要遵循規(guī)則:
- 從請(qǐng)求【握手】信息中提取?Sec-WebSocket-Key
- 利用magic_string 和 Sec-WebSocket-Key 進(jìn)行hmac1加密,再進(jìn)行base64加密
- 將加密結(jié)果響應(yīng)給客戶端
注:magic string為:258EAFA5-E914-47DA-95CA-C5AB0DC85B11
請(qǐng)求【握手】信息為:
提取Sec-WebSocket-Key值并加密:
import socket import base64 import hashlibdef get_headers(data):"""將請(qǐng)求頭格式化成字典: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_dictsock = 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) # 提取請(qǐng)求頭信息 # 對(duì)請(qǐng)求頭中的sec-websocket-key進(jìn)行加密 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']) # 響應(yīng)【握手】信息 conn.send(bytes(response_str, encoding='utf-8')) ... ... ...4.客戶端和服務(wù)端收發(fā)數(shù)據(jù)
客戶端和服務(wù)端傳輸數(shù)據(jù)時(shí),需要對(duì)數(shù)據(jù)進(jìn)行【封包】和【解包】。客戶端的JavaScript類庫(kù)已經(jīng)封裝【封包】和【解包】過(guò)程,但Socket服務(wù)端需要手動(dòng)實(shí)現(xiàn)。
第一步:獲取客戶端發(fā)送的數(shù)據(jù)【解包】
info = conn.recv(8096)payload_len = info[1] & 127if 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 = Nonemask = 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)body = str(bytes_list, encoding='utf-8')print(body) 基于Python實(shí)現(xiàn)解包過(guò)程(未實(shí)現(xiàn)長(zhǎng)內(nèi)容)解包詳細(xì)過(guò)程:?
0 1 2 30 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:
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.
?第二步:向客戶端發(fā)送數(shù)據(jù)【封包】
def send_msg(conn, msg_bytes):"""WebSocket服務(wù)端向客戶端發(fā)送消息:param conn: 客戶端連接到服務(wù)器端的socket對(duì)象,即: conn,address = socket.accept():param msg_bytes: 向客戶端發(fā)送的字節(jié):return: """import structtoken = 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_bytesconn.send(msg)return True View Code5. 基于Python實(shí)現(xiàn)簡(jiǎn)單示例
a. 基于Python socket實(shí)現(xiàn)的WebSocket服務(wù)端:
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket import base64 import hashlibdef get_headers(data):"""將請(qǐng)求頭格式化成字典: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_dictdef send_msg(conn, msg_bytes):"""WebSocket服務(wù)端向客戶端發(fā)送消息:param conn: 客戶端連接到服務(wù)器端的socket對(duì)象,即: conn,address = socket.accept():param msg_bytes: 向客戶端發(fā)送的字節(jié):return: """import structtoken = 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_bytesconn.send(msg)return Truedef run():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'))while True:try:info = conn.recv(8096)except Exception as e:info = Noneif not info:breakpayload_len = info[1] & 127if 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 = Nonemask = 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)body = str(bytes_list, encoding='utf-8')send_msg(conn,body.encode('utf-8'))sock.close()if __name__ == '__main__':run()b. 利用JavaScript類庫(kù)實(shí)現(xiàn)客戶端
<!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="提交" οnclick="sendMsg();"/><input type="button" id="close" value="關(guān)閉連接" οnclick="closeConn();"/></div><div id="content"></div><script type="text/javascript">var socket = new WebSocket("ws://127.0.0.1:8003/chatsocket");socket.onopen = function () {/* 與服務(wù)器端連接成功后,自動(dòng)執(zhí)行 */var newTag = document.createElement('div');newTag.innerHTML = "【連接成功】";document.getElementById('content').appendChild(newTag);};socket.onmessage = function (event) {/* 服務(wù)器端向客戶端發(fā)送數(shù)據(jù)時(shí),自動(dòng)執(zhí)行 */var response = event.data;var newTag = document.createElement('div');newTag.innerHTML = response;document.getElementById('content').appendChild(newTag);};socket.onclose = function (event) {/* 服務(wù)器端主動(dòng)斷開連接時(shí),自動(dòng)執(zhí)行 */var newTag = document.createElement('div');newTag.innerHTML = "【關(guān)閉連接】";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 = "【關(guān)閉連接】";document.getElementById('content').appendChild(newTag);}</script> </body> </html>6. 基于Tornado框架實(shí)現(xiàn)Web聊天室
Tornado是一個(gè)支持WebSocket的優(yōu)秀框架,其內(nèi)部原理正如1~5步驟描述,當(dāng)然Tornado內(nèi)部封裝功能更加完整。
以下是基于Tornado實(shí)現(xiàn)的聊天室示例:
#!/usr/bin/env python # -*- coding:utf-8 -*- import uuid import json import tornado.ioloop import tornado.web import tornado.websocketclass IndexHandler(tornado.web.RequestHandler):def get(self):self.render('index.html')class ChatHandler(tornado.websocket.WebSocketHandler):# 用戶存儲(chǔ)當(dāng)前聊天室用戶waiters = set()# 用于存儲(chǔ)歷時(shí)消息messages = []def open(self):"""客戶端連接成功時(shí),自動(dòng)執(zhí)行: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):"""客戶端連發(fā)送消息時(shí),自動(dòng)執(zhí)行: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):"""客戶端關(guān)閉連接時(shí),,自動(dòng)執(zhí)行: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() app.py <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Python聊天室</title> </head> <body><lt;div><input type="text" id="txt"/><input type="button" id="btn" value="提交" onclick="sendMsg();"/><input type="button" id="close" value="關(guān)閉連接" 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()};wsUpdater.socket.send(JSON.stringify(msg));}</script></body> </html> index.html?
?
轉(zhuǎn)載于:https://www.cnblogs.com/hedeyong/p/8128129.html
總結(jié)
以上是生活随笔為你收集整理的WebSocket介绍的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matlab自带回归拟合数据,matla
- 下一篇: 基于单片机USB接口的温度控制器