基於http請求以拉的方式去作服務器的推送,不管是實時性和有效字節都是差強人意的效果。web
公司的im系統在與客戶端的交互上實際上藉助了websocket來實現服務器與客戶端的事實消息推送,今天就來簡單瞭解下這個協議,而且本身實現對websocket的響應。服務器
能夠看到在理解了tcp和http以後,websocket的設計其實並不複雜,再最開始創建連接的時候客戶端實際上會進行一次http請求,只不過請求頭的內容有些特別,這裏咱們來看下:websocket
GET /chat HTTP/1.1socket
Host: server.example.comtcp
Upgrade: websocketide
Connection: Upgrade測試
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==this
Origin: http://example.com編碼
Sec-WebSocket-Protool: chat,superchat加密
Sec-WebSocket-Version:13
能夠看到這個報文裏包含了一些附加頭信息。其中附加頭信息"Upgrade: WebSocket" ,代表這是一個申請協議升級的http請求。"Sec-WebSocket-Key"是隨機的,服務端會用這些數據構造出
一個SHA-1的信息摘要,把"Sec-WebSocket-Key"加上一個魔幻字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"。使用SHA-1加密,而後進行BASE-64編碼,將結果做爲"Sec-WebSocket-Accept"頭的值,返回給客戶端:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protool: chat
實現對websocket請求的響應:
public class WebsocketServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket=new ServerSocket(8080); Socket socket= serverSocket.accept(); new Thread(new Handle(socket)).start(); } }
public class Handle implements Runnable{ private Socket socket; Handle(Socket socket){ this.socket=socket; } @Override public void run() { try { BufferedReader reader=new BufferedReader(new InputStreamReader(socket.getInputStream())); OutputStreamWriter streamWriter=new OutputStreamWriter(socket.getOutputStream()); BufferedWriter bufferedWriter=new BufferedWriter(streamWriter); String key=null; //讀報文 while (true){ String s= reader.readLine(); System.out.println(s); if (s.equals("")){ break; }else{ if (s.contains("Sec-WebSocket-Key")){ String keyValue[]=s.split(":"); key=keyValue[1].trim()+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; } } } //寫報文 MessageDigest messageDigest=DigestUtils.getSha1Digest(); byte[] digest=messageDigest.digest(key.getBytes()); Base64 base64 = new Base64(); String finalKey=base64.encodeToString(digest); bufferedWriter.write("HTTP/1.1 101 Switching Protocols\r\n"); bufferedWriter.write("Upgrade: websocket\r\n"); bufferedWriter.write("Connection: Upgrade\r\n"); bufferedWriter.write("Sec-WebSocket-Accept: "+finalKey+"\r\n"); bufferedWriter.write("Sec-WebSocket-Protool: chat\r\n"); bufferedWriter.write("\r\n"); bufferedWriter.write("test"); bufferedWriter.write("\r\n"); bufferedWriter.flush(); //接收數據 System.out.println("響應報文已經發送"); while (true){ String s=reader.readLine(); System.out.println(s==null); } } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }
創建鏈接的時候要注意 Sec-WebSocket-Key在作sha-1哈希時,取得時摘要 而後拿着摘要去作base64遍嘛獲得Sec-WebSocket-Accept,上面代碼還有問題就是鏈接創建以後 BufferedReader的readLine()方法在遇到\r,\r\n,\n以前會等到填滿緩衝區纔會被喚醒,找的不少測試用的客戶端都會把換行符去掉致使線程在填滿緩衝區8KB以前一直阻塞,另外一個問題就是字符流的編碼問題。這裏更多的關注鏈接創建過程,鏈接創建以後其實就是直接用tcp傳輸數據了,這裏很少作贅述