1、WebSocket应用
WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信一允许服务器主动发送信息给客户端,这样就可以实现从客户端发送消息到服务器,而服务器又可以转发消息到客户端,这样就能够实现客户端之间的交互。
对于WebSocket的开发,Spring也提供了良好的支持。目前很多浏览器已经实现了WebSocket协议,但是依I旧存在着很多浏览器没有实现该协议,为了兼容那些没有实现该协议的浏览器,往往还需要通过STOMP协议来完成这些兼容。
2、开发简易的WebSocket服务
本案例开发一个简易的在线网络聊天室,实现访问用户的群聊功能。
2.1、项目依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!--引入Spring Boot内嵌的Tomcat对JSP的解析包,不加解析不了jsp页面-->
<!--如果只是使用JSP页面,可以只添加该依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!--如果使用JSTL必须添加该依赖-->
<!--jstl标签依赖的jar包start-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!--如果要使用servlet必须添加该以下两个依赖-->
<!-- servlet依赖的jar包-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<!--源文件位置-->
<directory>src/main/webapp</directory>
<!--指定编译到META-INF/resources,该目录不能随便写-->
<targetPath>META-INF/resources</targetPath>
<!--指定要把哪些文件编译进去,**表示webapp目录及子目录,*.*表示所有文件-->
<includes>
<include>**/*.*</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.2、springboot配置
#指定内嵌Tomcat端口号
server:
port: 8080
servlet:
context-path: /
#配置SpringMVC视图解析器
#其中:/ 表示目录为src/main/webapp
spring:
mvc:
view:
prefix: /
suffix: .jsp
2.3、自定义WebSocket服务端点配置
对于WebSocket的使用,可以先通过Spring创建Java配置文件。在这个文件中,先新建ServerEndpointExporter对象,通过它可以定义WebSocket服务器的端点,这样客户端就能请求服务器的端点:
@Configuration
public class WebSocketConfig {
//创建服务器端点
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
有了这个Bean,就可以使用@ServerEndpoint定义一个端点服务类。在这个站点的服务类中,还可以定义WebSocket的打开、关闭、错误和发送消息的方法:
@ServerEndpoint("/ws")
@Service
public class WebSocketServiceImpl {
//静态变量,用来记录当前在线连接数。应该设计为线程安全的
private static int onlineCount = 0;
// concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServiceImpl对象
private static CopyOnWriteArraySet<WebSocketServiceImpl> webSocketSet = new CopyOnWriteArraySet<>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
/**
* 连接建立成功调用的方法
* @param session
*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
webSocketSet.add(this);//加入set中
addOnlineCount();//在线数加1
System.out.println("有新连接加入!当前在线人数为:" + getOnlineCount());
//获取当前用户名称
Map<String, List<String>> requestParameterMap = session.getRequestParameterMap();
String name = requestParameterMap.get("name").get(0);
try {
sendMessage("欢迎" + name + "加入聊天室!");
} catch (IOException e) {
System.out.println("IO异常");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); //从set中删除
subOnlineCount();//在线数减1
System.out.println("有一连接关闭!当前在线人数为:" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
* @param message
* @param session
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("来自客户端的消息:" + message);
//群发消息
for (WebSocketServiceImpl socket : webSocketSet) {
try {
//获取当前用户名称
Map<String, List<String>> requestParameterMap = session.getRequestParameterMap();
String name = requestParameterMap.get("name").get(0);
//发送消息
if (socket.session == session) {
socket.sendMessage(message + "<--" + name);
} else {
socket.sendMessage(name + "-->" + message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 发生错误时调用
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("发生错误");
error.printStackTrace();
}
/**
* 发送消息
* @param message
* @throws IOException
*/
private void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 返回在线人数
* @return
*/
private static synchronized int getOnlineCount() {
return onlineCount;
}
/**
* 连接人数增加
*/
private static synchronized void addOnlineCount() {
WebSocketServiceImpl.onlineCount++;
}
/**
* 连接人数减少
*/
private static synchronized void subOnlineCount() {
WebSocketServiceImpl.onlineCount--;
}
}
- @ServerEndpoint((“/ws”):表示让Spring创建WebSocket的服务端点,其中请求地址是“ws”。
- @OnOpen:标注客户端打开WebSocket服务端点调用方法。
- @OnClose:标注客户端关闭WebSocket服务端点调用方法。
- @OnMessage:标注客户端发送消息,WebSocket服务端点调用方法。
- @OnError:标注客户端请求WebSocket服务端点发生异常调用方法。
因为每一个客户端打开时,都会为其创建一个WebSocketServiceImpl对象,所以这里的打开方法中都会去计数并且将这个对象保存到CopyOnWriteArraySet中,这样就可以知道拥有多少连接。对于关闭方法则是清除这个对象,并且计数减一。对于消息发送方法,则是通过轮询对所有的客户端连接都给予发送消息,所以所有的连接都可以收到这个消息。但是有时候可能只是需要发送给特定的用户,则需要得到用户的信息,然后再发送给特定的用户。
2.4、JSP页面
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>网络聊天室</title>
</head>
<body>
<h2>欢迎使用在线网络聊天室</h2>
<br/>
<form method="get" action="chat">
<input name="name" type="text" value="" placeholder="请输入您的昵称">
<button type="submit">提交</button>
</form>
</div>
</body>
</html>
websocket.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>网络聊天室</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-3.2.1.min.js"></script>
</head>
<script type="text/javascript">
var websocket = null;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
//创建WebSocket对象,连接服务器端点
websocket = new WebSocket("ws://localhost:8080/ws?name=${name}");
} else {
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function () {
appendMessage("error");
};
//连接成功建立的回调方法
websocket.onopen = function (event) {
appendMessage("open");
};
//接收到消息的回调方法
websocket.onmessage = function (event) {
appendMessage(event.data);
};
//连接关闭的回调方法
websocket.onclose = function () {
appendMessage("close");
};
//监听窗口关闭事件,当窗口关闭时,主动关闭websocket连接
//防止连接还没断开就关闭窗口,server端会抛出异常
window.onbeforeunload = function () {
websocket.close();
};
//将消息显示在网页上
function appendMessage(message) {
var context = $('#context').html() + '<br/>' + message;
$('#context').html(context);
};
//关闭连接
function closeWebSocket() {
websocket.close();
};
//发送消息
function sendMessage() {
var message = $('#message').val();
websocket.send(message);
};
</script>
<body>
<h2>在线网络聊天室</h2>
<br/>
<input id="message" type="text">
<button onclick="sendMessage()">发送消息</button>
<button onclick="closeWebSocket()">关闭WebSocket连接</button>
<div id="context">
</div>
</body>
</html>
2.5、Controller
@Controller
@RequestMapping("/websocket")
public class WebSocketController {
//跳转首页
@GetMapping("/index")
public String index() {
return "index";
}
//接收name参数,跳转websocket页面
@GetMapping("/chat")
public String websocket(String name, HttpServletRequest request) {
System.out.println(name);
request.setAttribute("name", name);
return "websocket";
}
}
2.6、测试