在現在很多業務場景(比如聊天室),又或者是手機端的一些online游戲,都需要做到實時通信,那怎麼來進行雙向通信呢,總不見得用曾經很破舊的ajax每隔10秒或者每隔20秒來請求吧,我的天吶,這尼瑪太坑了
跟webservice來相比,Web Socket可以做到保持長連接,或者說強連接,一直握手存在兩端可以互相發送消息互相收到消息,而webservice是一次性的,你要我響應就必須要請求我一次
注:浏覽器需要使用高版本的chrome或者Firefox,Tomcat使用8
先借用一下知乎某大神的原文:
作者:Ovear
鏈接:http://www.zhihu.com/question/20215561/answer/40316953
來源:知乎
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
熟悉HTTP的童鞋可能發現了,這段類似HTTP協議的握手請求中,多了幾個東西。Upgrade: websocket
Connection: Upgrade
這個就是Websocket的核心了,告訴Apache、Nginx等服務器:注意啦,窩發起的是Websocket協議,快點幫我找到對應的助理處理~不是那個老土的HTTP。Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
首先,Sec-WebSocket-Key 是一個Base64 encode的值,這個是浏覽器隨機生成的,告訴服務器:泥煤,不要忽悠窩,我要驗證尼是不是真的是Websocket助理。HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
這裡開始就是HTTP最後負責的區域了,告訴客戶,我已經成功切換協議啦~Upgrade: websocket
Connection: Upgrade
依然是固定的,告訴客戶端即將升級的是Websocket協議,而不是mozillasocket,lurnarsocket或者shitsocket。spring websocket chating room
使用spring websocket實現聊天室基本功能
1.群發消息給所有人
2.悄悄話給某個人
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4 <modelVersion>4.0.0</modelVersion> 5 <groupId>com.lee</groupId> 6 <artifactId>websocket</artifactId> 7 <name>maven-spring-websocket-01</name> 8 <packaging>war</packaging> 9 <version>1.0.0-BUILD-SNAPSHOT</version> 10 11 <properties> 12 13 <java.version>1.7</java.version> 14 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 15 <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> 16 17 <spring.version>4.0.0.RELEASE</spring.version> 18 19 <junit.version>4.11</junit.version> 20 21 <!-- Logging --> 22 <logback.version>1.0.13</logback.version> 23 <slf4j.version>1.7.7</slf4j.version> 24 </properties> 25 26 <dependencies> 27 <!--spring MVC --> 28 <dependency> 29 <groupId>org.springframework</groupId> 30 <artifactId>spring-core</artifactId> 31 <version>${spring.version}</version> 32 </dependency> 33 34 <dependency> 35 <groupId>org.springframework</groupId> 36 <artifactId>spring-web</artifactId> 37 <version>${spring.version}</version> 38 </dependency> 39 40 <dependency> 41 <groupId>org.springframework</groupId> 42 <artifactId>spring-webmvc</artifactId> 43 <version>${spring.version}</version> 44 </dependency> 45 46 <!-- jstl --> 47 <dependency> 48 <groupId>jstl</groupId> 49 <artifactId>jstl</artifactId> 50 <version>1.2</version> 51 </dependency> 52 53 <!--spring測試框架 --> 54 <dependency> 55 <groupId>org.springframework</groupId> 56 <artifactId>spring-test</artifactId> 57 <version>${spring.version}</version> 58 <scope>test</scope> 59 </dependency> 60 61 <!--spring數據庫操作庫 --> 62 <dependency> 63 <groupId>org.springframework</groupId> 64 <artifactId>spring-jdbc</artifactId> 65 <version>${spring.version}</version> 66 </dependency> 67 68 <dependency> 69 <groupId>junit</groupId> 70 <artifactId>junit</artifactId> 71 <version>4.8.2</version> 72 <scope>test</scope> 73 </dependency> 74 75 <!--spring websocket庫 --> 76 <dependency> 77 <groupId>org.springframework</groupId> 78 <artifactId>spring-websocket</artifactId> 79 <version>${spring.version}</version> 80 </dependency> 81 <dependency> 82 <groupId>org.springframework</groupId> 83 <artifactId>spring-messaging</artifactId> 84 <version>${spring.version}</version> 85 </dependency> 86 87 <!--jackson用於json操作 --> 88 <dependency> 89 <groupId>com.fasterxml.jackson.core</groupId> 90 <artifactId>jackson-databind</artifactId> 91 <version>2.3.0</version> 92 </dependency> 93 94 <dependency> 95 <groupId>commons-fileupload</groupId> 96 <artifactId>commons-fileupload</artifactId> 97 <version>1.2.2</version> 98 </dependency> 99 <dependency> 100 <groupId>commons-io</groupId> 101 <artifactId>commons-io</artifactId> 102 <version>2.2</version> 103 </dependency> 104 105 <!-- Logging with SLF4J & LogBack --> 106 <dependency> 107 <groupId>org.slf4j</groupId> 108 <artifactId>slf4j-api</artifactId> 109 <version>${slf4j.version}</version> 110 <scope>compile</scope> 111 </dependency> 112 <dependency> 113 <groupId>ch.qos.logback</groupId> 114 <artifactId>logback-classic</artifactId> 115 <version>${logback.version}</version> 116 <scope>runtime</scope> 117 </dependency> 118 119 <!--使用阿裡的連接池 --> 120 <dependency> 121 <groupId>com.alibaba</groupId> 122 <artifactId>druid</artifactId> 123 <version>1.0.4</version> 124 </dependency> 125 126 <!--mysql connector --> 127 <dependency> 128 <groupId>mysql</groupId> 129 <artifactId>mysql-connector-java</artifactId> 130 <version>5.1.29</version> 131 </dependency> 132 133 </dependencies> 134 135 <build> 136 <plugins> 137 <plugin> 138 <groupId>org.apache.maven.plugins</groupId> 139 <artifactId>maven-compiler-plugin</artifactId> 140 <configuration> 141 <source>1.7</source> 142 <target>1.7</target> 143 </configuration> 144 </plugin> 145 </plugins> 146 </build> 147 148 </project>
主要結構
HandshakeInterceptor.java
1 package com.lee.websocket; 2 3 import java.util.Map; 4 5 import javax.servlet.http.HttpSession; 6 7 import org.springframework.http.server.ServerHttpRequest; 8 import org.springframework.http.server.ServerHttpResponse; 9 import org.springframework.http.server.ServletServerHttpRequest; 10 import org.springframework.web.socket.WebSocketHandler; 11 12 public class HandshakeInterceptor implements org.springframework.web.socket.server.HandshakeInterceptor { 13 14 //進入hander之前的攔截 15 @Override 16 public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception { 17 if (request instanceof ServletServerHttpRequest) { 18 ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; 19 20 String clientName = (String)servletRequest.getServletRequest().getParameter("name"); 21 System.out.println(clientName); 22 23 HttpSession session = servletRequest.getServletRequest().getSession(true); 24 // String userName = "lee"; 25 if (session != null) { 26 //使用userName區分WebSocketHandler,以便定向發送消息 27 // String clientName = (String) session.getAttribute("WEBSOCKET_USERNAME"); 28 map.put("WEBSOCKET_USERNAME", clientName); 29 } 30 } 31 return true; 32 } 33 34 @Override 35 public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) { 36 37 } 38 39 }
HomeController.java
1 package com.lee.websocket; 2 3 import java.text.DateFormat; 4 import java.util.Date; 5 import java.util.Locale; 6 7 import org.slf4j.Logger; 8 import org.slf4j.LoggerFactory; 9 import org.springframework.stereotype.Controller; 10 import org.springframework.ui.Model; 11 import org.springframework.web.bind.annotation.RequestMapping; 12 import org.springframework.web.bind.annotation.RequestMethod; 13 14 /** 15 * Handles requests for the application home page. 16 */ 17 @Controller 18 public class HomeController { 19 20 private static final Logger logger = LoggerFactory.getLogger(HomeController.class); 21 22 /** 23 * Simply selects the home view to render by returning its name. 24 */ 25 @RequestMapping(value = "/", method = RequestMethod.GET) 26 public String home(Locale locale, Model model) { 27 logger.info("Welcome home! The client locale is {}.", locale); 28 29 Date date = new Date(); 30 DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale); 31 32 String formattedDate = dateFormat.format(date); 33 34 model.addAttribute("serverTime", formattedDate ); 35 36 return "home"; 37 } 38 39 @RequestMapping(value = "/chat", method = RequestMethod.GET) 40 public String chat(Locale locale, Model model) { 41 return "chat"; 42 } 43 44 }
WebSocketConfig.java
1 package com.lee.websocket; 2 3 import org.springframework.context.annotation.Configuration; 4 import org.springframework.web.socket.config.annotation.EnableWebSocket; 5 import org.springframework.web.socket.config.annotation.WebSocketConfigurer; 6 import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; 7 8 @Configuration 9 @EnableWebSocket//開啟websocket 10 public class WebSocketConfig implements WebSocketConfigurer { 11 @Override 12 public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { 13 registry.addHandler(new WebSocketHander(),"/echo").addInterceptors(new HandshakeInterceptor()); //支持websocket 的訪問鏈接 14 registry.addHandler(new WebSocketHander(),"/sockjs/echo").addInterceptors(new HandshakeInterceptor()).withSockJS(); //不支持websocket的訪問鏈接 15 } 16 }
WebSocketHander.java
1 package com.lee.websocket; 2 3 import java.io.IOException; 4 import java.util.ArrayList; 5 6 import org.slf4j.Logger; 7 import org.slf4j.LoggerFactory; 8 import org.springframework.web.socket.CloseStatus; 9 import org.springframework.web.socket.TextMessage; 10 import org.springframework.web.socket.WebSocketHandler; 11 import org.springframework.web.socket.WebSocketMessage; 12 import org.springframework.web.socket.WebSocketSession; 13 14 public class WebSocketHander implements WebSocketHandler { 15 private static final Logger logger = LoggerFactory.getLogger(WebSocketHander.class); 16 17 private static final ArrayList<WebSocketSession> users = new ArrayList<>(); 18 19 //初次鏈接成功執行 20 @Override 21 public void afterConnectionEstablished(WebSocketSession session) throws Exception { 22 logger.debug("鏈接成功......"); 23 users.add(session); 24 String userName = (String) session.getHandshakeAttributes().get("WEBSOCKET_USERNAME"); 25 if(userName!= null){ 26 session.sendMessage(new TextMessage("歡迎來到Nathan的聊天室,我們開始聊天吧!~")); 27 } 28 } 29 30 //接受消息處理消息 31 @Override 32 public void handleMessage(WebSocketSession session, WebSocketMessage<?> webSocketMessage) throws Exception { 33 String clientName = (String) session.getHandshakeAttributes().get("WEBSOCKET_USERNAME"); 34 35 clientName = "<a onclick='changeChater(this)'>" + clientName + "</a>"; 36 37 String msg = webSocketMessage.getPayload().toString(); 38 String charter = ""; 39 40 String msgs[] = msg.split("\\|"); 41 if (msgs.length > 1) { 42 msg = msgs[1]; 43 charter = msgs[0]; 44 sendMessageToUser(charter, new TextMessage(clientName + " 悄悄地對你說 :" + msg)); 45 } else { 46 sendMessageToUsers(new TextMessage(clientName + " 說:" + msg)); 47 } 48 49 } 50 51 @Override 52 public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception { 53 if(webSocketSession.isOpen()){ 54 webSocketSession.close(); 55 } 56 logger.debug("鏈接出錯,關閉鏈接......"); 57 users.remove(webSocketSession); 58 } 59 60 @Override 61 public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception { 62 logger.debug("鏈接關閉......" + closeStatus.toString()); 63 users.remove(webSocketSession); 64 } 65 66 @Override 67 public boolean supportsPartialMessages() { 68 return false; 69 } 70 71 /** 72 * 給所有在線用戶發送消息 73 * 74 * @param message 75 */ 76 public void sendMessageToUsers(TextMessage message) { 77 for (WebSocketSession user : users) { 78 try { 79 if (user.isOpen()) { 80 user.sendMessage(message); 81 } 82 } catch (IOException e) { 83 e.printStackTrace(); 84 } 85 } 86 } 87 88 /** 89 * 給某個用戶發送消息 90 * 91 * @param userName 92 * @param message 93 */ 94 public void sendMessageToUser(String userName, TextMessage message) { 95 for (WebSocketSession user : users) { 96 if (user.getHandshakeAttributes().get("WEBSOCKET_USERNAME").equals(userName)) { 97 try { 98 if (user.isOpen()) { 99 user.sendMessage(message); 100 } 101 } catch (IOException e) { 102 e.printStackTrace(); 103 } 104 break; 105 } 106 } 107 } 108 }
Person.java
1 package com.lee.websocket.entity; 2 3 public class Person { 4 5 private int age; 6 private String name; 7 private String sex; 8 9 public int getAge() { 10 return age; 11 } 12 public void setAge(int age) { 13 this.age = age; 14 } 15 public String getName() { 16 return name; 17 } 18 public void setName(String name) { 19 this.name = name; 20 } 21 public String getSex() { 22 return sex; 23 } 24 public void setSex(String sex) { 25 this.sex = sex; 26 } 27 28 }
chat.jsp
1 <%@ page contentType="text/html; charset=utf-8" language="java" %> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script> 6 <!-- 新 Bootstrap 核心 CSS 文件 --> 7 <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css"> 8 <!-- 可選的Bootstrap主題文件(一般不用引入) --> 9 <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap-theme.min.css"> 10 <!-- jQuery文件。務必在bootstrap.min.js 之前引入 --> 11 <script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script> 12 <!--<script type="text/javascript" src="js/jquery-1.7.2.js"></script>--> 13 <!-- 最新的 Bootstrap 核心 JavaScript 文件 --> 14 <script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> 15 <title>webSocket測試</title> 16 <script type="text/javascript"> 17 var chater; 18 19 $(function(){ 20 21 var websocket; 22 function connectServer() { 23 var clientName = $("#client_name").val(); 24 if ("WebSocket" in window) { 25 websocket = new WebSocket("ws://127.0.0.1:8080/websocket/echo?name=" + clientName); 26 } else if ("MozWebSocket" in window) { 27 alert("MozWebSocket"); 28 websocket = new MozWebSocket("ws://echo"); 29 } else { 30 alert("SockJS"); 31 websocket = new SockJS("http://127.0.0.1:8080/websocket/sockjs/echo"); 32 } 33 } 34 35 // websocket.onopen = function (evnt) { 36 // $("#tou").html("鏈接服務器成功!") 37 // }; 38 // websocket.onmessage = function (evnt) { 39 // $("#msg").html($("#msg").html() + "<br/>" + evnt.data); 40 // }; 41 // websocket.onerror = function (evnt) { 42 // }; 43 // websocket.onclose = function (evnt) { 44 // $("#tou").html("與服務器斷開了鏈接!") 45 // } 46 47 $("#conncet_server").bind("click", function() { 48 connectServer(); 49 50 websocket.onopen = function (evnt) { 51 $("#tou").html("鏈接服務器成功!") 52 }; 53 websocket.onmessage = function (evnt) { 54 $("#msg").html($("#msg").html() + "<br/>" + evnt.data); 55 }; 56 websocket.onerror = function (evnt) { 57 }; 58 websocket.onclose = function (evnt) { 59 $("#tou").html("與服務器斷開了鏈接!") 60 } 61 }); 62 63 $("#send").bind("click", function() { 64 send(); 65 }); 66 67 function send(){ 68 if (websocket != null) { 69 var message = document.getElementById("message").value; 70 71 if ($.trim(chater) != "") { 72 message = chater + "|" + message; 73 } 74 75 websocket.send(message); 76 } else { 77 alert("未與服務器鏈接."); 78 } 79 } 80 }); 81 82 function changeChater(e) { 83 chater = $(e).html(); 84 alert("您將和" + chater + "進行聊天..."); 85 } 86 </script> 87 88 </head> 89 <body> 90 91 <div class="page-header" id="tou">webSocket及時聊天Demo程序</div> 92 <div class="well" id="msg"></div> 93 <div class="col-lg"> 94 <div class="input-group"> 95 <input type="text" class="form-control" placeholder="請輸入用戶名..." id="client_name"> 96 <span class="input-group-btn"> 97 <button class="btn btn-default" type="button" id="conncet_server">連接服務器</button> 98 </span> 99 </div> 100 </div> 101 102 <br/> 103 104 <div class="col-lg"> 105 <div class="input-group"> 106 <input type="text" class="form-control" placeholder="發送信息..." id="message"> 107 <span class="input-group-btn"> 108 <button class="btn btn-default" type="button" id="send">發送</button> 109 </span> 110 </div> 111 </div> 112 </body> 113 114 </html>
有興趣的朋友可以關注github地址:https://github.com/leechenxiang/maven-spring-websocket-01