Java實現Websocket


Websocket介紹  

  在一個 WebSocket應用中, 服務器發布一個 WebSocket端點, 客戶端使用這個端點的URI來連接服務器。建立連接之后,websocket協議是對稱的;客戶端和服務器可以在連接打開的任何時間相互發送消息,而且它們可以在任何時間關閉連接。客戶端總是只連接到一 個服務器,而服務器可以接受多個客戶端的連接。
  WebSocket協議有兩部分: 握手和數據傳輸。 客戶端使用一個 WebSocket端點的 URI向它發送一個請求,發起握手過程。這個握手過程與現有基於 HTTP的基石出設施是兼容的:web服務器把它解釋為一個 HTTP連接升級請求。
WebSocket支持文本消息(編碼為 UTF-8 )和二進制消息。 WebSocket中的控制幀是close、 ping和pong(對ping幀的響應)。 ping和 pong幀可能還包含應用數據。websocket端點由采用以下形式的 URI表示:

  • ws://host:port/path?query
  • wss://host:port/path?query    

ws模式表示一個未加密的 WebSocket連接, wss模式表示一個加密的連接。 port是可選的;對於未加密的連接,默認的端口號是80,對於加密連接。默認的端口號是443。 path部分指示服務器中一個端點的位置。 query部分是可選的。現代 web瀏覽器實理了 WebSocket協議, 並提供一個 Javascript API來連接端點,發送消息,以及為WebSocket事件指定回調方法(如打開連接、接收消息、關閉連接)。

在Java 中創建WebSocket應用  

WebSocket Java API包括以下包:

  • javax.websocket.server 包含創建和配置服務端點的注解、類和接口。
  • javax.websocket包含客戶端和服務端點公共的注解、類、和接口。

  WebSocket端點是javax.websocket.Endpoint美的實例. WebSocket Java API允許創建兩類端點: 可編程端點和注解端點。

可編程端點

  通過擴展Endpoint類來創建一個端點:

package com.zhang;
import java.io.IOException;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
public class EchoEndpoint extends Endpoint{
    @Override
    public void onOpen(Session session, EndpointConfig config) {
        session.addMessageHandler(new MessageHandler.Whole<String>() {
            @Override
            public void onMessage(String msg) {
                try {
                    session.getBasicRemote().sendText("echo:" + msg);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        
    }
}

  在父類Endpoint有三個生命周期方法:onOpen、onClose、和onError,所以只需實現onOpen即可,后面兩個默認為空操作。
  創建完端點后,還需要把它啟動起來,是通過ServerContainer.addEndpoint來啟動。

package com.zhang;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
@WebListener
public class EndpointListener implements ServletContextListener{
    @Override
    public void contextInitialized(ServletContextEvent context) {
        ServerContainer container = (ServerContainer) context.getServletContext()
                .getAttribute(ServerContainer.class.getName());
        ServerEndpointConfig config = ServerEndpointConfig.Builder.create(EchoEndpoint.class, "/echo").build();
        try {
            container.addEndpoint(config);
        } catch (DeploymentException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
        
    }
}

  上面代碼中,ServerContainer是通過context.getServletContext().getAttribute(ServerContainer.class.getName())來獲得,這個在容器啟動后,放在ServletContext中

注解端點

package com.zhang;
import java.io.IOException;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/echo")
public class EchoEndpoint{
    @OnMessage
    public void onMessage(Session session,String msg) {
        try {
            session.getBasicRemote().sendText("echo:" + msg);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  用注解就比較簡潔,不用通過在監聽器里添加端點,容器啟動后,自動添加端點。

收發二進制消息

  服務端接收消息onMessage(String msg),還是發消息session.getBasicRemote().sendText(“echo:”);都是字符串類,如果要收發二進制的內容,只需把參數名改成ByteBuffer或byte[]數組,例如onMessage(Session session,ByteBuffer buffer) {,session.getBasicRemote().sendBinary(ByteBuffer buffer)

維持客戶端狀態

  由於容器會為每一個連接創建一個端點類實例, 所以可以定義並使用實例變量來存儲客戶端狀態信息。另外, session.getUserProperties方法提供了一個可修改的映射來存儲用戶屬性。

使用編碼器和解碼器

  WebSocket Java API使用編碼器和解碼器為 WebSocket消息和定制 Java類型之間的轉換提供支持。編碼器取一個 Java對象,生成一種可以作為 WebSocket消息傳輸的表示;例如編碼器通常會生成 JSON、 XML或二進制表示。解碼器完成相反的功能,它讀取一個WebSocket消息, 並創建一個 Java對象。這種機制可以簡化 WebSocket應用, 因為這樣可以使業務邏輯與對象的串行化和逆串行化解耦合。

編碼器

  在端點中實現以下某個接口:

  • 對於文本消息實現 Enooder.Text;
  • 對於二進制消息實現 Encoder.Binary

以下是把Java編碼成JSON字符串

package com.zhang.quick;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
import com.alibaba.fastjson.JSONObject;
public class TextEncoder implements Encoder.Text<ChatMessage>{
    @Override
    public String encode(ChatMessage chat) throws EncodeException {
        return JSONObject.toJSONString(chat);
    }
    ...
}

在@ServerEndpoint注解中添加encoders

@ServerEndpoint(value="/chat",encoders={TextEncoder.class,MessageEncoder.class})
public class ChatEndpoint {

可以看到encoders是個Class數組,說明是可以有多個編碼器。

解碼器

  實現以下某個接口

  • 對於文本消息實現Decoder.Text。
  • 對於二進制消息實現Decoder.Binary。
public class MessageDecoder implements Decoder.Text<ChatMessage>{
    @Override
    public ChatMessage decode(String json) throws DecodeException {
        return JSON.parseObject(json, ChatMessage.class);
    }
    ...
}

接下來,為@ServerEndpoint注解增加decoder參數
在@ServerEndpoint注解中添加encoders

@ServerEndpoint(value="/chat",encoders={TextEncoder.class,MessageEncoder.class}
,decoders={MessageDecoder.class})
public class ChatEndpoint {

WebSocket JavaScript編程方式

var webSocket = new WebSocket(
                'ws://localhost:8080/websocket/echo');
webSocket.onerror = function(event) {
    onError(event)
};
webSocket.onopen = function(event) {
    onOpen(event)
};
webSocket.onmessage = function(event) {
    onMessage(event)
};
function onMessage(event) {
    document.getElementById('messages').innerHTML += '<br />'
            + event.data;
}
function onOpen(event) {
    document.getElementById('messages').innerHTML = 'Connection established';
}
function onError(event) {
    alert(event.data);
}
function start() {
    webSocket.send('hello');
    return false;
}

代碼中var webSocket = new WebSocket(‘ws://localhost:8080/websocket/echo’);里只是創建一個WebSocket對象,沒有真正連接到服務器,只有當調用WebSocket.send時才開始連接。

參考資料

  • 《Java EE 7權威指南》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM