1. The background

Based on project requirements, there was a recent need to implement a simple chat function. In daily life, we are used to chatting, wechat, QQ and other software is often used, in fact, we can also introduce some third-party SDK packages to achieve, can also use WebSocket communication protocol to manually achieve a simple chat. This article mainly describes the WebSocket implementation of the specific steps and the realization of the effect diagram.

2. Scheme selection and introduction of advantages and disadvantages

  • Scheme one uses HTTP interface to manually implement three interfaces: SengMsg (message sending), receiveMsg(message receiving), getHistoryMsg(historical message receiving), then the front-end calls sendMsg interface when sending messages, writes data to the database for obtaining historical messages. When receiving messages, the front-end declares a timer. Refresh the message receiving interface every second to get the message content displayed in the chat box. Finally, if the user needs to look through the history of the message, call the getHistoryMsg interface.

    • Advantages The backend is simple to implement, and the chat messages can be persisted to the database for permanent storage, according to the chat room ID at any time to obtain the message content
    • Disadvantages Due to the frequent call interface, the server and API interface pressure is relatively large, the server may break down in high concurrency, and when no message is sent, due to the use of timer, frequent front-end requests will cause empty run, which is obviously not reasonable
  • Scheme 2 uses the existing WebSocket service to achieve the chat function

    • Advantages No additional implementation of their own interface, directly in accordance with the rules defined by WebSocket can be directly applied
    • Disadvantages The messages are not persisted and you may not be able to view historical messages if the service is down

3. Service establishment and implementation

  • 3.1 Importing Dependencies
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
Copy the code
  • 3.2 Declaring a Socket configuration class
