1、websocket概要:javascript
websocket是基於TCP傳輸層協議實現的一種標準協議(關於網絡協議,能夠看看文末的圖片),用於在客戶端和服務端雙向傳輸數據html
傳統的客戶端想要知道服務端處理進度有兩個途徑:前端
1)經過ajax不斷輪詢,因爲http的無狀態性,每次輪詢服務器都須要去解析http協議,對服務器壓力也很大java
2)採用long poll的方式,服務端不給客戶端反饋,客戶端就一直等待,服務就一直被掛起,此階段一直是阻塞狀態python
而當服務器完成升級(http-->websocket)後,上面兩個問題就獲得解決了:web
1)被動性,升級後,服務端能夠主動推送消息給客戶端,解決了輪詢形成的同步延遲問題ajax
2)升級後,websocket只須要一次http握手,服務端就能一直與客戶端保持通訊,直到關閉鏈接,這樣就解決了服務器須要反覆解析http協議,減小了資源的開銷算法
2、websocket通訊過程:瀏覽器
websocket目前基本主流瀏覽器都已經支持,IE10如下不支持。安全
一、創建鏈接
在客戶端,new WebSocket實例化一個新的WebSocket客戶端對象,鏈接相似 ws://yourdomain:port/path 的服務端 WebSocket URL, WebSocket 客戶端對象會自動解析並識別爲 WebSocket 請求,從而鏈接服務端接口,執行雙方握手過程,客戶端發送數據格式相似:
1)客戶端請求報文:
GET / HTTP/1.1 Upgrade:websocket #line1 Connection:Upgrade #line2 :與http請求報文比,多了line1和line2這兩行,它告訴服務器這次發起的是websocket協議,而不是http協議了,記得要升級哦 Host:example.com Origin:http://example.com Sec-WebSocket-Key:sN9cRrP/n9NdMgdcy2VJFQ== # line3:這個是瀏覽器隨機生成的一個base64加密值,提供基本的防禦,告訴服務器,我有提供的密碼的,我會作驗證的,防止惡意或無心的鏈接 Sec-WebSocket-Version:13 #line4 :告訴服務器使用的websocket版本,若是服務器不支持該版本,會返回一個Sec-WebSocket-Versionheader,裏面包含服務器支持的版本號
客戶端建立websocket鏈接
var ws = new websocket("ws:127.0.0.1:8000")
完整客戶端代碼以下:
<script type="text/javascript"> var ws; var box = document.getElementById("box"); function startWS(){ ws = new websocket("ws:127.0.0.1:8000"); ws.onopen = function(msg){ console.log("websocket opened!"); } ws.onomessage = function(message){ console.log("receive message:"+message.data); box.insertAdjacentHTML("beforeend", "<p>"+message.data+"</p>"); } ws.onerror = function(err){ console.log("error:"+err.name+err.number); } ws.onclose = function(){ console.log("websocket closed!") } } function sendMsg(){ console.log("sending a message..."); var text = document.getElementById("text"); ws.send(text.value); } window.onbeforeunload = function(){ ws.onclose = function(){} ws.close() } </script>
2)服務端響應報文:
HTTP/1.1 101 Switching Protocols # 101表示服務端已經理解了客戶端的請求,並將經過Upgrade消息頭通知客戶端採用不一樣的協議來完成這個請求 Upgrade:websocket Connection:Upgrade # 這裏兩行是告訴瀏覽器,我已經成功切換協議了,協議是websocket Sec-WebSocket-Accept:HSmrc0sM1YUkAGmm50PpG2HaGwK= #通過服務器確認並加密後的Sec-WebSocket-Key Sec-WebSocket-Protocol:chat # 表示最終使用的協議,至此http就已經完成所有的工做,接下來就是徹底按照websocket協議進行了。
上文的Sec-WebSocket-Accept加密算法:
a)將Sec-WebSocket-Key和258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接
b)經過SHA1計算出摘要,並轉成Base64字符串
如token = base64.b64encode(hashlib.sha1(key+magic_str).encode("utf8").degist())
這裏在作加密以前,key必定要記得看先後有沒有空白,有的話要記得去空白,否則加密的結果會一直報錯不匹配,這個坑被坑了好久
注意:此處的Sec-WebSocket-Key/Sec-WebSocket-Accept的換算,只能帶來基本保障,但鏈接是否安全,數據是否安全,客戶端 服務端是不是合法的ws客戶端 ws服務端,並無實際保證
Sec-WebSocket-Protocol:表示最終使用的協議
完整的服務端代碼:
1)建立websocket服務端
import socket import threading global clients clients = {} class Websocket_Server(threading.Thread): def __init__(self, port): self.port = port super(Websocket_Server, self).__init__() def run(self): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(("127.0.0.1", self.port)) sock.listen(5) while(True):
# 等待客戶端鏈接 conn, addr = sock.accept() print("客戶端{}鏈接成功:".format(addr)) conn.send(("welcome...").encode("utf8")) while(True): try: info = conn.recv(1024) connId = "ID:"+str(addr[1]) clients[connId] = conn print("{0}:{1}".format(connId, info.decode("utf8"))) except Exception as e: print(e) msg = input() conn.send(msg.encode("utf8")) if info == b"bye": print("客戶端退出") conn.close() break
上面建立了websocket服務端,經過socket.socket()建立了TCP服務對象
接收兩個參數:family和type
family:有三種:
AF_INET :即IPV4(默認)
AF_INET6:即IPV6
AF_UNIX:只可以用於單一的Unix系統進程間通訊
type:套接字類型:
流套接字(SOCK_STREAM)(默認):只讀取TCP協議的數據,用於提供面向鏈接,可靠的數據傳輸服務。該服務能夠保證數據能夠實現無差錯無重複發送,並按序接收。之因此可以實現可靠的數據傳輸,緣由在於使用了傳輸控制協議(TCP)
數據報套接字(SOCK_DGRAM):只讀取UDP協議的數據。提供了一種無鏈接服務,該服務並不能保證數據的可靠性。有可能在數據傳輸過程當中出現數據丟失,錯亂重複等。因爲數據包套接字不能保證數據的可靠性,對於有可能出現數據丟失的狀況,在程序中要作相應的處理。
原始套接字(SOCK_RAW):原始套接字和標準套接字(上面兩種)的區別是:原始套接字能夠讀取內核沒有處理的IP數據包。而流套接字只能讀取TCP協議的數據;數據包套接字只能讀取UDP協議的數據
可靠UDP形式:(SOCK_RDM),會對數據進行校驗,通常不會使用
可靠的連續數據包:(SOCK_SEQPACKET)通常也不會使用
2)建立websocket客戶端
import socket import threading class Websocket_Client(threading.Thread): def __init__(self): self.host ="localhost" self.port = 8000 self.address = (host, port) self.buffer = 1024 def run(): #建立TCP客戶端程序 tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 鏈接服務端 tcp_client.connect(self.address) while True: info = tcp_client.recv(self.buffer) print("{}".format(str(info, encoding="utf8"))) msg = input() tcp_client.send(msg.encode("utf8")) if info.lower().decode("utf8")=="bye": tcp_client.close() break
完成上述代碼,分別打開兩個終端,運行WebSocket_Server和WebSocket_Client代碼,控制檯交互結果以下,能夠看到已經成功實現了客戶端和服務端的通訊:
3)如何監聽瀏覽發送過來的socket鏈接請求?修改上面的WebSocket_Client以下:
class WebSocket_Client():
def __init__(self, conn, connId):
self.connection = conn
self.connId= connId
super(WebSocket_Client, self).__init__()
def run():
print("new socket joined")
# 前端傳遞過來的請求鏈接數據
data = self.connection.recv(1024)
try:
headers = self.parse_header(data)
token = self.generate_token(headers["Sec-WebSocket-Key"]).decode("utf8")
response ="HTTP/1.1 101 Switching Protocols\r\n" \
"Connection:Upgrade\r\n" \
"Upgrade:websocket\r\n" \
"Sec-WebSocket-Accept:{0}\r\n" \
"WebSocket-Protocol:chat\r\n\r\n".format(token)
# 通知前端已經成功創建鏈接
self.connection.send(response.encode("utf8"))
except socket.error as e:
print("unexpect error:", e)
clients.pop(self.connId)
while True:
# 啓動 Socket 並監聽鏈接,使用socket中的同名方法,建立TCP服務對象 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # socket.AF_INET是指使用哪一種IP地址,socket.AF_INET即便用IPv4,若是是socket.AF_INET6的話,則使用IPv6;socket.SOCKET_STREAM是指使用的套接字類型,在這裏咱們使用流式套接字。 try: sock.bind(("127.0.0.1",8000)) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.listen(5) except Exception as e: logging.error(e) return else: logging.info(Server running...) # 等待訪問 while True: conn, addr = sock.accept() #此時會進入waiting狀態 data = str(conn.recv(1024)) logging.debug(data) header_dict = {} header, _ = data.split(r"\r\n\r\n", 1) for line in header.split(r"\r\n")[1:]: key, val = line.split(":",1) header_dict[key] = val if "Sec-WebSocket-Key" not in header_dict: logging.error(this socket is not websocket, connection closed...) conn.close() return magic_key = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" sec_key = header_dic["Sec-WebSocket-Key"]+magic_key key = base64.b64encode(hashlib.sha1(bytes(sec_key, encoding="utf-8")).digest()) key_str = str(key)[2:30] logging.debug(key_str) response = "HTTP/1.1 101 Switching Protocols\r\n"\ "Connection:Upgrade\r\n"\ "Upgrade:websocket\r\n"\ "Sec-WebSocket-Accept:{0}\r\n"\ "WebSocket-Protocol:chat\r\n\r\n".format(key_str) conn.send(bytes(response),encoding="utf-8") logging.debug("send the handshake data") WebsocketThread(conn).start()
3)進行通訊:
Sever端接收到client發來的報文進行解析
下面是軟件通訊的七層結構:
下三層結構偏向於數據通訊,上三層更偏向於數據處理,中間的傳輸層是上三層與下三層之間的鏈接橋樑,每一層作不一樣的工做,上層協議依賴於下層協議,上層協議須要調用下層協議的接口,下層協議使用上層協議的參數,分工合做。
socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。屬於傳輸控制層協議,websocket是一個典型的應用層協議。
參考資料:https://www.cnblogs.com/JetpropelledSnake/p/9033064.html
https://www.cnblogs.com/lichmama/p/3931212.html