本示例的目的,就是通过webSocket向客户端(浏览器端)发送消息.
一、什么是WebSocket
WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。
WebSocket通信协议于2011年被 IETF定为标准RFC 6455,并被RFC7936所补充规范。
二、通过Java代码实现从服务端发消息给客户端
三、环境说明
java1.7、apache-tomcat-7.0.78、maven
依赖
1 <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency>四、代码 2 3import javax.websocket.*; 4import javax.websocket.server.PathParam; 5import javax.websocket.server.ServerEndpoint; 6import java.io.IOException; 7import java.util.Map; 8import java.util.concurrent.ConcurrentHashMap; 9import java.util.concurrent.ConcurrentMap; 10 11/** 12 * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端, 13 * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端 14 */ 15@ServerEndpoint("/websocket/{userId}") 16public class NoticeWebSocket { 17 //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 18 private static int onlineCount = 0; 19 20 //concurrent包的线程安全map,用来存放每个客户端对应的NoticeWebSocket对象 21 private static ConcurrentMap<String, NoticeWebSocket> webSocketSet = new ConcurrentHashMap<String, NoticeWebSocket>(); 22 23 //与某个客户端的连接会话,需要通过它来给客户端发送数据 24 private Session session; 25 26 /** 27 * 连接建立成功调用的方法 28 * 29 * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据 30 */ 31 @OnOpen 32 public void onOpen(@PathParam(value = "userId") String userId, Session session) { 33 this.session = session; 34 webSocketSet.put(userId, this); //加入set中 35 addOnlineCount(); //在线数加1 36 System.out.println("有新连接加入!当前在线人数为" + getOnlineCount()); 37 } 38 39 /** 40 * 连接关闭调用的方法 41 */ 42 @OnClose 43 public void onClose(@PathParam(value = "userId") String userId) { 44 webSocketSet.remove(userId); //从set中删除 45 subOnlineCount(); //在线数减1 46 System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount()); 47 } 48 49 /** 50 * 收到客户端消息后调用的方法 51 * 52 * @param message 客户端发送过来的消息 53 * @param session 可选的参数 54 */ 55 @OnMessage 56 public void onMessage(String message, Session session) { 57 System.out.println("来自客户端的消息:" + message); 58 //群发消息 59 for (Map.Entry entry : webSocketSet.entrySet()) { 60 try { 61 NoticeWebSocket item = (NoticeWebSocket) entry.getValue(); 62 item.sendMessage(message); 63 } catch (IOException e) { 64 e.printStackTrace(); 65 continue; 66 } 67 } 68 } 69 70 /** 71 * 发生错误时调用 72 * 73 * @param session 74 * @param error 75 */ 76 @OnError 77 public void onError(Session session, Throwable error) { 78 System.out.println("发生错误"); 79 error.printStackTrace(); 80 } 81 82 /** 83 * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。 84 * 85 * @param message 86 * @throws IOException 87 */ 88 public void sendMessage(String message) throws IOException { 89 this.session.getBasicRemote().sendText(message); 90 //this.session.getAsyncRemote().sendText(message); 91 } 92 93 public static synchronized int getOnlineCount() { 94 return onlineCount; 95 } 96 97 public static synchronized void addOnlineCount() { 98 NoticeWebSocket.onlineCount++; 99 } 100 101 public static synchronized void subOnlineCount() { 102 NoticeWebSocket.onlineCount--; 103 } 104 105 public static ConcurrentMap<String, NoticeWebSocket> getWebSocketSet() { 106 return webSocketSet; 107 } 108 109 public static void setWebSocketSet(ConcurrentMap<String, NoticeWebSocket> webSocketSet) { 110 NoticeWebSocket.webSocketSet = webSocketSet; 111 } 112}
页面Js
1var websocket = null; 2 //判断当前浏览器是否支持WebSocket 3 if ('WebSocket' in window) { 4 websocket = new WebSocket("ws://localhost:8080/websocket/xxx"); 5 } 6 else { 7 alert('当前浏览器 Not support websocket') 8 } 9 10 //连接发生错误的回调方法 11 websocket.onerror = function () { 12 setMessageInnerHTML("WebSocket连接发生错误"); 13 }; 14 15 //连接成功建立的回调方法 16 websocket.onopen = function () { 17 setMessageInnerHTML("WebSocket连接成功"); 18 } 19 20 //接收到消息的回调方法 21 websocket.onmessage = function (event) { 22 setMessageInnerHTML(event.data); 23 } 24 25 //连接关闭的回调方法 26 websocket.onclose = function () { 27 setMessageInnerHTML("WebSocket连接关闭"); 28 } 29 30 //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 31 window.onbeforeunload = function () { 32 closeWebSocket(); 33 } 34 35 //将消息显示在网页上 36 function setMessageInnerHTML(innerHTML) { 37 console.log(innerHTML); 38 } 39 40 //关闭WebSocket连接 41 function closeWebSocket() { 42 websocket.close(); 43 } 44 45 //发送消息 46 function send() { 47 var message = "牛。。"; 48 websocket.send(message); 49 }
测试
我是用了SpringMvc 写了一个Controller
1/** 2 * 发送消息给当前登录人 TEST 3 * 4 * @return 5 */ 6 @RequestMapping(value = "/free/sendMessageTest", method = RequestMethod.GET) 7 @ResponseBody 8 public Map<String, Object> sendMessageTest(String userId) { 9 Map<String, Object> map = new HashMap<String, Object>(); 10 ConcurrentMap<String, NoticeWebSocket> noticeWebSockets = NoticeWebSocket.getWebSocketSet(); 11 try { 12 map.put("status", 0); 13 NoticeWebSocket noticeWebSocket = noticeWebSockets.get(userId); 14 if (null != noticeWebSocket) { 15 noticeWebSocket.sendMessage("牛逼Class UserId=" + userId); 16 } 17 } catch (IOException e) { 18 e.printStackTrace(); 19 map.put("status", -1); 20 } 21 return map; 22 }
说明
websocket = new WebSocket("ws://localhost:8080/websocket/xxx"); xxx 代表一个用户唯一标识,当页面加载这段Js时会和服务端建立连接调用后端onOpen(String userId)方法会把"xxx"传到后台去,然后用map保存,userId 就是key,登录的session 就是value。(如果有多个页面访问就会有多个session),我这里之所有用ConcurrentMap 是因为它是线程安全的。