websocket是什么
WebSocket是web客户端和服务器之间新的通讯方式, 依然架构在HTTP协议之上。使用WebSocket连接, web应用程序可以执行实时的交互, 而不是以前的poll方式。
WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议,可以用来创建快速的更大规模的健壮的高性能实时的web应用程序。WebSocket通信协议于2011年被IETF定为标准RFC 6455,WebSocketAPI被W3C定为标准。
在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
实现websocket有哪些方式
- Java WebSocket教程
-
springboot集成websocket
- netty实现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;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。