zoukankan      html  css  js  c++  java
  • 基于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
  • 相关阅读:
    MPLS 知识要点1
    ISIS的SSN和SRM标志
    对比ISIS和OSPF
    ISIS帧中继实验
    ISIS 认证实验
    ISIS数据库同步
    ISIS Lab 路由泄露
    ISIS Lab 重分布直连
    32、端口有效范围是多少到多少?
    33、为何需要把 TCP/IP 协议栈分成 5 层(或7层)?开放式回答。
  • 原文地址:https://www.cnblogs.com/leechenxiang/p/5306372.html
Copyright © 2011-2022 走看看