基于Spring 4.0 的 Web Socket 聊天室/游戏服务端简单架构

跟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 4.0.0
5 com.lee
6 websocket
7 maven-spring-websocket-01
8 war
9 1.0.0-BUILD-SNAPSHOT
10
11
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
22 <logback.version>1.0.13</logback.version>
23 <slf4j.version>1.7.7</slf4j.version>
24
25
26
27
28
29 org.springframework
30 spring-core
31 s p r i n g . v e r s i o n &lt; / v e r s i o n &gt; 32 &lt; / d e p e n d e n c y &gt; 3334 &lt; d e p e n d e n c y &gt; 35 &lt; g r o u p I d &gt; o r g . s p r i n g f r a m e w o r k &lt; / g r o u p I d &gt; 36 &lt; a r t i f a c t I d &gt; s p r i n g − w e b &lt; / a r t i f a c t I d &gt; 37 &lt; v e r s i o n &gt; {spring.version}&lt;/version&gt; 32 &lt;/dependency&gt; 33 34 &lt;dependency&gt; 35 &lt;groupId&gt;org.springframework&lt;/groupId&gt; 36 &lt;artifactId&gt;spring-web&lt;/artifactId&gt; 37 &lt;version&gt; spring.version</version>32</dependency>3334<dependency>35<groupId>org.springframework</groupId>36<artifactId>springweb</artifactId>37<version>{spring.version}
38
39
40
41 org.springframework
42 spring-webmvc
43 s p r i n g . v e r s i o n &lt; / v e r s i o n &gt; 44 &lt; / d e p e n d e n c y &gt; 4546 &lt; ! − − j s t l − − &gt; 47 &lt; d e p e n d e n c y &gt; 48 &lt; g r o u p I d &gt; j s t l &lt; / g r o u p I d &gt; 49 &lt; a r t i f a c t I d &gt; j s t l &lt; / a r t i f a c t I d &gt; 50 &lt; v e r s i o n &gt; 1.2 &lt; / v e r s i o n &gt; 51 &lt; / d e p e n d e n c y &gt; 5253 &lt; ! − − s p r i n g 测 试 框 架 − − &gt; 54 &lt; d e p e n d e n c y &gt; 55 &lt; g r o u p I d &gt; o r g . s p r i n g f r a m e w o r k &lt; / g r o u p I d &gt; 56 &lt; a r t i f a c t I d &gt; s p r i n g − t e s t &lt; / a r t i f a c t I d &gt; 57 &lt; v e r s i o n &gt; {spring.version}&lt;/version&gt; 44 &lt;/dependency&gt; 45 46 &lt;!-- jstl --&gt; 47 &lt;dependency&gt; 48 &lt;groupId&gt;jstl&lt;/groupId&gt; 49 &lt;artifactId&gt;jstl&lt;/artifactId&gt; 50 &lt;version&gt;1.2&lt;/version&gt; 51 &lt;/dependency&gt; 52 53 &lt;!--spring测试框架 --&gt; 54 &lt;dependency&gt; 55 &lt;groupId&gt;org.springframework&lt;/groupId&gt; 56 &lt;artifactId&gt;spring-test&lt;/artifactId&gt; 57 &lt;version&gt; spring.version</version>44</dependency>4546<!jstl>47<dependency>48<groupId>jstl</groupId>49<artifactId>jstl</artifactId>50<version>1.2</version>51</dependency>5253<!spring>54<dependency>55<groupId>org.springframework</groupId>56<artifactId>springtest</artifactId>57<version>{spring.version}
58 test
59
60
61
62
63 org.springframework
64 spring-jdbc
65 s p r i n g . v e r s i o n &lt; / v e r s i o n &gt; 66 &lt; / d e p e n d e n c y &gt; 6768 &lt; d e p e n d e n c y &gt; 69 &lt; g r o u p I d &gt; j u n i t &lt; / g r o u p I d &gt; 70 &lt; a r t i f a c t I d &gt; j u n i t &lt; / a r t i f a c t I d &gt; 71 &lt; v e r s i o n &gt; 4.8.2 &lt; / v e r s i o n &gt; 72 &lt; s c o p e &gt; t e s t &lt; / s c o p e &gt; 73 &lt; / d e p e n d e n c y &gt; 7475 &lt; ! − − s p r i n g w e b s o c k e t 库 − − &gt; 76 &lt; d e p e n d e n c y &gt; 77 &lt; g r o u p I d &gt; o r g . s p r i n g f r a m e w o r k &lt; / g r o u p I d &gt; 78 &lt; a r t i f a c t I d &gt; s p r i n g − w e b s o c k e t &lt; / a r t i f a c t I d &gt; 79 &lt; v e r s i o n &gt; {spring.version}&lt;/version&gt; 66 &lt;/dependency&gt; 67 68 &lt;dependency&gt; 69 &lt;groupId&gt;junit&lt;/groupId&gt; 70 &lt;artifactId&gt;junit&lt;/artifactId&gt; 71 &lt;version&gt;4.8.2&lt;/version&gt; 72 &lt;scope&gt;test&lt;/scope&gt; 73 &lt;/dependency&gt; 74 75 &lt;!--spring websocket库 --&gt; 76 &lt;dependency&gt; 77 &lt;groupId&gt;org.springframework&lt;/groupId&gt; 78 &lt;artifactId&gt;spring-websocket&lt;/artifactId&gt; 79 &lt;version&gt; spring.version</version>66</dependency>6768<dependency>69<groupId>junit</groupId>70<artifactId>junit</artifactId>71<version>4.8.2</version>72<scope>test</scope>73</dependency>7475<!springwebsocket>76<dependency>77<groupId>org.springframework</groupId>78<artifactId>springwebsocket</artifactId>79<version>{spring.version}
80
81
82 org.springframework
83 spring-messaging
84 KaTeX parse error: Expected 'EOF', got '&' at position 755: …ing with SLF4J &̲ LogBack --> 10…{slf4j.version}
110 compile
111
112
113 ch.qos.logback
114 logback-classic
115 ${logback.version}
116 runtime
117
118
119
120
121 com.alibaba
122 druid
123 1.0.4
124
125
126
127
128 mysql
129 mysql-connector-java
130 5.1.29
131
132
133
134
135
136
137
138 org.apache.maven.plugins
139 maven-compiler-plugin
140
141 1.7
142 1.7
143
144
145
146
147
148

复制代码

主要结构

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 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 = “” + clientName + “”;
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
3
4
5
87
88
89
90
91


92

93

94

95
96
97 连接服务器
98

99

100

101
102

103
104

105

106
107
108 发送
109

110

111

112
113
114

复制代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值