在現在很多業務場景(比如聊天室),又或者是手機端的一些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。
你TMD又BBB了這麼久,那到底Websocket有什麼鬼用,http long poll,或者ajax輪詢不都可以實現實時信息傳遞麼。
三、Websocket的作用
spring websocket chating room
使用spring websocket實現聊天室基本功能
1.群發消息給所有人
2.悄悄話給某個人
主要代碼:
pom.xml引入必要的庫
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