websocket原理
?WebSocket協(xié)議是基于TCP的一種新的協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工(full-duplex)通信。其本質(zhì)是保持TCP連接,在瀏覽器和服務(wù)端通過Socket進(jìn)行通信。
1、啟動(dòng)客戶端
<script type="text/javascript">var socket = new WebSocket("ws://127.0.0.1:8002/xxoo");... </script>2、啟動(dòng)服務(wù)端并建立連接
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)的握手過程:
- 從請(qǐng)求握手的信息中提取Sec-WebSocket-Key
- 利用sha1和base64對(duì)magic_string(258EAFA5-E914-47DA-95CA-C5AB0DC85B11)和Sec-WebSocket-Key進(jìn)行加密
- 將加密結(jié)果響應(yīng)給客戶端
代碼如下:
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')) View Code3、客戶端和服務(wù)端收發(fā)數(shù)據(jù)
客戶端和服務(wù)端傳輸數(shù)據(jù)時(shí),需要對(duì)數(shù)據(jù)進(jìn)行【封包】和【解包】??蛻舳说腏avaScript類庫(kù)封裝了【封包】和【解包】過程,但Socket服務(wù)端需要手動(dòng)實(shí)現(xiàn):
第一步:獲取客戶端發(fā)送的數(shù)據(jù)【解包】
【解包】的詳細(xì)過程:
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 LengthTo 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 DataIf 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. 0 1 2 30 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 +-+-+-+-+-------+-+-------------+-------------------------------+ |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 | +-------------------------------- - - - - - - - - - - - - - - - +代碼如下:
info = conn.recv(8096)# 用數(shù)據(jù)包的第二個(gè)字節(jié),與127作與位運(yùn)算,拿到前七位 payload_len = info[1] & 127 # 這七位在數(shù)據(jù)頭部分成為payload,如果payload等于126,就要再擴(kuò)展2個(gè)字節(jié) if payload_len == 126:extend_payload_len = info[2:4]mask = info[4:8]decoded = info[8:] # 如果等于127,就要再擴(kuò)展8個(gè)字節(jié) elif payload_len == 127:extend_payload_len = info[2:10]mask = info[10:14]decoded = info[14:] # 如果小于等于125,那它就占這一個(gè)字節(jié) else:extend_payload_len = Nonemask = info[2:6]decoded = info[6:]# 當(dāng)payload確定之后,再往后數(shù)4個(gè)字節(jié),這4個(gè)字節(jié)成為masking key,再之后的內(nèi)容就是接收到的數(shù)據(jù)部分了 # 數(shù)據(jù)部分的每一字節(jié)都要和masking key作異或位運(yùn)算,得出來的結(jié)果就是真實(shí)的數(shù)據(jù)內(nèi)容 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)第二步:向客戶端發(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第三步:客戶端代碼
<!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="關(guān)閉連接" 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 () {/* 與服務(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>以上便利用Python完成了基于websocket的通信。
它的原理就是利用對(duì)magic_string和Sec-WebSocket-Key進(jìn)行加密來建立長(zhǎng)連接,在收發(fā)數(shù)據(jù)時(shí),利用payload和masking key來獲取數(shù)據(jù)部分。
?
轉(zhuǎn)載于:https://www.cnblogs.com/value-code/p/8746753.html
總結(jié)
以上是生活随笔為你收集整理的websocket原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CF 961E Tufurama
- 下一篇: iOS动画-从UIView到Core A