基於Spring 4.0 的 Web Socket 聊天室/游戲服務端簡單架構


在現在很多業務場景(比如聊天室),又或者是手機端的一些online游戲,都需要做到實時通信,那怎么來進行雙向通信呢,總不見得用曾經很破舊的ajax每隔10秒或者每隔20秒來請求吧,我的天吶(),這尼瑪太坑了

跟webservice來相比,Web Socket可以做到保持長連接,或者說強連接,一直握手存在兩端可以互相發送消息互相收到消息,而webservice是一次性的,你要我響應就必須要請求我一次(黃蓋:“請鞭撻我吧!”)

注:瀏覽器需要使用高版本的chrome或者Firefox,Tomcat使用8

先來了解一下基本概念

一、WebSocket是HTML5出的,是一種協議,也就是說原版的HTTP協議沒有變化的,又或者說這兩者壓根就是不一樣的東西,HTTP本身就不支持強連接
二、Websocket是什么樣的協議,具體有什么優點
首先,Websocket是一個 持久化的協議,相對於HTTP這種 非持久的協議來說。
舉個栗子吧,簡單來說
1) HTTP的生命周期通過Request來界定,也就是一個Request 對應一個Response,或者多個Request 對應多個Response,
也就是說request對應的response數量是恆定不變的。而且這個response也是 被動的,不能主動發起,必須有request才會有response
 
那么Websocket究竟是啥玩意呢
首先Websocket是基於HTTP協議的,或者說引用了HTTP的協議來完成一小部分的握手
簡單來說,客服的發起請求到服務端,服務端找到對應的小弟(服務助理),找到好,這個小弟就會一直和老大保持聯系,為老大服務

三、Websocket的作用
曾經接觸WebSocket之前,我接觸過ajax輪詢以及long poll ,先來說說這2個概念,因為至今還有一些小項目是這么做的
ajax輪詢:
原理非常簡單,JS控制讓瀏覽器隔個幾秒就發送一次請求,詢問服務器是否有新信息,有的話就響應給客戶端
以此循環獲取后端的數據,同時瀏覽器又不需要刷新
簡單的例子:OA首頁顯示流程,每個幾秒刷新看看有沒有需要處理的新流程出現

long poll:
long poll 其實原理跟 ajax輪詢 差不多,都是采用循環的方式,不過采取的手段不太友好,是阻塞模型,客戶端發起請求后,如果沒響應,就一直不返回Response,直到有響應才返回,返回完之后,客戶端再次建立連接,如此循環往復不亦樂乎。。。

從上面這兩種方式看出他們都是在不斷地建立HTTP連接,然后等待服務器處理,這樣顯得十分被動
 
那么缺點也隨之而來:
這兩種形式非常消耗資源,性能也不不好

好!接下來說說Websocket
Websocket的出現,使得資源不需要像之前那種方式那么浪費
它非常主動,服務端就可以主動推送信息給客戶端
所以,只需建立一次HTTP請求,就可以做到源源不斷的信息傳送了。(就像你在手機上玩ol游戲,一開始建立連接后,你就一直保持在線,除非你斷線再連)


下面貼出我的代碼片段以及github地址
功能點:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM