我们大多数在网上找到的相关资料只会告诉你这么做,就能实现这个功能,却不知道为什么这么做。在此我对实现过程以及原理做一个大致的总结。关于websock的原理以及详细介绍大家可以参考我的上一篇博客《通俗理解websocket》是一篇来自知乎大神的回答,个人觉得好多文章讲的都不及这一篇深刻。。。。
在此我只实现了消息单对单,以及群发,聊天记录保存的实现。细节方面在此就不做阐述了,有兴趣的小伙伴可以自行补全。主要看的是实现原以及实现思路。有了思路之后做起来当然顺风顺水了哈。
下面进入正题:
1.在Springboot的pom文件中添加maven相应依赖:
1 2 3 4 5 6 7 8 9 |
<!--wensocket支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> |
2.启用WebSocket的支持:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } } |
3.添加websocket服务端-封装了websocket的一些属性、事件和方法,以便我们直接可以调用!我们只需要在客户端实例化 WebSocket,创建连接,然后服务端和客户端就可以相互发送和响应消息。
onOpen方法会把在客户端实例化的用户对象添加到webSocketSet里面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
@ServerEndpoint("/websocket/{id}") @Component public class WebSocket { static Log log=LogFactory.get(WebSocket.class); //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 private static int onlineCount = 0; //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。 private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<WebSocket>(); //与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; //接收id private String id=""; /** * 连接建立成功调用的方法*/ @OnOpen public void onOpen(Session session,@PathParam("id") String id) { this.session = session; webSocketSet.add(this); //加入set中 addOnlineCount(); //在线数加1 log.info("有新窗口开始监听:"+id+",当前在线人数为" + getOnlineCount()); this.id=id; // try { // sendMessage("连接成功"); // } catch (IOException e) { // log.error("websocket IO异常"); // } } /** * 连接关闭调用的方法 */ @OnClose public void onClose() { webSocketSet.remove(this); //从set中删除 subOnlineCount(); //在线数减1 log.info("有一连接关闭!当前在线人数为" + getOnlineCount()); } /** * 收到客户端消息后调用的方法 * * @param message 客户端发送过来的消息*/ @OnMessage public void onMessage(String message, Session session) throws IOException { log.info("收到来自窗口"+id+"的信息:"+message); //由于我们需要对聊天信息保存,所以在客户端不直接调用这个方法。先让controller对消息进行处理,直接调用sendInfo就可以 } private void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } /** * 获取当前时间 * * @return */ private String getNowTime() { Date date = new Date(); DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String time = format.format(date); return time; } /** * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("发生错误"); error.printStackTrace(); } /** * 实现服务器主动推送 */ public void sendMessage(TbUserChat userChart) throws IOException { Map<String, Object> map = new HashMap<>(); try { map.put("data", userChart); map.put("status", 1); map.put("message","聊天记录发送成功"); ObjectMapper json = new ObjectMapper(); String message = json.writeValueAsString(map); this.session.getBasicRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } /** * 群发自定义消息 * */ public static void sendInfo(TbUserChat userChart ,@PathParam("id") String id) throws IOException { log.info("推送消息到窗口"+id+",推送内容:"+userChart.getContent()); for (WebSocket item : webSocketSet) { System.out.print(item.id); try { //这里可以设定只推送给这个id的 if(item.id.equals(id)){ item.sendMessage(userChart); } } catch (IOException e) { continue; } } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocket.onlineCount++; } public static synchronized void subOnlineCount() { WebSocket.onlineCount--; } } |
聊天实体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 |
public class TbUserChat { /** * id */ private String id; /** * 发送者id */ private String sendId; /** * 接收者id */ private String receiveId; /** * 内容 */ private String content; /** * 创建时间 */ private String createDate; /** * 是否删除(‘0’未删除:‘1’删除) */ private String delFlag; /** * 文件地址 */ private String fileUrl; /** * 文件名称 */ private String fileName; /** * "0":表示用户聊天记录,“1”群组聊天记录 */ private String type; /** * 发送者姓名 */ private String sendName; /** * 接收者姓名 */ private String receiceName; /** * 是否已读 */ private String isread; /** * 文件类型 */ private String filetype; /** * 用户信息 */ private UserFilter user; public String getFiletype() { return filetype; } public void setFiletype(String filetype) { this.filetype = filetype; } public String getIsread() { return isread; } public void setIsread(String isread) { this.isread = isread; } public UserFilter getUser() { return user; } public void setUser(UserFilter user) { this.user = user; } public String getId() { return id; } public void setId(String id) { this.id = id == null ? null : id.trim(); } public String getSendId() { return sendId; } public void setSendId(String sendId) { this.sendId = sendId == null ? null : sendId.trim(); } public String getReceiveId() { return receiveId; } public void setReceiveId(String receiveId) { this.receiveId = receiveId == null ? null : receiveId.trim(); } public String getContent() { return content; } public void setContent(String content) { this.content = content == null ? null : content.trim(); } public String getCreateDate() { return createDate; } public void setCreateDate(String createDate) { this.createDate = createDate == null ? null : createDate.trim(); } public String getDelFlag() { return delFlag; } public void setDelFlag(String delFlag) { this.delFlag = delFlag == null ? null : delFlag.trim(); } public String getFileUrl() { return fileUrl; } public void setFileUrl(String fileUrl) { this.fileUrl = fileUrl == null ? null : fileUrl.trim(); } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName == null ? null : fileName.trim(); } public String getType() { return type; } public void setType(String type) { this.type = type == null ? null : type.trim(); } public String getSendName() { return sendName; } public void setSendName(String sendName) { this.sendName = sendName == null ? null : sendName.trim(); } public String getReceiceName() { return receiceName; } public void setReceiceName(String receiceName) { this.receiceName = receiceName == null ? null : receiceName.trim(); } } |
客户端实现:关键点就是实例化websocket对象,发送消息调用send()方法就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
<script> // 指定websocket路径 var websocket; var pid; if ('WebSocket' in window) { //客户端实例化websocket对象 websocket = new WebSocket("ws://localhost:8080/websocket/${user.id}");//id表示当前登录的用户 } else if ('MozWebSocket' in window) { websocket = new MozWebSocket("ws://localhost:8080/websocket/${user.id}"); } websocket.onopen = function() { console.log("Socket 已打开"); }; //获得消息事件 websocket.onmessage = function(event) { var object=event.data; var obj3 = JSON.parse(object); //由json字符串转换为json对象 $("#newId").attr("value",obj3.data.user.id); // 接收服务端的实时消息并添加到HTML页面中 $(".msg-cell").append("<div class='msg-cell-info'><div><a href=''><img src='"+obj3.data.user.photo+"'></a></div><dl><dt><a href=''>"+obj3.data.user.username+"</a></dt> <dd>"+obj3.data.content+"</dd></dl></div>"); // 滚动条滚动到最低部 scrollToBottom(); }; //关闭事件 websocket.onclose = function() { console.log("Socket已关闭"); $("#status").html("未连接..."); socket = null; }; //发生了错误事件 websocket.onerror = function() { alert("Socket发生了错误"); //此时可以尝试刷新页面 } //发送消息 function send() { var pid=$("#newId").val(); /* alert(pid); */ var message = document.getElementById('text').value;//要发送的消息内容 if(pid==""||pid==null) { layer.alert("请先选择发送对象!") }else{ if(message==null||message=="") { layer.alert("请输入聊天内容!") }else{ /* websocket.send(message); */ this.talk var type=0; $.ajax({ url: '/userChat/saveUserChat', data:{'sendId':2,'receiveId':pid,'content':message,'type':0}, //表示接收对象的id,type=0表示给个人发送,等于1表示给群组发送消息 async : false, type: "POST", dataType:'json', success:function(data){ }, error : function(data) { alert("出错:" + data.code); } }); var content = $(".time-cell").html(); var photo = $("#photo").html(); var username = $("#username").html(); if(content == null || content.length == 0) { $(".time-cell").append("<p>"+getNowFormatDate()+"</p>"); } $(".msg-cell").append("<div class='msg-cell-info'><div><a href=''>"+photo+"</a></div><dl><dt><a href=''>"+username+"</a></dt> <dd>"+message+"</dd></dl></div>"); scrollToBottom(); $("#myinfo").val(""); } } } |
接收到客户端发来的消息后,判断type值为0的话是用户直接发送,为1的话群组发送信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
@RequestMapping("/saveUserChat") public Map<String, Object> saveUserChat(TbUserChat userChat, HttpSession session) { Map<String, Object> map = new HashMap<>(); TbUser myUser = (TbUser) session.getAttribute("user"); // 将当前用户信息加入聊天记录中 UserFilter userFilter = new UserFilter(); userFilter.setId(myUser.getId()); userFilter.setMail(myUser.getMail()); userFilter.setPhone(myUser.getPhone()); userFilter.setPhoto(myUser.getPhoto()); userFilter.setPosition(myUser.getPosition()); userFilter.setUsername(myUser.getUsername()); userChat.setUser(userFilter); userChat.setSendId(myUser.getId()); userChat.setFiletype("2"); //UserFilter是一个用户实体类保存用户的基本信息 try { if (userChat.getType().equals("0")) { // 用户之间发送 WebSocket.sendInfo(userChat, userChat.getReceiveId()); } else if (userChat.getType().equals("1")) { // 群组发送消息 List<TbUser> userList =groupService.selectUserbyGroupId(userChat.getReceiveId()); for (TbUser user : userList) {// 遍历群组中的用户分别发送 // 排除当前用户 if (!user.getId().equals(myUser.getId())) { WebSocket.sendInfo(userChat, user.getId()); } } } } catch (Exception e){ map.put("status", "0"); map.put("message", "用户聊天记录添加失败"); return map; } //保存聊天记录 TbUserChat saveUserChat = userChatService.saveUserChat(userChat, session); map.put("status", "1"); map.put("message", "用户聊天记录添加成功"); map.put("data", saveUserChat); return map; } |
然后直接调用服务端websocket的sendInfo,如果单对单发送消息,则只调用一次sendInfo方法,群组发的话则遍历调用sendInfo方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/** * 单对单/群发消息实现 * */ public static void sendInfo(TbUserChat userChart ,@PathParam("id") String id) throws IOException { log.info("推送消息到窗口"+id+",推送内容:"+userChart.getContent()); for (WebSocket item : webSocketSet) { try { //这里可以设定只推送给这个id的 if(item.id.equals(id)){ item.sendMessage(userChart); } } catch (IOException e) { continue; } } } |