背景
在实现聊天室项目的时候,我遇到了一个问题,那就是消息是如何从服务器端自己推送给客户端的呢?一开始我的想法是每隔一段时间,就让客户端向服务器端请求数据,也就是所谓的“轮询”机制,但这种方式显然是太耗费资源的,我在查阅资料后,发现有一个应用层协议专门服务于这种情况的:WebSocket。 (依旧是基于传输层的TCP实现的)
报文格式
websocket握手过程
代码示例
先来编写服务器端的代码:
1)创建一个类,作为WebSocketHandler(处理websocket中的各个通信流程)
具体的业务场景:
@Component
public class WebSocketComponent extends TextWebSocketHandler {
@Autowired
OnlineUserManager onlineUserManager;
@Autowired
private ObjectMapper objectMapper;
@Autowired
MessageMapper messageMapper;
@Autowired
MessageSessionMapper messageSessionMapper;
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
UserInfo userInfo = (UserInfo) session.getAttributes().get("SESSION_USER");
if(userInfo == null){
return;
}
System.out.println("get websocket session -> "+userInfo.getId());
onlineUserManager.online(userInfo.getId(),session);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
UserInfo userInfo = (UserInfo) session.getAttributes().get("SESSION_USER");
if(userInfo == null){
return;
}
MessageRequest request = objectMapper.readValue(message.getPayload(),MessageRequest.class);
if(request.getType().equals("message")){
transferMessage(userInfo,request);
}else{
System.out.println("ERROR : MessageRequest:Type is error!");
}
}
private void transferMessage(UserInfo userInfo, MessageRequest request) throws IOException {
//先构造一个response 并且转成String类型
MessageResponse messageResponse = new MessageResponse();
messageResponse.setContent(request.getContent());
messageResponse.setFromid(userInfo.getId());
messageResponse.setFromname(userInfo.getUsername());
messageResponse.setSessionid(request.getSessionId());
String resJson = objectMapper.writeValueAsString(messageResponse);
//通过sessionId 来获取好友列表 再遍历这个链表 进行传输信息
List<Friend> list = messageSessionMapper.getFriendsBySID(request.getSessionId(),userInfo.getId());
Friend mySelf = new Friend();
mySelf.setFriendname(userInfo.getUsername());
mySelf.setFriendId(userInfo.getId());
list.add(mySelf);
for(Friend friend : list){
WebSocketSession webSocketSession = onlineUserManager.getSession(friend.getFriendId());
if(webSocketSession == null){
// 这种情况是未登录的状态 先不发送
continue;
}else{
webSocketSession.sendMessage(new TextMessage(resJson));
}
}
// 还要把这个消息 发给 数据库 进行永久保存操作
Message message = new Message();
message.setFromid(userInfo.getId());
message.setSessionid(request.getSessionId());
message.setContent(request.getContent());
messageMapper.add(message);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
UserInfo userInfo = (UserInfo) session.getAttributes().get("SESSION_USER");
if(userInfo == null){
return;
}
onlineUserManager.offline(userInfo.getId(),session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
UserInfo userInfo = (UserInfo) session.getAttributes().get("SESSION_USER");
if(userInfo == null){
return;
}
onlineUserManager.offline(userInfo.getId(),session);
}
}
2)把上述类的实例,给注册到Spring里面,配置路由。(关联上哪个路径对应到上述的handler)
@Configuration
@EnableWebSocket //启动websocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
WebSocketComponent webSocketComponent;
//通过这个方法 进行路由注册
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketComponent,"websocketmessage")
.addInterceptors(new HttpSessionHandshakeInterceptor());
}
}
写客户端的代码: