基于Spring 4.0 的 Web Socket 聊天室/游戏服务端简单架构
在现在很多业务场景(比如聊天室),又或者是手机端的一些online游戏,都需要做到实时通信,那怎么来进行双向通信呢,总不见得用曾经很破旧的ajax每隔10秒或者每隔20秒来请求吧,我的天呐(),这尼玛太坑了
跟webservice来相比,Web Socket可以做到保持长连接,或者说强连接,一直握手存在两端可以互相发送消息互相收到消息,而webservice是一次性的,你要我响应就必须要请求我一次(黄盖:“请鞭挞我吧!”)
注:浏览器需要使用高版本的chrome或者Firefox,Tomcat使用8
先来了解一下基本概念
二、Websocket是什么样的协议,具体有什么优点
首先,Websocket是一个持久化的协议,相对于HTTP这种非持久的协议来说。
举个栗子吧,简单来说
1) HTTP的生命周期通过Request来界定,也就是一个Request 对应一个Response,或者多个Request 对应多个Response,
也就是说request对应的response数量是恒定不变的。而且这个response也是被动的,不能主动发起,必须有request才会有response
首先Websocket是基于HTTP协议的,或者说引用了HTTP的协议来完成一小部分的握手
ajax轮询:
以此循环获取后端的数据,同时浏览器又不需要刷新
long poll:
long poll 其实原理跟 ajax轮询 差不多,都是采用循环的方式,不过采取的手段不太友好,是阻塞模型,客户端发起请求后,如果没响应,就一直不返回Response,直到有响应才返回,返回完之后,客户端再次建立连接,如此循环往复不亦乐乎。。。
这两种形式非常消耗资源,性能也不不好
好!接下来说说Websocket
Websocket的出现,使得资源不需要像之前那种方式那么浪费
所以,只需建立一次HTTP请求,就可以做到源源不断的信息传送了。(就像你在手机上玩ol游戏,一开始建立连接后,你就一直保持在线,除非你断线再连)
spring websocket chating room
使用spring websocket实现聊天室基本功能
1.群发消息给所有人
2.悄悄话给某个人

- <?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>
主要结构
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;
- }
- }
- }
- }
- 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>
基于Spring 4.0 的 Web Socket 聊天室/游戏服务端简单架构的更多相关文章
- socket聊天室(服务端)(多线程)(TCP)
#include<string.h> #include<signal.h> #include<stdio.h> #include<sys/socket.h&g ...
- 重构 JAVA 聊天室 —— CS 模式的简单架构实现
前言 自从开始弄起数据挖掘之后,已经很久没写过技术类的博客了,最近学校 JAVA 课设要求实现一个聊天室,想想去年自己已经写了一个了,但是有些要求到的功能我也没实现,但看着原有的代码想了想加功能好像有 ...
- JAVA-Socket通信 打造属于自己的聊天室(服务端)
我们每天都在使用着微信.QQ等聊天软件,但不知你是否有想过这些聊天软件是如何实现的?是否想过要制作一个属于自己的聊天室? 本篇博客将带你打造一个简单的属于自己的聊天室,将cmd作为聊天窗口,可通过内网 ...
- 基于spring boot2.0+spring security +oauth2.0+ jwt微服务架构
github地址:https://github.com/hankuikuide/microservice-spring-security-oauth2 项目介绍 该项目是一个演示项目,主要演示了,基于 ...
- DispatcherServlet和ContextLoaderListener,还有spring+servlet3.0 无web.xml启动问题
上篇提到: 关于spring +springmvc中两个spring应用上下文(DispatcherServlet和ContextLoaderListener)的问题,挺让人迷糊的. 他们都是加载Be ...
- 转: 基于netty+ protobuf +spring + hibernate + jgroups开发的游戏服务端
from: http://ybak.iteye.com/blog/1853335 基于netty+ protobuf +spring + hibernate + jgroups开发的游戏服务端 游戏服 ...
- Java Socket聊天室编程(一)之利用socket实现聊天之消息推送
这篇文章主要介绍了Java Socket聊天室编程(一)之利用socket实现聊天之消息推送的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下 网上已经有很多利用socket实现聊天的例子了 ...
- 基于tcp和多线程的多人聊天室-C语言
之前在学习关于网络tcp和多线程的编程,学了知识以后不用一下总绝对心虚,于是就编写了一个基于tcp和多线程的多人聊天室. 具体的实现过程: 服务器端:绑定socket对象->设置监听数-> ...
- python socket 聊天室
socket 发送的时候,使用的是全双工的形式,不是半双工的形式.全双工就是类似于电话,可以一直通信.并且,在发送后,如果又接受数据,那么在这个接受到数据之前,整个过程是不会停止的.会进行堵塞,堵塞就 ...
随机推荐
- utc时间转换成标准时间
把这个时间 /Date(1484884647943+0800)/ 转成标准时间 String str = String.format("%tF %<tT", 14848846 ...
- 【oracle】dblink创建
目的:oracle中跨数据库查询 两台数据库服务器db_A(本地)和db_B(远程192.168.1.100),db_A下用户user_a 需要访问到db_B下user_b的数据 解决:查询得知使用d ...
- HTTP和Socket的区别
1: HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用. HTTP ...
- HTTP Content-type整理
文件扩展名 Content-Type(Mime-Type) 文件扩展名 Content-Type(Mime-Type) .*( 二进制流.不知道下载文件类型) application/octet-st ...
- 改进版高速排序(平均复杂度O(NlogN))
#include<iostream> using namespace std; #define MAXSIZE 21 typedef int SqList[MAXSIZE]; #defin ...
- GIS+=地理信息+容器技术(4)——Docker执行
-------------------------------------------------------------------------------------- Blog: http ...
- 算法笔记_127:蓝桥杯2017模拟赛-本科组习题解答(Java)
目录 1 算年龄 2 猜算式 3 排列序数 4 字符串比较 5 还款计算 6 滑动解锁 7 风险度量 PS:以下代码部分仅供参考,若有不当之处,还请路过同学指出哦~ 1 算年龄 标题:算年龄 英 ...
- 使用 Spring 2.5 TestContext 测试框架
Spring 2.5 TestContext 测试框架用于测试基于 Spring 的程序,TestContext 测试框架和低版本 Spring 测试框架没有任何关系,是一个全新的基于注解的测试框架, ...
- 深入理解索引系列1:B+树
- html5之canvas画图 1.写字板功能
写字板事例: 写字板分析:1.点击鼠标開始写字(onmosedown)2.按下鼠标写字(onmousemove)3.松开鼠标,停下写字(撤销onmousemove事件): 代 ...