WebSocket是web客戶端和服務器之間新的通信方式, 依然架構在HTTP協議之上。使用WebSocket鏈接, web應用程序能夠執行實時的交互, 而不是之前的poll方式。html
WebSocket是HTML5開始提供的一種在單個 TCP 鏈接上進行全雙工通信的協議,能夠用來建立快速的更大規模的健壯的高性能實時的web應用程序。WebSocket通訊協議於2011年被IETF定爲標準RFC 6455,WebSocketAPI被W3C定爲標準。
在WebSocket API中,瀏覽器和服務器只須要作一個握手的動做,而後,瀏覽器和服務器之間就造成了一條快速通道。二者之間就直接能夠數據互相傳送。前端
—— Java WebSocket教程java
springboot集成websocketgit
首先要在maven中引入相關的依賴github
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
咱們使用註解方式Annotation-driven編寫websocket客戶端代碼。經過在POJO加上註解, 開發者就能夠處理WebSocket生命週期事件。web
首先在類上添加@ServerEndpoint
註解:spring
因爲加了@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
定義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() { } }
將處理好的消息再次編碼,定義要給 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; }