Websocket --(2)实现
首先声明,本篇博文参考文章
https://blog.csdn.net/jack_eusong/article/details/79064081
主要在于理解和自己动手搭建环境,自己搭建的过程中会发生很多意想不到的错误。
1.环境
Eclipse Mars + jdk1.7 + tomcat8
2.使用的jar包,spring 的核心jar包就不写了,只写和websocket相关的。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>4.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>4.0.2.RELEASE</version> </dependency> <dependency> <groupId>javax.websocket</groupId> <artifactId>javax.websocket-api</artifactId> <version>1.1</version> </dependency>
其实我感觉只用 javax.websocket-api 这一个包就够了,可能是我本地环境有问题,如果取消前面两个包Tomcat 无法启动,所以就只能留着了
搭建完的项目结构如下图
主要的代码注意一个类,通过@ServerEndpoint注解将类声明为服务端
package com.lzl.ws; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; 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 org.apache.log4j.Logger; import org.springframework.web.socket.server.standard.SpringConfigurator; @ServerEndpoint(value="/chat/{userId}",configurator = SpringConfigurator.class) public class websocketchat{ public static Logger logger = Logger.getLogger(websocketchat.class); //在线人数 public static int onlineCount = 0; //记录每个用户下多个终端的连接 private static Map<String, Set<websocketchat>> userSocket = new HashMap<>(); //需要session来对用户发送数据, 获取连接特征userId private Session session; private String userId; @OnOpen public void onOpen(@PathParam("userId") String userId,Session session) throws IOException{ this.session = session; this.userId = userId; onlineCount++; //根据该用户当前是否已经在别的终端登录进行添加操作 if (userSocket.containsKey(this.userId)) { logger.info("当前用户id:{"+this.userId+"}已有其他终端登录"); userSocket.get(this.userId).add(this); //增加该用户set中的连接实例 }else { logger.info("当前用户id:{"+this.userId+"}第一个终端登录"); Set<websocketchat> addUserSet = new HashSet<>(); addUserSet.add(this); userSocket.put(this.userId, addUserSet); } logger.info("用户{"+userId+"}登录的终端个数是为{"+userSocket.get(this.userId).size()+"}"); logger.info("当前在线用户数为:{"+userSocket.size()+"},所有终端个数为:{"+onlineCount+"}"); } @OnClose public void onClose(){ //移除当前用户终端登录的websocket信息,如果该用户的所有终端都下线了,则删除该用户的记录 if (userSocket.get(this.userId).size() == 0) { userSocket.remove(this.userId); }else{ userSocket.get(this.userId).remove(this); } logger.info("用户{"+this.userId+"}登录的终端个数是为{"+userSocket.get(this.userId).size()+"}"); logger.info("当前在线用户数为:{"+userSocket.size()+"},所有终端个数为:{"+onlineCount+"}"); } /** * @Title: onMessage * @Description: 收到消息后的操作 * @param @param message 收到的消息 * @param @param session 该连接的session属性 */ @OnMessage public void onMessage(String message, Session session) { logger.info("收到来自用户id为:{"+this.userId+"}的消息:{"+message+"}"); if(session ==null) logger.info("session null"); //测试向客户端发送消息发送 logger.info("开始将消息以广播形式发送至所有终端......."); sendMessageToUser( this.userId+"说:"+message); logger.info("广播结束......."); } /** * @Title: onError * @Description: 连接发生错误时候的操作 * @param @param session 该连接的session * @param @param error 发生的错误 */ @OnError public void onError(Session session, Throwable error){ logger.info("用户id为:{"+this.userId+"}的连接发送错误"); error.printStackTrace(); } /** * @Title: sendMessageToUser * @Description: 发送消息给用户下的所有终端 * @param @param userId 用户id * @param @param message 发送的消息 * @param @return 发送成功返回true,反则返回false */ public Boolean sendMessageToUser(String message){ for(Entry<String, Set<websocketchat>> entry:userSocket.entrySet()){ logger.info(" 给用户id为:{"+entry.getKey()+"}的所有终端发送消息:{"+message+"}"); for(websocketchat ws:entry.getValue()){ try { ws.session.getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); logger.info(" 给用户id为:{"+userId+"}发送消息失败"); return false; } } } return true; } }
前端界面
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script> <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css"> <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap-theme.min.css"> <script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script> <script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> <script type="text/javascript"> $(function() { var websocket; if('WebSocket' in window) { console.log("此浏览器支持websocket"); websocket = new WebSocket("ws://127.0.0.1:8080/websocket/chat/tom"); } else if('MozWebSocket' in window) { alert("此浏览器只支持MozWebSocket"); } else { alert("此浏览器只支持SockJS"); } websocket.onopen = function(evnt) { $("#tou").html("链接服务器成功!") }; websocket.onmessage = function(evnt) { $("#msg").html($("#msg").html() + "<br/>" + evnt.data); }; websocket.onerror = function(evnt) {}; websocket.onclose = function(evnt) { $("#tou").html("与服务器断开了链接!") } $('#send').click(function(){ send(); }); function send() { if(websocket != null) { var message = document.getElementById('message').value; websocket.send(message); } else { alert('未与服务器链接.'); } } }); </script> <title>chat</title> </head> <body> <div > <div class="page-header" id="tou" style = "text-align:center"> webSocket多终端聊天测试 </div> <div class="well" id="msg" style = "width:800px;margin:0 auto"></div> <div class="col-lg"> <div class="input-group" style = "width:800px;margin:0 auto"> <input type="text" class="form-control" placeholder="发送信息..." id="message"> <span class="input-group-btn"> <button class="btn btn-default" type="button" id="send" >发送</button> </span> </div> </div> </div> </body> </html>
在参考文章的基础上将发送消息改为群发形式,即一个人发送所有在线终端均可接收。chat.jsp,chat2.jsp,chat3.jsp用来模拟三个用户的登录。
websocket = new WebSocket("ws://127.0.0.1:8080/websocket/chat/tom") 中websocket是我的工程名字,chat是服务端路径,tom是服务端参数,定义为用户ID。其实只用写一个界面即可,通过其他方式动态获取用户ID。这里为了方便测试,后续会增加一个登陆功能,然后将用户ID放到session中,获取用户ID后创建websocket对象,类似一个简易的聊天室功能。chat2和chat3 和chat 一模一样,只是更改了路径中的用户ID。这三个用户ID分别是tom jone jack,实现效果如下图。