Websocket實現羣聊功能

websocket是什麼

WebSocket是web客戶端和服務器之間新的通信方式, 依然架構在HTTP協議之上。使用WebSocket鏈接, web應用程序能夠執行實時的交互, 而不是之前的poll方式。html

WebSocket是HTML5開始提供的一種在單個 TCP 鏈接上進行全雙工通信的協議,能夠用來建立快速的更大規模的健壯的高性能實時的web應用程序。WebSocket通訊協議於2011年被IETF定爲標準RFC 6455,WebSocketAPI被W3C定爲標準。
在WebSocket API中,瀏覽器和服務器只須要作一個握手的動做,而後,瀏覽器和服務器之間就造成了一條快速通道。二者之間就直接能夠數據互相傳送。前端

—— Java WebSocket教程java

實現websocket有哪些方式

springboot集成websocket

首先要在maven中引入相關的依賴github

<dependency>  
 <groupId>org.springframework.boot</groupId>  
 <artifactId>spring-boot-starter-websocket</artifactId>  
</dependency>

WebSockerServerEndpoint核心的方法

咱們使用註解方式Annotation-driven編寫websocket客戶端代碼。經過在POJO加上註解, 開發者就能夠處理WebSocket生命週期事件。web

首先在類上添加@ServerEndpoint註解:spring

  • value 定義訪問 websocket 的路徑
  • decoders 和 encoders 用於定義編解碼類(下一小節會講)

因爲加了@ServerEndpoint,咱們須要實現與websocket生命週期相關的4個方法json

  • @OnOpen 表示剛創建鏈接時的操做,好比咱們要實現羣聊功能,就要在此時將同一個羣中的session都存起來
  • @OnMessage 表示創建鏈接以後接收到消息以後進行的操做
  • @OnClose 鏈接關閉時的操做,一樣是上面的羣聊,在關閉鏈接的時候就要將相應的session移除
  • @OnError 表示鏈接出現異常時的操做
@ServerEndpoint(  
        value \= "/chat-room/{conferenceId}/{userId}",  
  decoders \= { MessageDecoder.class },  
  encoders \= { ResponseMessageEncode.class })  
public class WebSockerServerEndpoint {  

  @OnOpen  
  public void openSession(@PathParam("conferenceId") String conferenceId,  @PathParam("userId") String userId,  
  Session session) {  
  
  }  
  
    @OnMessage  
  public void onMessage(@PathParam("conferenceId") String conferenceId,  @PathParam("userId") String userId,  
  Message message) {  
  
    }  
  
    @OnClose  
  public void onClose(@PathParam("userId") String userId, @PathParam("conferenceId") String conferenceId) {  
        
  }  
  
    @OnError  
  public void onError(Throwable t){  
        
  }  
  
}

編解碼器

websocket默認接受String類型的數據,而咱們須要實現更加複雜的需求。在聊天的過程當中,咱們但願可以發送圖片、文件、消息,還但願能有心跳保持鏈接,消息可以撤回等。這些僅僅經過String類型的數據是沒法知足咱們的需求的,因此要用更加複雜的類來實現,這就涉及到了encoder/decoder標準通訊過程。通訊的格式是能夠咱們本身定義的,能夠使用xml或者json等,下面演示json格式的編解碼。segmentfault

decoders 解碼

定義Message類和相應的解碼類,MessageDecode:後端

public class Message {  
    private String id;  
  
 private String conferenceId;  
  
 private String type;  
  
 private String content;  
  
 private String fileId;
 ...
import com.fasterxml.jackson.databind.ObjectMapper;  
  
import javax.websocket.DecodeException;  
import javax.websocket.Decoder;  
import javax.websocket.EndpointConfig;  
import java.io.IOException;  
  
public class MessageDecoder implements Decoder.Text<Message> {  
    @Override  
  public Message decode(String jsonMessage) throws DecodeException {  
        ObjectMapper mapper = new ObjectMapper();  
  Message message = null;  
 try {  
            message = mapper.readValue(jsonMessage, Message.class);  
  } catch (IOException e) {  
            e.printStackTrace();  
  }  
        return message;  
  }  
  
    @Override  
  public boolean willDecode(String jsonMessage) {  
        ObjectMapper mapper = new ObjectMapper();  
 try {  
            mapper.readValue(jsonMessage, Message.class);  
 return true;  } catch (IOException e) {  
            return false;  
  }  
    }  
  
    @Override  
  public void init(EndpointConfig endpointConfig) {  
  
    }  
  
    @Override  
  public void destroy() {  
  
    }  
}

encoders 編碼

將處理好的消息再次編碼,定義要給 ResponseMessage 和 ResponseMessageEncode 兩個類

public class ResponseMessage {  
  
    private String id;  
  
 private Account fromUser;  
  
 private String type;  
  
 private String content;  
  
 private ConferenceFile file;  
  
  @JsonFormat(pattern \= "yyyy-MM-dd HH:mm:ss")  
    private Date createTime;
import com.fasterxml.jackson.core.JsonProcessingException;  
import com.fasterxml.jackson.databind.ObjectMapper;  
  
import javax.websocket.EncodeException;  
import javax.websocket.Encoder;  
import javax.websocket.EndpointConfig;  
   
public class ResponseMessageEncode implements Encoder.Text<ResponseMessage> {  
    @Override  
  public String encode(ResponseMessage responseMessage) throws EncodeException {  
        ObjectMapper mapper = new ObjectMapper();  
  String json = null;  
 try {  
            json = mapper.writeValueAsString(responseMessage);  
  } catch (JsonProcessingException e) {  
            e.printStackTrace();  
  }  
  
        return json;  
  }  
  
    @Override  
  public void init(EndpointConfig endpointConfig) {  
  
    }  
  
    @Override  
  public void destroy() {  
  
    }  
}

功能實現

點對點發送消息

private void sendText(ResponseMessage responseMessage, Session session) {  
  
    RemoteEndpoint.Basic basic = session.getBasicRemote();  
  
 try {  
        basic.sendObject(responseMessage);  
  } catch (IOException e) {  
        e.printStackTrace();  
  } catch (EncodeException e) {  
        e.printStackTrace();  
  }  
}

羣發消息

private void sendTextAll(ResponseMessage responseMessage, String conferenceId) {  
    if(livingSessions.containsKey(conferenceId)){  
        HashMap<String, Session> sessionMap = livingSessions.get(conferenceId);  
  sessionMap.forEach((sessionId, session) -> {  
            sendText(responseMessage, session);  
  });  
  }  
}

心跳檢測

@OnMessage  
public void onMessage(@PathParam("conferenceId") String conferenceId,  
  @PathParam("userId") String userId,  
  Message message) {  
  
    try{  
        ResponseMessage responseMessage = new ResponseMessage();  
  
 if(MessageTypeEnum.HEART.equals(message.getType()) && "ping".equals(message.getContent())){  
            this.heartMessage();  
 return;  }
private void heartMessage() {  
    ResponseMessage responseMessage = new ResponseMessage();  
  responseMessage.setType(MessageTypeEnum.HEART.name());  
  responseMessage.setContent("pong");  
  sendText(responseMessage, this.session);  
}

消息撤回

消息撤回要經過廣播被撤回的 message_id 來實現,由前端向後端傳遞一個被撤回的 message_id,後端刪除消息並進行廣播。前端接收到撤回消息的廣播以後,將相應 message_id 的消息刪除便可。

題外話-靜態注入

spring的bean都是單例(singleton)的,websocket是多實例單線程的。websocket中的對象在@Autowried時,會在應用啓動時注入一次,以後建立的websocket對象都不會注入service,因此websocket中注入的的bean會是null。

能夠用下面這樣靜態注入的方式,在應用啓動的時候注入bean,因爲是靜態變量,能夠供全部websocket對象使用。

private static IMessageService messageService;

@Autowired  
public void setMessageService(MessageServiceImpl messageService){  
    WebSockerServerEndpoint.messageService \= messageService;  
}
相關文章
相關標籤/搜索