websocket是什么

WebSocket是web客户端和服务器之间新的通讯方式, 依然架构在HTTP协议之上。使用WebSocket连接, web应用程序可以执行实时的交互, 而不是以前的poll方式。

WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议,可以用来创建快速的更大规模的健壮的高性能实时的web应用程序。WebSocket通信协议于2011年被IETF定为标准RFC 6455,WebSocketAPI被W3C定为标准。
在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

—— Java WebSocket教程

实现websocket有哪些方式

springboot集成websocket

首先要在maven中引入相关的依赖

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

WebSockerServerEndpoint核心的方法

我们使用注解方式Annotation-driven编写websocket客户端代码。通过在POJO加上注解, 开发者就可以处理WebSocket生命周期事件。

首先在类上添加@ServerEndpoint注解:

  • value 定义访问 websocket 的路径
  • decoders 和 encoders 用于定义编解码类(下一小节会讲)

由于加了@ServerEndpoint,我们需要实现与websocket生命周期相关的4个方法

  • @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格式的编解码。

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;  
}

深页
105 声望3 粉丝