What is WebSocket?
WebSocket protocol is a new network protocol based on TCP. It implements full duplex communication between the browser and the server -- allowing the server to actively send information to the client
Why do I need WebSocket?
People who come into contact with WebSocket for the first time will ask the same question: we already have the HTTP protocol. Why do we need another protocol? What benefits can it bring?
The answer is very simple, because the HTTP protocol has a defect: communication can only be initiated by the client, and the HTTP protocol cannot push information to the client actively.
Don't say much. Enter the dry goods moment immediately.
maven dependency
Springboot 2.0's support for WebSocket is great. There are packages that can be introduced directly
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
WebSocketConfig
Enabling WebSocket support is also very simple, with a few words of code
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * Enable WebSocket support */ @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
WebSocketServer
That's the point. The core is here.
1. Because WebSocket is similar to client and server (ws protocol is adopted), the WebSocket server here is actually equivalent to a Controller of ws protocol
2. Directly enable @ ServerEndpoint("/imserver/{userId}") and @ Component, and then implement @ OnOpen to open the connection, @ onClose to close the connection, and @ onMessage to receive messages.
3. Create a new ConcurrentHashMap webSocketMap to receive the WebSocket of the current userId, so as to facilitate the push message of userId between IM S. The stand-alone version can be implemented here.
4. The cluster Version (multiple ws nodes) also needs to be processed with the help of mysql or redis, and the corresponding sendMessage method can be modified.
import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Component; import cn.hutool.log.Log; import cn.hutool.log.LogFactory; @ServerEndpoint("/imserver/{userId}") @Component public class WebSocketServer { static Log log=LogFactory.get(WebSocketServer.class); /**Static variable, used to record the current number of online connections. It should be designed to be thread safe.*/ private static int onlineCount = 0; /**concurrent The thread safe Set of the package is used to store the MyWebSocket object corresponding to each client.*/ private static ConcurrentHashMap<String,WebSocketServer> webSocketMap = new ConcurrentHashMap<>(); /**The connection session with a client needs to send data to the client through it*/ private Session session; /**Receive userId*/ private String userId=""; /** * Method successfully called for connection establishment*/ @OnOpen public void onOpen(Session session,@PathParam("userId") String userId) { this.session = session; this.userId=userId; if(webSocketMap.containsKey(userId)){ webSocketMap.remove(userId); webSocketMap.put(userId,this); //Add to set }else{ webSocketMap.put(userId,this); //Add to set addOnlineCount(); //Online number plus 1 } log.info("User connection:"+userId+",The number of people currently online is:" + getOnlineCount()); try { sendMessage("Connection succeeded"); } catch (IOException e) { log.error("user:"+userId+",Network exception!!!!!!"); } } /** * Method called for connection closure */ @OnClose public void onClose() { if(webSocketMap.containsKey(userId)){ webSocketMap.remove(userId); //Delete from set subOnlineCount(); } log.info("User exit:"+userId+",The number of people currently online is:" + getOnlineCount()); } /** * Method of calling after receiving client message * * @param message Messages sent by the client*/ @OnMessage public void onMessage(String message, Session session) { log.info("User message:"+userId+",message:"+message); //You can send messages in groups //Save messages to database and redis if(StringUtils.isNotBlank(message)){ try { //Analyze the sent message JSONObject jsonObject = JSON.parseObject(message); //Add sender (prevent serial modification) jsonObject.put("fromUserId",this.userId); String toUserId=jsonObject.getString("toUserId"); //websocket transmitted to the corresponding toUserId user if(StringUtils.isNotBlank(toUserId)&&webSocketMap.containsKey(toUserId)){ webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString()); }else{ log.error("Requested userId:"+toUserId+"Not on this server"); //Otherwise, it will not be sent to mysql or redis on this server } }catch (Exception e){ e.printStackTrace(); } } } /** * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("user error:"+this.userId+",reason:"+error.getMessage()); error.printStackTrace(); } /** * Realize server active push */ public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } /** * Send custom message * */ public static void sendInfo(String message,@PathParam("userId") String userId) throws IOException { log.info("Send message to:"+userId+",message:"+message); if(StringUtils.isNotBlank(userId)&&webSocketMap.containsKey(userId)){ webSocketMap.get(userId).sendMessage(message); }else{ log.error("user"+userId+",Not online!"); } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketServer.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketServer.onlineCount--; } }
Message push
As for pushing new information, you can write a method in your Controller to call WebSocketServer.sendInfo(); that will do
import com.softdev.system.demo.config.WebSocketServer; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.ModelAndView; import java.io.IOException; /** * WebSocketController * @author zhengkai.blog.csdn.net */ @RestController public class DemoController { @GetMapping("index") public ResponseEntity<String> index(){ return ResponseEntity.ok("Request succeeded"); } @GetMapping("page") public ModelAndView page(){ return new ModelAndView("websocket"); } @RequestMapping("/push/{toUserId}") public ResponseEntity<String> pushToWeb(String message, @PathVariable String toUserId) throws IOException { WebSocketServer.sendInfo(message,toUserId); return ResponseEntity.ok("MSG SEND SUCCESS"); } }
Page initiation
The page calls websocket with js code. Of course, too old browsers can't do it. Generally, there is no problem with new browsers or Google browsers. Also, remember that the protocol is ws. If some path classes are used, replace("http", "ws") can be used to replace the protocol.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>websocket communication</title> </head> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script> <script> var socket; function openSocket() { if(typeof(WebSocket) == "undefined") { console.log("Your browser does not support WebSocket"); }else{ console.log("Your browser supports WebSocket"); //Implement the WebSocket object, specify the server address to be connected and establish a connection with the port //Equivalent to socket = new WebSocket("ws://localhost:8888/xxxx/im/25"); //var socketUrl="${request.contextPath}/im/"+$("#userId").val(); var socketUrl="http://localhost:9999/demo/imserver/"+$("#userId").val(); socketUrl=socketUrl.replace("https","ws").replace("http","ws"); console.log(socketUrl); if(socket!=null){ socket.close(); socket=null; } socket = new WebSocket(socketUrl); //Open event socket.onopen = function() { console.log("websocket Opened"); //socket.send("this is a message from the client" + location.href + new Date()); }; //Get message event socket.onmessage = function(msg) { console.log(msg.data); //The discovery message enters the start processing front-end trigger logic }; //Close event socket.onclose = function() { console.log("websocket Closed"); }; //An error event occurred socket.onerror = function() { console.log("websocket An error has occurred"); } } } function sendMessage() { if(typeof(WebSocket) == "undefined") { console.log("Your browser does not support WebSocket"); }else { console.log("Your browser supports WebSocket"); console.log('{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'"}'); socket.send('{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'"}'); } } </script> <body> <p>[userId]: <div><input id="userId" name="userId" type="text" value="10"></div> <p>[toUserId]: <div><input id="toUserId" name="toUserId" type="text" value="20"></div> <p>[toUserId]: <div><input id="contentText" name="contentText" type="text" value="hello websocket"></div> <p>[[operation]:<div><a onclick="openSocket()">open socket</a></div> <p>[[operation]:<div><a onclick="sendMessage()">send message</a></div> </body> </html>
Operation effect
First open two pages and press F12 to call up the control console to view the test effect:
Then open the socket respectively and send the message
Figure 2:
2. Push data to the front end:
http://localhost:9999/demo/push/10?message=123123
By calling the push api, you can push information to the specified userId. Of course, the message is scribbled here. It is recommended to specify the format.
ServerEndpointExporter error
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serverEndpointExporter' defined in class path resource [com/xxx/WebSocketConfig.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: javax.websocket.server.ServerContainer not available
If the tomcat deployment keeps reporting this error, please remove the injection of @ Bean ServerEndpointExporter in WebSocketConfig.
ServerEndpointExporter is a standard implementation officially provided by Spring. It is used to scan ServerEndpointConfig configuration classes and @ ServerEndpoint annotation instances. The rules are also simple:
If you use a default embedded container such as Tomcat, you must manually provide the server endpoint exporter in the context.
If you use an external container to deploy the war package, you do not need to provide the ServerEndpointExporter, because at this time, SpringBoot defaults to the external container to handle the scanning behavior of the server, so you should note out the code injected into the bean in WebSocketConfig during online deployment.
websocket connection for Vue version
<script> export default { data() { return { socket:null, userId:localStorage.getItem("ms_uuid"), toUserId:'2', content:'3' } }, methods: { openSocket() { if (typeof WebSocket == "undefined") { console.log("Your browser does not support WebSocket"); } else { console.log("Your browser supports WebSocket"); //Implement the WebSocket object, specify the server address to be connected and establish a connection with the port //Equivalent to socket = new WebSocket("ws://localhost:8888/xxxx/im/25"); //var socketUrl="${request.contextPath}/im/"+$("#userId").val(); var socketUrl = "http://localhost:8081/imserver/" + this.userId; socketUrl = socketUrl.replace("https", "ws").replace("http", "ws"); console.log(socketUrl); if (this.socket != null) { this.socket.close(); this.socket = null; } this.socket = new WebSocket(socketUrl); //Open event this.socket = new WebSocket(socketUrl); //Open event this.socket.onopen = function() { console.log("websocket Opened"); //socket.send("this is a message from the client" + location.href + new Date()); }; //Get message event this.socket.onmessage = function(msg) { console.log(msg.data); //The discovery message enters the start processing front-end trigger logic }; //Close event this.socket.onclose = function() { console.log("websocket Closed"); }; //An error event occurred this.socket.onerror = function() { console.log("websocket An error has occurred"); }; } }, sendMessage() { if (typeof WebSocket == "undefined") { console.log("Your browser does not support WebSocket"); } else { console.log("Your browser supports WebSocket"); console.log( '{"toUserId":"' + this.toUserId + '","contentText":"' + this.content + '"}' ); this.socket.send( '{"toUserId":"' + this.toUserId + '","contentText":"' + this.content + '"}' ); } }