SpringBoot + WebSocket 开发笔记
本文内容纲要:
1.服务端的实现,我尝试了两种方式:
- 第一种是用“@ServerEndPoint”注解来实现,实现简单;
- 第二种稍显麻烦,但是可以添加拦截器在WebSocket连接建立和断开前进行一些额外操作。
不管用哪种实现方式,都需要先导入jar包(如下),其中version根据实际springboot版本选择,避免冲突
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<!--<version>1.3.5.RELEASE</version>-->
</dependency>
1.1第一种实现方法
(1)WebSocket业务逻辑实现。参数传递采用路径参数的方法,通过以下方式获取参数:
-
@ServerEndpoint("/testWebSocket/{id}/{name}")
-
publicvoidonOpen(Sessionsession,@PathParam("id")longid,@PathParam("name")Stringname)
import java.util.concurrent.CopyOnWriteArraySet;
importjavax.websocket.OnClose; importjavax.websocket.OnError; importjavax.websocket.OnMessage; importjavax.websocket.OnOpen; importjavax.websocket.Session; importjavax.websocket.server.ServerEndpoint;
importorg.slf4j.Logger; importorg.slf4j.LoggerFactory; importorg.springframework.web.bind.annotation.RestController;
@ServerEndpoint("/testWebSocket/{id}/{name}") @RestController publicclassTestWebSocket{
//用来记录当前连接数的变量 privatestaticvolatileintonlineCount=0; //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象 privatestaticCopyOnWriteArraySet<TestWebSocket>webSocketSet=newCopyOnWriteArraySet<TestWebSocket>(); //与某个客户端的连接会话,需要通过它来与客户端进行数据收发 privateSessionsession; privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(TestWebSocket.class);
@OnOpen publicvoidonOpen(Sessionsession,@PathParam("id")longid,@PathParam("name")Stringname)throwsException{ this.session=session; System.out.println(this.session.getId()); webSocketSet.add(this); LOGGER.info("Openawebsocket.id={},name={}",id,name); }
@OnClose publicvoidonClose(){ webSocketSet.remove(this); LOGGER.info("Closeawebsocket."); } @OnMessage publicvoidonMessage(Stringmessage,Sessionsession){ LOGGER.info("Receiveamessagefromclient:"+message); } @OnError publicvoidonError(Sessionsession,Throwableerror){ LOGGER.error("Errorwhilewebsocket.",error); } publicvoidsendMessage(Stringmessage)throwsException{ if(this.session.isOpen()){ this.session.getBasicRemote().sendText("Sendamessagefromserver."); } } publicstaticsynchronizedintgetOnlineCount(){ returnonlineCount; } publicstaticsynchronizedvoidaddOnlineCount(){ TestWebSocket.onlineCount++; } publicstaticsynchronizedvoidsubOnlineCount(){ TestWebSocket.onlineCount--; }
}
(2)配置ServerEndpointExporter,配置后会自动注册所有“@ServerEndpoint”注解声明的WebsocketEndpoint
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
publicclassWebSocketConfig{
@Bean
publicServerEndpointExporterserverEndpointExporter(){
returnnewServerEndpointExporter();
}
}
1.2第二种实现方法
(1)WebSocket业务逻辑实现。参数传递采用类似GET请求的方式传递,服务端的参数在拦截器中获取之后通过attributes传递给WebSocketHandler。
importjava.util.ArrayList;
importjava.util.concurrent.atomic.AtomicInteger;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.web.bind.annotation.RestController;
importorg.springframework.web.socket.CloseStatus;
importorg.springframework.web.socket.WebSocketHandler;
importorg.springframework.web.socket.WebSocketMessage;
importorg.springframework.web.socket.WebSocketSession;
@RestController
publicclassTestWebSocketControllerimplementsWebSocketHandler{
privatestaticAtomicIntegeronlineCount=newAtomicInteger(0);
privatestaticfinalArrayList<WebSocketSession>sessions=newArrayList<>();
privatefinalLoggerLOGGER=LoggerFactory.getLogger(TestWebSocketController.class);
@Override
publicvoidafterConnectionEstablished(WebSocketSessionsession)throwsException{
sessions.add(session);
intonlineNum=addOnlineCount();
LOGGER.info("OprnaWebSocket.Currentconnectionnumber:"+onlineNum);
}
@Override
publicvoidafterConnectionClosed(WebSocketSessionsession,CloseStatusstatus)throwsException{
sessions.remove(session);
intonlineNum=subOnlineCount();
LOGGER.info("CloseawebSocket.Currentconnectionnumber:"+onlineNum);
}
@Override
publicvoidhandleMessage(WebSocketSessionwsSession,WebSocketMessage<?>message)throwsException{
LOGGER.info("Receiveamessagefromclient:"+message.toString());
}
@Override
publicvoidhandleTransportError(WebSocketSessionsession,Throwableexception)throwsException{
LOGGER.error("ExceptionoccursonwebSocketconnection.disconnecting....");
if(session.isOpen()){
session.close();
}
sessions.remove(session);
subOnlineCount();
}
/*
*是否支持消息拆分发送:如果接收的数据量比较大,最好打开(true),否则可能会导致接收失败。
*如果出现WebSocket连接接收一次数据后就自动断开,应检查是否是这里的问题。
*/
@Override
publicbooleansupportsPartialMessages(){
returntrue;
}
publicstaticintgetOnlineCount(){
returnonlineCount.get();
}
publicstaticintaddOnlineCount(){
returnonlineCount.incrementAndGet();
}
publicstaticintsubOnlineCount(){
returnonlineCount.decrementAndGet();
}
}
(2)HandShake拦截器实现
importjava.util.Map;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.context.ApplicationContext;
importorg.springframework.http.server.ServerHttpRequest;
importorg.springframework.http.server.ServerHttpResponse;
importorg.springframework.http.server.ServletServerHttpRequest;
importorg.springframework.web.socket.WebSocketHandler;
importorg.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
publicclassTestHandShakeInterceptorextendsHttpSessionHandshakeInterceptor{
privatefinalLoggerLOGGER=LoggerFactory.getLogger(TestHandShakeInterceptor.class);
/*
*在WebSocket连接建立之前的操作,以鉴权为例
*/
@Override
publicbooleanbeforeHandshake(ServerHttpRequestrequest,ServerHttpResponseresponse,
WebSocketHandlerwsHandler,Map<String,Object>attributes)throwsException{
LOGGER.info("HandlebeforewebSocketconnected.");
//获取url传递的参数,通过attributes在Interceptor处理结束后传递给WebSocketHandler
//WebSocketHandler可以通过WebSocketSession的getAttributes()方法获取参数
ServletServerHttpRequestserverRequest=(ServletServerHttpRequest)request;
Stringid=serverRequest.getServletRequest().getParameter("id");
Stringname=serverRequest.getServletRequest().getParameter("name");
if(tokenValidation.validateSign()){
LOGGER.info("Validationpassed.WebSocketconnecting....");
attributes.put("id",id);
attributes.put("name",name);
returnsuper.beforeHandshake(request,response,wsHandler,attributes);
}else{
LOGGER.error("Validationfailed.WebSocketwillnotconnect.");
returnfalse;
}
}
@Override
publicvoidafterHandshake(ServerHttpRequestrequest,ServerHttpResponseresponse,
WebSocketHandlerwsHandler,Exceptionex){
//省略
}
}
(3)WebSocket配置类实现(注册WebSocket实现类,绑定接口,同时将实现类和拦截器绑定)
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.web.servlet.config.annotation.EnableWebMvc;
importorg.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
importorg.springframework.web.socket.config.annotation.EnableWebSocket;
importorg.springframework.web.socket.config.annotation.WebSocketConfigurer;
importorg.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
importTestWebSocketController;
importTestHandShakeInterceptor;
@Configuration
@EnableWebMvc
@EnableWebSocket
publicclassWebSocketConfigextendsWebMvcConfigurerAdapterimplementsWebSocketConfigurer{
@Autowired
privateTestWebSocketControllertestWebSocketController;
@Override
publicvoidregisterWebSocketHandlers(WebSocketHandlerRegistryregistry){
registry.addHandler(TestWebSocketController,"/testWebSocket")
.addInterceptors(newTestHandShakeInterceptor()).setAllowedOrigins("*");
}
}
1.3补充说明
(1)在WebSocket实现过程中,尤其是通过“@ServerEndpoint”实现的时候,可能会出现注入失败的问题,即注入的Bean为null的问题。可以通过手动注入的方式来解决,需要改造实现类和SpringBoot启动类,如下:
@ServerEndpoint("testWebsocket")
@RestController
publicclassWebSocketController{
privateTestServicetestService;
privatestaticApplicationContextapplicationContext;
@OnOpen
publicvoidonOpen(Sessionsession){
testService=applicationContext.getBean(TestService.class);
}
@OnClose
publicvoidonClose(){}
@OnMessage
publicvoidonMessage(Stringmessage,Sessionsession){}
@OnError
publicvoidonError(Sessionsession,Throwableerror){}
publicstaticvoidsetApplicationContext(ApplicationContextapplicationContext){
WebSocketController.applicationContext=applicationContext;
}
}
importorg.springframework.boot.SpringApplication;
importorg.springframework.boot.autoconfigure.SpringBootApplication;
importorg.springframework.context.ConfigurableApplicationContext;
importWebSocketController;
@SpringBootApplication
publicclassApplication{
publicstaticvoidmain(String[]args){
//SpringApplication.run(Application.class,args);
SpringApplicationspringApplication=newSpringApplication(Application.class);
ConfigurableApplicationContextconfigurableApplicationContext=springApplication.run(args);
WebSocketController.setApplicationContext(configurableApplicationContext);//解决WebSocket不能注入的问题
}
}
2.1html实现
<!DOCTYPEhtml>
<html>
<head>
<title>WebSocket示例</title>
<metacontent='width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no'name='viewport'/>
<metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/>
</head>
<body>
<inputid="text"type="text"/>
<buttononclick="send()">发送消息</button>
<hr/>
<buttononclick="closeWebSocket()">关闭WebSocket连接</button>
<hr/>
<divid="message"></div>
</body>
<scripttype="text/javascript">
varwebsocket=null;
//判断当前浏览器是否支持WebSocket
if('WebSocket'inwindow){
//不带参数的写法
websocket=newWebSocket("ws://127.0.0.1:18080/testWebsocket");
//通过路径传递参数的方法(服务端采用第一种方法"@ServerEndpoint"实现)
websocket=newWebSocket("ws://127.0.0.1:18080/testWebsocket/23/Lebron");
//通过类似GET请求方式传递参数的方法(服务端采用第二种方法"WebSocketHandler"实现)
websocket=newWebSocket("ws://127.0.0.1:18080/testWebsocket?id=23&name=Lebron");
}
else{
alert('当前浏览器Notsupportwebsocket')
}
//连接发生错误的回调方法
websocket.onerror=function(){
setMessageInnerHTML("WebSocket连接发生错误");
};
//连接成功建立的回调方法
websocket.onopen=function(){
setMessageInnerHTML("WebSocket连接成功");
}
//接收到消息的回调方法
websocket.onmessage=function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose=function(){
setMessageInnerHTML("WebSocket连接关闭");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload=function(){
closeWebSocket();
}
//将消息显示在网页上
functionsetMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML+=innerHTML+'<br/>';
}
//关闭WebSocket连接
functioncloseWebSocket(){
websocket.close();
}
//发送消息
functionsend(){
varmessage=document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
2.2JavaWebSocketClient实现
(1)WebSocketClient实现类
importjava.net.URI;
importorg.java_websocket.client.WebSocketClient;
importorg.java_websocket.drafts.Draft;
importorg.java_websocket.handshake.ServerHandshake;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
publicclassTestWebSocketClientextendsWebSocketClient{
privatefinalLoggerLOGGER=LoggerFactory.getLogger(TestWebSocketClient.class);
publicTestWebSocketClient(URIserverUri){
super(serverUri);
}
publicTestWebSocketClient(URIserverUri,DraftprotocolDraft){
super(serverUri,protocolDraft);
}
@Override
publicvoidonOpen(ServerHandshakeserverHandshake){
LOGGER.info("OpenaWebSocketconnectiononclient.");
}
@Override
publicvoidonClose(intarg0,Stringarg1,booleanarg2){
LOGGER.info("CloseaWebSocketconnectiononclient.");
}
@Override
publicvoidonMessage(Stringmsg){
LOGGER.info("WebSocketClientreceivesamessage:"+msg);
}
@Override
publicvoidonError(Exceptionexception){
LOGGER.error("WebSocketClientexception.",exception);
}
}
(2)WebSocketClient发送数据
StringserverUrl="ws://127.0.0.1:18080/testWebsocket"
URIrecognizeUri=newURI(serverUrl);
client=newTestWebSocketClient(recognizeUri,newDraft_6455());
client.connect();
client.send("Thisisamessagefromclient.");
本文内容总结:
原文链接:https://www.cnblogs.com/strugglion/p/10021173.html