關於WebSocket的介紹和相關API,網上有很多文檔,感覺下面這個說得比較細:
這個是SpringBoot整合 WebSocket的文檔,前端用的SocketJS/Stomp。後端用的是Spring所支持SocketJS:springboot websocket 一篇足夠了
前端用的Stomp來獲取client進行SocketJS的API操作:Stomp Over Websocket文檔
我們知道SocketJS對Cookie的兼容性不好,網上有看到可以把Cookie信息放到Header中進行傳輸及獲取,這是相關的整合文檔:
Spring+WebSocket+SockJS實現實時聊天
關於爲什麼不直接用原生的WebSocket而用SocketJS應該不用我多說,就是IE10以下不支持ws協議,爲了它的兼容性,需要在不支持websocket瀏覽器下降級成長輪詢的方式。而SocketJS和SocketIO都封裝了WebSocket。而SocketIO後端官方是用NodeJS實現的,如果要用Java當服務器端,可以使用基於Netty實現的netty-socketio:githup地址,它官方也提供了相應的demo,提出了namespace/room的概念,比較適合當聊天工具的開發,只是要開啓新的端口來監聽連接(socketJS就不需要)
補充一下:SocketJS中有個
registry.setUserDestinationPrefix("/user");
有點不容易被理解,看了源碼,在convertAndSendToUser和convertAndSend方法時體現了他們的不同之處
SimpMessagingTemplate.class
// convertAndSendToUser方法底層調用的convertAndSend方法,只不過參數添加了config中的destinationPrefix及點對點的接收人
super.convertAndSend(this.destinationPrefix + user + destination, payload, headers, postProcessor);
另附上後端調用api時主要的代碼及註釋:
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.socket.WebSocketSession;
import top.fissile.manager.SocketManager;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
/**
* demo地址:http://123.56.157.29:3333/html/index.html
* @Author duln
* @Date 2019/12/27 11:27
* @Version 1.0
*/
@RestController
@Slf4j
public class TestController {
@Autowired
private SimpMessagingTemplate template;
/**
* @Description 接收客戶端的消息
* @param principal 這是在攔截器中設置的數據,之前只用token給設置個name值
* @param msg 客戶端發送過來的消息(也可以用map接收,前端就需要obj轉json)
*/
// 只和前端的發送參數相關,類似RequestMapping,前端直接clent.send('/sendAllUser',...)就可以
@MessageMapping("/sendAllUser")
// 貌似得和@MessageMapping連用才生效,也就限制了一般只能用在controller中
// 區別於@SendToUser,@SendTo是用來服務器給客戶端羣發的:value就是羣發的destination,方法返回值就是羣發數據
// 如果返回值爲Map,則客戶端收到的是application/json格式;如果返回值是String,則收到的是xxx/text格式
// @SendTo同template.convertAndSend(),只是template可用於任何地方
@SendTo("/topic/receiveMsg")
public Map<String, Object> sendAllUser(Principal principal, String msg) {
Map<String, Object> map = new HashMap<>();
map.put("type", "公開");
map.put("userName", principal.getName());
map.put("message", msg);
return map;
}
/**
* 客戶端請求獲取當前人數信息
*/
@MessageMapping("/getCount")
public void getCount(Principal principal) {
Map<String, Object> map = SocketManager.get();
// 與convertAndSend不同的是:this.destinationPrefix + user
// super.convertAndSend(this.destinationPrefix + user + destination, payload, headers, postProcessor);
template.convertAndSendToUser(principal.getName(), "/queue/count", map);
}
/**
* 點對點用戶聊天
*/
@MessageMapping("/sendOneUser")
public void sendOneUser(Principal principal, Map<String, String> map) {
log.info("map = {}", map);
String toName = map.get("userName");
String fromName = principal.getName();
WebSocketSession webSocketSession = SocketManager.get(toName);
if (webSocketSession != null) {
Map<String, Object> toMap = new HashMap<>();
toMap.put("type", "私聊");
toMap.put("message", map.get("message"));
Map<String, Object> fromMap = new HashMap<>(toMap);
toMap.put("fromName", fromName);
toMap.put("toName", "你");
fromMap.put("fromName", "你");
fromMap.put("toName", toName);
template.convertAndSendToUser(toName, "/queue/sendUser", toMap);
template.convertAndSendToUser(fromName, "/queue/sendUser", fromMap);
}
}
}