@configuration public class WebSocketConfig {// Inject a ServerEndpointExporter @bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); }}Copy the code
  • 3.3 Declaring the Chat Controller
/** * Chat controller * @serverendpoint ("/chat/{userId}") userId is the id of the current user when the front end creates the session window, i.e. the id of the message sender */ @serverendpoint ("/chat/{userId}") @Component public class ChatWebSocketController { private final Logger logger = Logger.getLogger(ChatWebSocketController.class); //onlineCount: Number of online connections Private static AtomicInteger onlineCount = new AtomicInteger(0); //webSocketSet: stores the MyWebSocket object corresponding to each client. public static List<ChatWebSocketController> webSocketSet = new ArrayList<>(); Public static List<String> userList = new ArrayList<>(); // A Session is used to connect to a client and send data to the client. // userId public String userId = ""; / / @onopen public void OnOpen (Session Session, @PathParam("userId") String userId) { this.session = session; this.userId = userId; this.userList.add(userId) ; // Add webSocketSet.add(this); / / online number 1 onlineCount incrementAndGet (); Logger. info(" New connection added!" + userId + "Onlinecount.get ()"; JSONObject msg = new JSONObject(); Try {MSG. Put (" MSG ", "connect successfully "); msg.put("status", "SUCCESS"); msg.put("userId", userId); sendMessage(JSON.toJSONString(msg)); } catch (Exception e) {logger.debug("IO Exception "); @onClose public void OnClose (@pathParam ("userId") String userId) {// Delete from set webSocketSet.remove(this); onlineCount.decrementAndGet(); Logger. info(" user "+ userId +" quit chat! + onlinecount.get ()); } /** * @param message public void OnMessage (String message, @pathParam ("userId") String userId) {// The message entered by the client is processed and encapsulated into a new message. After receiving the new message, the back end parses the data, determines whether the message is sent in a group or in a single group, and calls the corresponding method Logger. info(" message from client "+ userId +" :" + message); try { MyMessage myMessage = JSON.parseObject(message, MyMessage.class); String messageContent = myMessage.getMessage(); String messageType = mymessage.getMessageType (); If ("1".equals(messageType)){String recUser = mymessage.getUserId (); / / recUser: message receiver sendInfo (messageContent recUser, userId); RecUser: message receiver userId Message sender}else{// Group chat sendGroupInfo(messageContent,userId); //messageContent: userId message sender}} Catch (Exception e) {logger.error(" parsing failed: {}", e); Public void OnError (Throwable error) {logger.debug("Websocket error "); error.printStackTrace(); } public synchronized void sendMessage(String message) { this.session.getAsyncRemote().sendText(message); } /** * single chat * message: the actual content of the message, not the concatenated content * recUser: message receiver * sendUser: Public void sendInfo(String message, String recUser,String sendUser) { JSONObject msgObject = new JSONObject(); //msgObject Message containing sender information for (ChatWebSocketController item: WebSocketSet) {if (stringutil. equals(item.userid, recUser)) {logger.info(" to user "+ recUser +" pass message :" + message); Msgobject. put("message",message); msgobject. put("message",message); msgObject.put("sendUser",sendUser); item.sendMessage(JSON.toJSONString(msgObject)); }}} /** * group chat * message: the content of the message, not the content of the concatenated * sendUser: Public void sendGroupInfo(String message,String sendUser) {JSONObject msgObject = new JSONObject(); //msgObject Message containing sender information if (stringutil. isNotEmpty(webSocketSet)) {for (ChatWebSocketController item: webSocketSet) { if(! Stringutil. equals(item.userid, sendUser)) {// Exclude sending messages back to the sender itself, or logger.info(" Sending messages back :" + message); Msgobject. put("message",message); msgobject. put("message",message); msgObject.put("sendUser",sendUser); item.sendMessage(JSON.toJSONString(msgObject)); }}}} /** * Map/Set must override hashCode and equals when the Map/Set key is a custom object. * The rule for handling hashCode and equals follows: * 1) Whenever you override equals, you must override hashCode. * 2) Since Set stores non-repeating objects according to hashCode and equals, we must override these two methods. * 3) If a custom object is used as a Map key, then hashCode and equals must be overridden. * * @param o * @return */ @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() ! = o.getClass()) { return false; } ChatWebSocketController that = (ChatWebSocketController) o; return Objects.equals(session, that.session); } @Override public int hashCode() { return Objects.hash(session); }}Copy the code
  • 3.4 Declare the MyMessage entity class in Controller
public class MyMessage implements Serializable { private static final long serialVersionUID = 1L; private String userId; private String message; // Message content private String messageType; Public String getUserId() {return userId; } public void setUserId(String userId) { this.userId = userId; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getMessageType() { return messageType; } public void setMessageType(String messageType) { this.messageType = messageType; }}Copy the code
  • 3.5 Declare the StringUtil utility class in Controller
Public final class StringUtil {public static Boolean isEmpty(object object) {public static Boolean isEmpty(object object) {  if (object == null) { return true; } if (object instanceof String && "".equals(((String) object).trim())) { return true; } if (object instanceof List && ((List) object).size() == 0) { return true; } if (object instanceof Map && ((Map) object).isEmpty()) { return true; } if (object instanceof CharSequence && ((CharSequence) object).length() == 0) { return true; } if (object instanceof Arrays && (Array.getLength(object) == 0)) { return true; } return false; } public static Boolean isNotEmpty(object object) {return! isEmpty(object); } @param string string * @param c * @return */ public static int strFirstIndex(string c, String string) { Matcher matcher = Pattern.compile(c).matcher(string); if (matcher.find()) { return matcher.start() + 1; } else { return -1; }} / * * * * * @ two objects are equal param * @ param obj1 obj2 * @ return * / public static Boolean equals (Object obj1, Object obj2) { if (obj1 instanceof String && obj2 instanceof String) { obj1 = ((String) obj1).replace("\\*", ""); obj2 = ((String) obj2).replaceAll("\\*", ""); if (obj1.equals(obj2) || obj1 == obj2) { return true; } } if (obj1.equals(obj2) || obj1 == obj2) { return true; } return false; } /** * public static String[] * @param bytes * @param Content separatorByBytes(double[] bytes, String content) { String[] contentArray = new String[bytes.length]; double[] array = new double[bytes.length + 1]; array[0] = 0; Arraycopy (bytes, 0, array, 1, bytes.length); for (int i = 0; i < bytes.length; i++) { content = content.substring((int) (array[i] * 2)); contentArray[i] = content; } String[] strings = new String[bytes.length]; for (int i = 0; i < contentArray.length; i++) { strings[i] = contentArray[i].substring(0, (int) (bytes[i] * 2)); } return strings; } /** * gets the number of occurrences of the specified string ** @param srcText source string * @param findText to find the string * @return */ public static int appearNumber(String srcText, String findText) { int count = 0; Pattern p = Pattern.compile(findText); Matcher m = p.matcher(srcText); while (m.find()) { count++; } return count; } /** * public static String[] setStr(String STR) {int m = str.length() / 2; if (m * 2 < str.length()) { m++; } String[] strings = new String[m]; int j = 0; for (int i = 0; i < str.length(); I++) {if (I % 2 = = 0) {/ / every two strings [j] = "" + STR. The charAt (I); } else { strings[j] = strings[j] + str.charAt(i); j++; } } return strings; } /** * define a StringBuffer, Public static String reverseString2(String s) {if (s.length() > 0) { StringBuffer buffer = new StringBuffer(s); return buffer.reverse().toString(); } else { return ""; Public static List<String> dateTimeSubAll(String STR) {try {public static List<String> dateTimeSubAll(String STR) {try { List<String> dateTimeStrList = new ArrayList<>(); String regex = "[0-9] {4} [-] [0-9] {1, 2} [-] [0-9] {1, 2} [] [0-9] {1, 2} [:] [0-9] {1, 2} [:] [0-9] {1, 2}"; Pattern pattern = compile(regex); Matcher matcher = pattern.matcher(str); while (matcher.find()) { String group = matcher.group(); dateTimeStrList.add(group); } return dateTimeStrList; } catch (Exception e) { e.getMessage(); return null; Public static List<String> dateSubAll(String STR) {try {List<String>  dateStrList = new ArrayList<>(); The Pattern the Pattern = the compile (" [0-9] {4} [-] [0-9] {1, 2} [-] [0-9] {1, 2} "); Matcher matcher = pattern.matcher(str); while (matcher.find()) { String group = matcher.group(); dateStrList.add(group); } return dateStrList; } catch (Exception e) { e.getMessage(); return null; Public static String getRandomString(int length) {String base = String base "abcdefghijklmnopqrstuvwxyz0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < length; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); }}Copy the code
  • 3.6 HTML page for background declaration test
<! DOCTYPE HTML> <html> <head> <title>WebSocket Chat Demo</title> </head> <body> <input id="inputContent" type="text" style="width:600px;" /> <button onclick="send()">Send</button> <button onclick="closeConnection()">Close</button> <div id="msg"></div> </body> <script type="text/javascript"> var websocket = null; If (' websocket 'in window) {var random = parseInt(math.random () * 1000000) + "; websocket = new WebSocket("ws://localhost:8005/chat/"+ random); } else {alert('Not support websocket')} websocket.onerror = function() {setMessageInnerHTML("error"); }; Websocket. onopen = function(event) {//setMessageInnerHTML("open"); Websocket. onMessage = function(event) {setMessageInnerHTML(event.data); Websocket. onclose = function() {setMessageInnerHTML("close"); } // Listen for window closing events and close corresponding websocket connection window.onbeforeunload = function() {websocket.close(); Function setMessageInnerHTML(innerHTML) {document.getelementById (' MSG '). InnerHTML += innerHTML + '<br/>';  } function closeConnection() {websocket.close(); Function () {var MSG = document.getelementById ('inputContent').value; websocket.send(msg); } </script> </html>Copy the code

The path of this class is as follows:

4. Start the service and test it

Enter IP + port on the page to establish websocket connection and send a message. The test result is shown in the figure below:

Pay attention to:

Pay attention to

  • 1. Under normal circumstances, only the actual chat content to be sent can be entered in the input box, such as “husband, urgent”, but for easier testing, the page is input with the spliced JSON message body, recipient user ID, and message type. In the actual development, the data format can be processed by the front-end. The front end can splice the input content into the data format as shown in the input block diagram
  • 2. MessageType is used to distinguish single chat from group chat. However, group chat here is all websocket services that establish connections, without the concept of grouping. When sending messages in group chat, only push group chat messages to users in the group according to different RoomIDS
  • 3. In addition, you can use the webSocket online test link as follows: WebSocket online test link

At the end of the article’s welfare

Ok, that’s all for today’s sharing. If it’s helpful, please feel free to comment and give me a thumbs up! For the content of high quality comments, there are exquisite gifts waiting for you to receive, I will draw two users to give a random badge, what are you waiting for, hurry to participate in the comments, exquisite gifts can not be missed!