基于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这种非持久的协议来说。
举个栗子吧,简单来说
1) HTTP的生命周期通过Request来界定,也就是一个Request 对应一个Response,或者多个Request 对应多个Response,
也就是说request对应的response数量是恒定不变的。而且这个response也是被动的,不能主动发起,必须有request才会有response
那么Websocket究竟是啥玩意呢
首先Websocket是基于HTTP协议的,或者说引用了HTTP的协议来完成一小部分的握手
首先Websocket是基于HTTP协议的,或者说引用了HTTP的协议来完成一小部分的握手
简单来说,客服的发起请求到服务端,服务端找到对应的小弟(服务助理),找到好,这个小弟就会一直和老大保持联系,为老大服务
三、Websocket的作用
曾经接触WebSocket之前,我接触过ajax轮询以及long poll ,先来说说这2个概念,因为至今还有一些小项目是这么做的
ajax轮询:
ajax轮询:
原理非常简单,JS控制让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息,有的话就响应给客户端
以此循环获取后端的数据,同时浏览器又不需要刷新
以此循环获取后端的数据,同时浏览器又不需要刷新
简单的例子:OA首页显示流程,每个几秒刷新看看有没有需要处理的新流程出现
long poll:
long poll 其实原理跟 ajax轮询 差不多,都是采用循环的方式,不过采取的手段不太友好,是阻塞模型,客户端发起请求后,如果没响应,就一直不返回Response,直到有响应才返回,返回完之后,客户端再次建立连接,如此循环往复不亦乐乎。。。
long poll:
long poll 其实原理跟 ajax轮询 差不多,都是采用循环的方式,不过采取的手段不太友好,是阻塞模型,客户端发起请求后,如果没响应,就一直不返回Response,直到有响应才返回,返回完之后,客户端再次建立连接,如此循环往复不亦乐乎。。。
从上面这两种方式看出他们都是在不断地建立HTTP连接,然后等待服务器处理,这样显得十分被动
那么缺点也随之而来:
这两种形式非常消耗资源,性能也不不好
好!接下来说说Websocket
Websocket的出现,使得资源不需要像之前那种方式那么浪费
这两种形式非常消耗资源,性能也不不好
好!接下来说说Websocket
Websocket的出现,使得资源不需要像之前那种方式那么浪费
它非常主动,服务端就可以主动推送信息给客户端
所以,只需建立一次HTTP请求,就可以做到源源不断的信息传送了。(就像你在手机上玩ol游戏,一开始建立连接后,你就一直保持在线,除非你断线再连)
所以,只需建立一次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