websocket筆記以及一個微型聊天室例子

首先說明:這裏的tomcat用的是tomcat8.0.36,並不適合tomcat7以及如下版本,(沒辦法websocket的api一直在變,到8以後貌似穩定下來了)javascript

websocket也是html5的新增長內容之一,號稱是下一代客戶端/服務器異步通訊辦法,私覺得雖然有點吹牛的成分,可是之後說不定能成爲異步通訊的半壁江山,至於取代ajax,我覺的應該不會html

websocket的一個頗有意思的特色就是雙向通訊,這一點其實也不稀奇,跟socket同樣的。
我記得大二上學期的java課程設計我作的是一個仿照QQ的用戶程序socket通訊,寫起來雖然順暢可是由於那個c/s架構寫起來痛苦不已,只實現一個簡單的羣聊就要大概一千五的代碼量,尤爲回調函數跟界面綁定的時候,寫起來很X痛,今後就再沒寫過windows界面程序
下邊是websocket的原理性知識總結是寫給我本身看的,若是你沒興趣,能夠跳過直接到代碼:前端

  • tcp創建鏈接
    tcp鏈接的創建須要經歷」三次握手「的過程。過程以下client發送SYN包(值爲j)以及SEQ包到server端,此時client進入SYN_SEND狀態。此爲第一次握手。server端收到SYN包後,發送一個ACK(值爲seq+1)確認包和SYN(值爲k)給client,此時server進入SYN_RECV狀態。此爲第二次握手。client收到SYN+ACK包後,向server發送一個ACK(值爲k+1),該包發送完成後,client和server均進入ESTABLISH狀態。此爲第三次握手。html5

client和server兩端狀態變化以下:java

client:node

CLOSED->SYN_SEND->ESTABLISHweb

server:ajax

CLOSED->LISTEN->SYN_RECV->ESTABLISHwindows

  • tcp鏈接釋放
    Tcp釋放鏈接的過程須要經歷「四次揮手」的過程,爲何創建鏈接只須要3次握手,而釋放鏈接須要進行4次揮手呢?很簡單,由於TCP鏈接是全雙工(Full Duplex)的,所以形成了兩個方向都須要進行關閉。怎麼理解呢?client和server,須要關閉鏈接,此時client通知server我要關閉鏈接了,此時關閉的只會是client這一端的鏈接,而server端並未關閉,它依舊可以向client發送數據。固然,關閉鏈接也能夠是server做爲主動方的。接下來以client主動斷開與server端的鏈接爲場景來描述整個過程,咱們把它分爲兩個階段,分別爲client端關閉鏈接和server端關閉鏈接。api

圖片描述

第一階段

首先client會發送一個FIN包給server(同時還有ack和seq包),這是要告訴server,我已經沒有數據要發給你了,此時client處於FIN_WAIT_1狀態。接收到FIN包的server處於CLOSE_WAIT的狀態。
server發回一個ACK(值爲client傳過來的seq+1)和seq(值爲client傳過來的ack的值)給client。client收到server發過來的包後確認關閉鏈接,此時client處於FIN_WAIT_2。

第二階段

server在接收到client的FIN後,得知client要斷開tcp鏈接了,因而在發送完ack和seq給client後,本身發送一個FIN包給client(也帶有ack和seq包),告訴client我也要斷開鏈接了,此時server處於LAST_ACK狀態。
client接收到server的FIN信息後,會回覆server一個ack包,而且會進入TIME_WAIT狀態,持續2個MSL(Max Segment Lifetime),這個時間windows下爲240s。而server接收到client後便關閉鏈接。client在指定時間事後仍然沒有接收到server的數據,確認server已經沒有數據過來,也關閉了鏈接。
此時雙方都進入CLOSED狀態。
  • 創建細節

tcp是傳輸層的協議,tcp三次握手後,應用層協議http也便創建了鏈接。而對於當今web的發展狀況,http仍有許多瓶頸。

一條鏈接只能發送一個請求。
請求只能從客戶端開始。客戶端不能夠接收除響應之外的指令。
請求/響應首部未經壓縮發送,首部信息越多延遲越大。
發送冗長的首部。每次互相發送相同的首部形成較多的浪費。
可任意選擇數據壓縮格式。非強制壓縮發送。
雖然已經出現了不少解決方案,如ajax、comet,可是他們最終使用的都是http協議,所以也沒法從根本上解決這些瓶頸。

所以也就誕生了一個新的通訊協議,WebSocket協議,一種全雙工通訊協議。

該通訊協議創建在http協議的基礎之上,所以鏈接的發起方仍然是客戶端,在http鏈接創建以後,再將協議升級爲webSocket鏈接,在webSocket鏈接創建以後,客戶度和服務器端均可以主動向對方發送報文信息了。

創建webSocket鏈接,須要先創建http鏈接,並在此基礎上再進行一次」握手「。

client會發起一個」握手「的請求,請求首部含有upgrade:websocket(還有其餘首部,具體看以下示例)。服務器端返回一個101狀態碼,確認轉換協議。完成握手後即可以使用websocket協議進行通訊。

Client(request)

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: AQIDBAUGBwgJCgsMDQ4PEC==
Origin: http://example.com
Sec-WebSocket-protocol: chat, superchat
Sec-WebSocket-Version: 13

server (response)

HTTP/1.1 101 switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Protocol: chat

websocket這個協議涉及前端顯示,以及服務器處理,我這裏使用基礎的java+js來實現一個簡單的羣聊

若是不熟悉websocket的api,你能夠看這裏:https://developer.mozilla.org...
以及:https://www.ibm.com/developer...

圖片描述

重要的只有兩個文件:chat.java以及chat.html

Chat.java:

package example;

import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;


@ServerEndpoint(value="/ws/chat/{nickName}")
public class Chat {
    private static final Set<Chat> connections = new CopyOnWriteArraySet<Chat>();
    private String nickName;
    private Session session;
    
    
    public Chat(){
        
    }
    
    /*
     * 打開鏈接
     */    
    @OnOpen
    public void onOpen(Session session,@PathParam(value="nickName") String nickName){
        this.session=session;
        this.nickName=nickName;
        connections.add(this);
        System.out.println("新用戶鏈接進入,名字是:"+this.nickName);
        String message=String.format("System>%s %s",this.nickName,"hasjoined.");
        Chat.broadCast(message);
        
    }
    /*
     * 關閉鏈接
     */
    @OnClose
    public void onClose(){
        connections.remove(this);
        String message=String.format("System> %s, %s", this.nickName,
                " has disconnection.");
        Chat.broadCast(message);
    }
    
    /*
     * 接收信息
     */
    @OnMessage
    public void onMessage(String message,@PathParam(value="nickName")String nickName){
        System.out.println("新消息from:"+nickName+" : "+message);
        Chat.broadCast(nickName+">"+message);
    }
    /*
     * 錯誤消息
     */
    @OnError
    public void onError(Throwable throwable){
        System.out.println(throwable.getMessage());
    }
    /*
     * 廣播消息
     */
    private static void broadCast(String message){
        for(Chat chat:connections){
            try{
                synchronized (chat) {
                    chat.session.getBasicRemote().sendText(message);
                }
            }catch(IOException e){
                connections.remove(chat);
                try{
                    chat.session.close();
                    
                }catch(IOException e1){
                    chat.broadCast(String.format("System> %s %s", chat.nickName,
                            " has bean disconnection."));
                }
            }
        }
    }
}

chat.html:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Testing websockets</title>
</head>
<body>
  <div>
      <input type="text" id="yourName"/>
    <button id="start">click to start</button>
  </div>
  <div style="width:100%;">
      <div id="messages" style="border:5px solid red;width:50%;height:300px;margin-top:20px;"></div>
  </div>
  <div style="width:100%;margin-top:20px;">
      <div id="sends" style="float:left;width:50%;height:50px;margin-right:20px;">
          <input type="text"  id="MessageUN" style="width:100%;height:48px;"/>
      </div>
      <button style="float:left;width:10%;height:50px;" id="sendMessage">發送</button>
  </div>
  
  <script type="text/javascript">
  var button=document.getElementById("start");
  button.onclick=function(){
      
      
      
      
      var name=document.getElementById("yourName");
      console.log(name.value);
      var websocketAdd="ws://localhost:8080/t8j8/ws/chat/"+name.value;
      var webSocket=new WebSocket(websocketAdd); 

      
      function onMessage(event) {
            document.getElementById('messages').innerHTML 
              += '<br />' + event.data;
      }

      function onOpen(event) {
            document.getElementById('messages').innerHTML 
              = 'Connection established';
          //一旦連接開始,嘗試發出一條通信消息
             start();
          alert("消息通道開啓,能夠發送消息了");
          //確認連接開始,就能夠開始消息的發送了
          //首先綁定一個點擊事件
          var sendMessage=document.getElementById("sendMessage");
          sendMessage.onclick=function(){
              var message= document.getElementById('MessageUN').value;
              //咱們以前會有一個惟一的標識符,就是在click to start以前的標識符
              webSocket.send(message);
              //上邊一部完成以後,會自動觸發onmessage事件
          }
          
      }
      
      
       function start() {
         webSocket.send(name.value+" : "+'hello');
       }

      function onError(event) {
            alert(event.data);
      }
      
      webSocket.onerror = function(event) {
            onError(event)
      };

      webSocket.onopen = function(event) {
            onOpen(event)
      };

      webSocket.onmessage = function(event) {
            onMessage(event)
      };      
        
  }
  
  </script>
</body>
</html>

一些問題:
WebSockets 簡介:將套接字引入網絡

WebSocket 是什麼原理?爲何能夠實現持久鏈接?(看尤雨溪大神的回答)

再談應用環境下的TIME_WAIT和CLOSE_WAIT

若是你熟悉nodejs你還能夠:
使用Node.js+Socket.IO搭建WebSocket實時應用

相關文章
相關標籤/搜索