一、什麼是WebSocket
API
WebSocket API是下一代用戶端-伺服器的非同步通訊方法。該通訊取代了單個的TCP通訊端,使用ws或wss協議,可用於任意的用戶端和伺服器程式。WebSocket目前由W3C進行標準化。WebSocket已經受到Firefox
4、Chrome 4、Opera 10.70以及Safari 5等瀏覽器的支援。
WebSocket API最偉大之處在於伺服器和用戶端可以在給定的時間範圍內的任意時刻,相互推送資訊。WebSocket並不限於以Ajax(或XHR)方式通訊,因為Ajax技術需要用戶端發起請求,而WebSocket伺服器和用戶端可以彼此相互推送資訊;XHR受到域的限制,而WebSocket允許跨域通訊。
二、WebSocket協議 websocket的協議是很簡單的,這裡我把它分成用戶端和服務端來講。在用戶端,new WebSocket即可執行個體化一個新的websocket對象,但其參數略微有一點不一樣,參數格式是這樣的ws://yourdomain:port/path
,WebSocket對象會自動解析這段字串,發送到指定伺服器連接埠,首先執行的是雙方握手(handshake),用戶端發送資料格式類似這樣:
GET /chat HTTP/1.1Upgrade: WebSocketConnection: UpgradeHost: www.zendstudio.net:9108Origin: http://www.zendstudio.netCookie: somenterCookie
這很是有些類似於http的頭資訊,同樣每行都是以”\r\n”結尾的,上面這段格式無需我們去構造,WebSocket對象會自動發送,對用戶端這是透明的。此時服務端應該返回的資訊是:
HTTP/1.1 101 Web Socket Protocol HandshakeUpgrade: WebSocketConnection: UpgradeWebSocket-Origin: http://www.zendstudio.netWebSocket-Location: ws://www.zendstudio.net:9108/chat
從這裡我們太容易看出來,websocket協議的握手部分根本就是個類http的協議,所不同的是http每次都會有這樣子的頭資訊互動,這在某些時候不得不顯得很糟糕。而websocket只會執行一次這個過程,之後的傳輸資訊就變得異常簡潔了。
握手協議:request中有三個隨機的key值,頭部有兩個,後面body裡是長度為8位元組的key3(括弧裡的文字是提示,還有字元間的冒號也是為了看上去清晰才加上的,真正傳輸是沒有的),以此向server發送一個challenge,server需要根據這三個key計算出一個token,在響應中發回給client,以證明自己對request的正常解讀。計算方法是這樣的:對於key1,抽取其中的數字字元,形成一個整數num,然後除以他自身的空格數spaces,保留整數部分i1;
key2如法炮製,得到i2,把i1和i2按照big-endian字元序串連起來,然後再與key3串連,得到一個初始的序列,對這個序列使用md5計算出一個16位元組長的摘要,就是所需的token。另外值得注意的是Origin頭部,意味著Websocket是支援cross origin的。
三、用戶端client.html
<html><head><title>WebSocket</title> <style> html,body{font:normal 0.9em arial,helvetica;} #log {width:440px; height:200px; border:1px solid #7F9DB9; overflow:auto;} #msg {width:330px;}</style> <script>var socket; function init(){ var host = "ws://10.3.18.105:19887/"; try{ socket = new WebSocket(host); socket.onopen = function(msg){ ; }; socket.onmessage = function(msg){ log(msg.data); }; socket.onclose = function(msg){ log("Lose Connection!"); }; } catch(ex){ log(ex); } $("msg").focus();} function send(){ var txt,msg; txt = $("msg"); msg = txt.value; if(!msg){ alert("Message can not be empty"); return; } txt.value=""; txt.focus(); try{ socket.send(msg); } catch(ex){ log(ex); }} window.onbeforeunload=function(){ try{ socket.send('quit'); socket.close(); socket=null; } catch(ex){ log(ex); }}; function $(id){ return document.getElementById(id); }function log(msg){ $("log").innerHTML+="<br>"+msg; }function onkey(event){ if(event.keyCode==13){ send(); } }</script> </head><body onload="init()"> <h3>WebSocket</h3> <br><br> <div id="log"></div> <input id="msg" type="textbox" onkeypress="onkey(event)"/> <button onclick="send()">發送</button></body></html>
四、伺服器端server.py
import socketimport structimport hashlibimport threading,randomconnectionlist = {}def sendMessage(message): global connectionlist for connection in connectionlist.values(): connection.send("\x00%s\xFF" %message)def deleteconnection(item): global connectionlist del connectionlist['connection'+item]class WebSocket(threading.Thread): def __init__(self, conn, index, name, remote, path="/"): threading.Thread.__init__(self) self.conn = conn self.index = index self.name = name self.remote = remote self.path = path self.buffer = "" def run(self): print 'Socket %s Start!' %self.index headers = {} self.handshaken = False while True: if self.handshaken == False: print 'Socket %s Start Handshaken with %s!' %(self.index, self.remote) self.buffer += self.conn.recv(1024) if self.buffer.find('\r\n\r\n') != -1: header, data = self.buffer.split('\r\n\r\n', 1) for line in header.split("\r\n")[1:]: key, value = line.split(": ", 1) headers[key] = value headers["Location"] = "ws://%s%s" %(headers["Host"], self.path) print headers key1 = headers["Sec-WebSocket-Key1"] key2 = headers["Sec-WebSocket-Key2"] if len(data) < 8: data += self.conn.recv(8-len(data)) key3 = data[:8] self.buffer = data[8:] token = self.generate_token(key1, key2, key3) handshake = '\HTTP/1.1 101 Web Socket Protocol Handshake\r\n\Upgrade: WebSocket\r\n\Connection: Upgrade\r\n\Sec-WebSocket-Origin: %s\r\n\Sec-WebSocket-Location: %s\r\n\r\n\' %(headers['Origin'], headers['Location']) self.conn.send(handshake + token) self.handshaken = True print 'Socket %s Handshaken with %s success!' %(self.index, self.remote) sendMessage('Welcome, ' + self.name + ' !') else: self.buffer += self.conn.recv(64) if self.buffer.find("\xFF") != -1: s = self.buffer.split("\xFF")[0][1:] if s == 'quit': print 'Socket %s Logout !' %(self.index) sendMessage(self.name + ' Logout') deleteconnection(str(self.index)) self.conn.close() break else: print 'Socket %s Got msg: %s from %s!' %(self.index,s,self.remote) sendMessage(self.name + ':' + s) self.buffer = "" def generate_token(self, key1, key2, key3): num1=int("".join([digit for digit in list(key1) if digit.isdigit()])) spaces1 = len([char for char in list(key1) if char == " "]) num2 = int("".join([digit for digit in list(key2) if digit.isdigit()])) spaces2 = len([char for char in list(key2) if char == " "]) combined = struct.pack(">II", num1/spaces1, num2/spaces2) + key3 return hashlib.md5(combined).digest()class WebSocketServer(object): def __init__(self): self.socket = None def begin(self): print "WebSocketSerber Start!" self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.bind(("10.3.18.105", 19887)) self.socket.listen(50) global connectionlist i = 0 while True: connection, address = self.socket.accept() username = address[0] newSocket = WebSocket(connection, i, username, address) newSocket.start() conectionlist['connection'+str(i)] = connection i = i + 1if __name__ == "__main__": server = WebSocketServer() server.begin()