上一章節我們說了websocket的優缺點,我們通過websocket和http的對比分析,總結出用websocket的場景。今天小編帶大家通過一個案例使用下升級版的websocket。
sockjs介紹
So長江J算是一個瀏覽器庫,它提供了一個跨瀏覽器的api , 他在瀏覽器和服務端建立了一個低延遲、全雙工、跨域通信通道。
產生的原因
向ie這些瀏覽器可能缺少對websocket的支持,我們上一章節也是在谷歌瀏覽器下開發完成的。這里對ie這些瀏覽器沒有做測試,但是一些低版本的瀏覽器的確是不支持的websocket的。
sockJs對瀏覽器兼容性很大。在原聲的websocket基礎上進行了優化。sockjs在不支持websocket的瀏覽器上會采用輪詢的方式實現雙向通信。
環境搭建
springboot整合sockjs
springboot 對sockjs支持性很良好。只需要在原有的websocket配置上添加已sockjs方式發布就可以了。
@Configuration
//注解開啟使用STOMP協議來傳輸基於代理(message broker)的消息,這時控制器支持使用@MessageMapping,就像使用@RequestMapping一樣
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {//注冊STOMP協議的節點(endpoint),並映射指定的url
//注冊一個STOMP的endpoint,並指定使用SockJS協議
registry.addEndpoint("/endpointAric")
.setAllowedOrigins("*")
.addInterceptors(createSessionInterceptor())
.withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {//配置消息代理(Message Broker)
//廣播式應配置一個/topic消息代理
registry.enableSimpleBroker("/topic", "/queue");
//registry.setApplicationDestinationPrefixes("/app");
//點對點使用的訂閱前綴(客戶端訂閱路徑上會體現出來),不設置的話,默認也是/user/
registry.setUserDestinationPrefix("/user/");
}
/**
* 配置客戶端入站通道攔截器
*/
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(createUserInterceptor());
}
@Bean
public HandshakeInterceptor createSessionInterceptor(){
return new SessionAuthHandshakeInterceptor();
}
/*將客戶端渠道攔截器加入spring ioc容器*/
@Bean
public UserInterceptor createUserInterceptor() {
return new UserInterceptor();
}
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.setMessageSizeLimit(500 * 1024 * 1024);
registration.setSendBufferSizeLimit(1024 * 1024 * 1024);
registration.setSendTimeLimit(200000);
}
}
registry.enableSimpleBroker("/topic", "/queue");
設置的topic,queue就是客戶端和服務端的通信通道。
使用場景
本節內容我們將通過聊天室體驗下websocket的使用。
聊天室開發
上面我們已經配置了服務端的環境,這樣我們就可以開發通道內的通信內容。聊天室使用websocket進行通信解決了我們通過ajax調用的實時性。保證了消息的准確性。無延后性。
點對點通信
/*點對點通信*/
@MessageMapping(value = "/sendToUser")
public void templateTest1(@Payload String message, @Header("userId") String userId,
@Headers Map<String, Object> headers) {
int i = 1;
for (SimpUser user : userRegistry.getUsers()) {
System.out.println("用戶" + i++ + "---" + user);
}
CustomUser userInfo = (CustomUser) ((Map)(headers.get("simpSessionAttributes"))).get(CoreConstants.USERINFO);
String fromUserId = String.valueOf(userInfo.getUserId());
//發送消息給指定用戶
messagingTemplate.convertAndSendToUser(userId, "/queue/message",new AricResponse(fromUserId,userId,message));
if (!fromUserId.equals(userId)) {
//給自己發送一條,消息同步
messagingTemplate.convertAndSendToUser(fromUserId, "/queue/message",new AricResponse(fromUserId,userId,message));
}
//消息新增
messageService.insertBackMessage(fromUserId,userId,message);
}
@MessageMapping(value = "sendToUser")
該注解實現了接受客戶端的請求。對應客戶端可以通過
stompClient.send("/sendToUser", {'userId': userId},content);
進行發送至服務端,userId是客戶端用戶發送給指定用戶的id, content是發送的內容
服務端通過@Payload和@Header注解接受前端傳送的信息。上述代碼中獲取發送者用戶id的地方為什么那么寫呢。下面我們看看客戶端連接的方式
// 建立連接對象(還未發起連接)
socket = new SockJS(host+"/endpointAric");
// 獲取 STOMP 子協議的客戶端對象
stompClient = Stomp.over(socket);
// 向服務器發起websocket連接並發送CONNECT幀
stompClient.connect(
{
userId: currentUser.userId // 攜帶客戶端信息
},
function connectCallback(frame) {
// 連接成功時(服務器響應 CONNECTED 幀)的回調方法
subscribe();
console.log("連接成功");
},
function errorCallBack(error) {
// 連接失敗時(服務器響應 ERROR 幀)的回調方法
console.log("連接失敗"+error);
if (errorTimes < 10) {
errorTimes++;
setTimeout("connect();",8000);
}
}
);
在連接的時候客戶端會將當前用戶的id傳遞進來,這里也解釋了為什么服務端那樣獲取用戶信息。然后就通過/queue/message
發送給指定的用戶。因為我們在配置websocket的時候指定了
registry.setUserDestinationPrefix("/user/");
,所以服務端發送給/queue/message
了,客戶端訂閱的時候需要加上user 即 /user/queue/message
function subscribe() {
stompClient.subscribe('/user/queue/message', function (response) {
var returnData = JSON.parse(response.body);
if (returnData.fromUserId == returnData.toUserId) {
//自己發送的消息需要自己渲染到聊天框中
setMessageInnerHTML(currentUser.userId, returnData, 0);
} else if (returnData.fromUserId == currentUser.userId) {
//自己發送信息給別,自己收到的信息
setMessageInnerHTML(returnData.toUserId, returnData, 1);
} else {
//別人發送的信息
setMessageInnerHTML(returnData.fromUserId, returnData, 0);
}
});
}
群聊
/**
* 多房間聊天室
* @param chatRoomId
* @param message
* @return
*/
@MessageMapping("/welcome/{chatRoomId}") //當瀏覽器向服務端發送請求時,通過@MessageMapping映射/welcome這個地址,類似於@ResponseMapping
@SendTo("/topic/getResponse/{chatRoomId}")//當服務器有消息時,會對訂閱了@SendTo中的路徑的瀏覽器發送消息
public AricResponse say(@Header("userId") String userId,@DestinationVariable("chatRoomId") String chatRoomId, AricMessage message) {
try {
//睡眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new AricResponse(userId,null,"welcome," + message.getName()+chatRoomId + "!");
}
群聊相對與單聊簡單很多,我們發送信息的時候只需要發送到房間通道內就行。而在次群聊內的人只需要訂閱房號的信息就行了。這里代碼不在贅述。有疑問的小伙伴可以通過下方加入戰隊找到我。
效果
總結
sockjs在websocket基礎上進行各個瀏覽器的兼容,讓我們的開發變得友好起來。
如果你使用Java做服務端,同時又恰好使用Spring Framework作為框架,那么推薦使用SockJS,因為Spring Framework本身就是SockJS推薦的Java Server實現,同時也提供了Java 的client實現。
如果你使用Node.js做服務端,那么毫無疑問你該選擇Socket.IO,它本省就是從Node.js開始的,當然服務端也提供了engine.io-server-java實現。甚至你可以使用
加入戰隊
微信公眾號