基于Spring 4.0 的 Web Socket 聊天室/游戏服务端简单架构
2016-03-22 14:33
411 查看
在现在很多业务场景(比如聊天室),又或者是手机端的一些online游戏,都需要做到实时通信,那怎么来进行双向通信呢,总不见得用曾经很破旧的ajax每隔10秒或者每隔20秒来请求吧,我的天呐(
![](http://images2015.cnblogs.com/blog/841496/201604/841496-20160406202540718-1878457117.png)
),这尼玛太坑了
跟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.悄悄话给某个人
效果:
![](http://images2015.cnblogs.com/blog/841496/201603/841496-20160322142517448-2094403273.png)
主要代码:
pom.xml引入必要的库
主要结构
![](http://images2015.cnblogs.com/blog/841496/201603/841496-20160322142825214-436305965.png)
HandshakeInterceptor.java
HomeController.java
WebSocketConfig.java
WebSocketHander.java
Person.java
chat.jsp
有兴趣的朋友可以关注github地址:https://github.com/leechenxiang/maven-spring-websocket-01
![](http://images2015.cnblogs.com/blog/841496/201604/841496-20160406202540718-1878457117.png)
),这尼玛太坑了
跟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.悄悄话给某个人
效果:
![](http://images2015.cnblogs.com/blog/841496/201603/841496-20160322142517448-2094403273.png)
主要代码:
pom.xml引入必要的库
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.lee</groupId> <artifactId>websocket</artifactId> <name>maven-spring-websocket-01</name> <packaging>war</packaging> <version>1.0.0-BUILD-SNAPSHOT</version> <properties> <java.version>1.7</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring.version>4.0.0.RELEASE</spring.version> <junit.version>4.11</junit.version> <!-- Logging --> <logback.version>1.0.13</logback.version> <slf4j.version>1.7.7</slf4j.version> </properties> <dependencies> <!--spring MVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- jstl --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!--spring测试框架 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> <scope>test</scope> </dependency> <!--spring数据库操作库 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> <scope>test</scope> </dependency> <!--spring websocket库 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>${spring.version}</version> </dependency> <!--jackson用于json操作 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.2</version> </dependency> <!-- Logging with SLF4J & LogBack --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> <scope>runtime</scope> </dependency> <!--使用阿里的连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.4</version> </dependency> <!--mysql connector --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.29</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build> </project>
主要结构
![](http://images2015.cnblogs.com/blog/841496/201603/841496-20160322142825214-436305965.png)
HandshakeInterceptor.java
package com.lee.websocket; import java.util.Map; import javax.servlet.http.HttpSession; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.web.socket.WebSocketHandler; public class HandshakeInterceptor implements org.springframework.web.socket.server.HandshakeInterceptor { //进入hander之前的拦截 @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception { if (request instanceof ServletServerHttpRequest) { ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; String clientName = (String)servletRequest.getServletRequest().getParameter("name"); System.out.println(clientName); HttpSession session = servletRequest.getServletRequest().getSession(true); // String userName = "lee"; if (session != null) { //使用userName区分WebSocketHandler,以便定向发送消息 // String clientName = (String) session.getAttribute("WEBSOCKET_USERNAME"); map.put("WEBSOCKET_USERNAME", clientName); } } return true; } @Override public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) { } }
HomeController.java
package com.lee.websocket; import java.text.DateFormat; import java.util.Date; import java.util.Locale; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; /** * Handles requests for the application home page. */ @Controller public class HomeController { private static final Logger logger = LoggerFactory.getLogger(HomeController.class); /** * Simply selects the home view to render by returning its name. */ @RequestMapping(value = "/", method = RequestMethod.GET) public String home(Locale locale, Model model) { logger.info("Welcome home! The client locale is {}.", locale); Date date = new Date(); DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale); String formattedDate = dateFormat.format(date); model.addAttribute("serverTime", formattedDate ); return "home"; } @RequestMapping(value = "/chat", method = RequestMethod.GET) public String chat(Locale locale, Model model) { return "chat"; } }
WebSocketConfig.java
package com.lee.websocket; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; @Configuration @EnableWebSocket//开启websocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new WebSocketHander(),"/echo").addInterceptors(new HandshakeInterceptor()); //支持websocket 的访问链接 registry.addHandler(new WebSocketHander(),"/sockjs/echo").addInterceptors(new HandshakeInterceptor()).withSockJS(); //不支持websocket的访问链接 } }
WebSocketHander.java
package com.lee.websocket; import java.io.IOException; import java.util.ArrayList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; public class WebSocketHander implements WebSocketHandler { private static final Logger logger = LoggerFactory.getLogger(WebSocketHander.class); private static final ArrayList<WebSocketSession> users = new ArrayList<>(); //初次链接成功执行 @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { logger.debug("链接成功......"); users.add(session); String userName = (String) session.getHandshakeAttributes().get("WEBSOCKET_USERNAME"); if(userName!= null){ session.sendMessage(new TextMessage("欢迎来到Nathan的聊天室,我们开始聊天吧!~")); } } //接受消息处理消息 @Override public void handleMessage(WebSocketSession session, WebSocketMessage<?> webSocketMessage) throws Exception { String clientName = (String) session.getHandshakeAttributes().get("WEBSOCKET_USERNAME"); clientName = "<a onclick='changeChater(this)'>" + clientName + "</a>"; String msg = webSocketMessage.getPayload().toString(); String charter = ""; String msgs[] = msg.split("\\|"); if (msgs.length > 1) { msg = msgs[1]; charter = msgs[0]; sendMessageToUser(charter, new TextMessage(clientName + " 悄悄地对你说 :" + msg)); } else { sendMessageToUsers(new TextMessage(clientName + " 说:" + msg)); } } @Override public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception { if(webSocketSession.isOpen()){ webSocketSession.close(); } logger.debug("链接出错,关闭链接......"); users.remove(webSocketSession); } @Override public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception { logger.debug("链接关闭......" + closeStatus.toString()); users.remove(webSocketSession); } @Override public boolean supportsPartialMessages() { return false; } /** * 给所有在线用户发送消息 * * @param message */ public void sendMessageToUsers(TextMessage message) { for (WebSocketSession user : users) { try { if (user.isOpen()) { user.sendMessage(message); } } catch (IOException e) { e.printStackTrace(); } } } /** * 给某个用户发送消息 * * @param userName * @param message */ public void sendMessageToUser(String userName, TextMessage message) { for (WebSocketSession user : users) { if (user.getHandshakeAttributes().get("WEBSOCKET_USERNAME").equals(userName)) { try { if (user.isOpen()) { user.sendMessage(message); } } catch (IOException e) { e.printStackTrace(); } break; } } } }
Person.java
package com.lee.websocket.entity; public class Person { private int age; private String name; private String sex; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } }
chat.jsp
<%@ page contentType="text/html; charset=utf-8" language="java" %> <html> <head lang="en"> <meta charset="UTF-8"> <script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script> <!-- 新 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css"> <!-- 可选的Bootstrap主题文件(一般不用引入) --> <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap-theme.min.css"> <!-- jQuery文件。务必在bootstrap.min.js 之前引入 --> <script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script> <!--<script type="text/javascript" src="js/jquery-1.7.2.js"></script>--> <!-- 最新的 Bootstrap 核心 JavaScript 文件 --> <script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> <title>webSocket测试</title> <script type="text/javascript"> var chater; $(function(){ var websocket; function connectServer() { var clientName = $("#client_name").val(); if ("WebSocket" in window) { websocket = new WebSocket("ws://127.0.0.1:8080/websocket/echo?name=" + clientName); } else if ("MozWebSocket" in window) { alert("MozWebSocket"); websocket = new MozWebSocket("ws://echo"); } else { alert("SockJS"); websocket = new SockJS("http://127.0.0.1:8080/websocket/sockjs/echo"); } } // websocket.onopen = function (evnt) { // $("#tou").html("链接服务器成功!") // }; // websocket.onmessage = function (evnt) { // $("#msg").html($("#msg").html() + "<br/>" + evnt.data); // }; // websocket.onerror = function (evnt) { // }; // websocket.onclose = function (evnt) { // $("#tou").html("与服务器断开了链接!") // } $("#conncet_server").bind("click", function() { connectServer(); websocket.onopen = function (evnt) { $("#tou").html("链接服务器成功!") }; websocket.onmessage = function (evnt) { $("#msg").html($("#msg").html() + "<br/>" + evnt.data); }; websocket.onerror = function (evnt) { }; websocket.onclose = function (evnt) { $("#tou").html("与服务器断开了链接!") } }); $("#send").bind("click", function() { send(); }); function send(){ if (websocket != null) { var message = document.getElementById("message").value; if ($.trim(chater) != "") { message = chater + "|" + message; } websocket.send(message); } else { alert("未与服务器链接."); } } }); function changeChater(e) { chater = $(e).html(); alert("您将和" + chater + "进行聊天..."); } </script> </head> <body> <div class="page-header" id="tou">webSocket及时聊天Demo程序</div> <div class="well" id="msg"></div> <div class="col-lg"> <div class="input-group"> <input type="text" class="form-control" placeholder="请输入用户名..." id="client_name"> <span class="input-group-btn"> <button class="btn btn-default" type="button" id="conncet_server">连接服务器</button> </span> </div> </div> <br/> <div class="col-lg"> <div class="input-group"> <input type="text" class="form-control" placeholder="发送信息..." id="message"> <span class="input-group-btn"> <button class="btn btn-default" type="button" id="send">发送</button> </span> </div> </div> </body> </html>
有兴趣的朋友可以关注github地址:https://github.com/leechenxiang/maven-spring-websocket-01
相关文章推荐
- 使用Spring的注解方式实现AOP
- Eclipse Mars.2 Release (4.5.2)中构建Maven web项目出错
- java建造者模式
- HashSet源码剖析
- Spring-mvc静态资源无法加载
- (转) java.lang.ClassNotFoundException: com.fasterxml.jackson.core.JsonProcessingException
- 拦截器
- JAVA8 十大新特性详解
- 基于 OAuth 安全协议的 Java 应用编程1
- Java之美[从菜鸟到高手演练]之JDK动态代理的实现及原理
- Spring动态创建bean切换数据源
- JAVA中重载与多态
- myeclipse搭建SSH框架
- java 知识点
- Java中类继承的初始化顺序
- JAVA类的继承初探
- java基础之transient的使用
- Java常用排序算法及性能测试集合
- java基础——复制文件
- comet4j java服务端推送消息到web页面实例