前言
最近由于一些原因很久没写文章了,今天给大家分享一个Spring方式使用ws长连接实现简单聊天室功能
什么是websocket?
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
为什么有了HTTP协议还要WebSocket
HTTP协议采用的是客户端(浏览器)轮询的方式,即客户端发送请求,服务端做出响应,为了获取最新的数据,需要不断的轮询发出HTTP请求,占用大量带宽。
WebSocket采用了一些特殊的报头,使得浏览器和服务器只需要通过“握手”建立一条连接通道后,此链接保持活跃状态,之后的客户端和服务器的通信都使用这个连接,解决了Web实时性的问题,相比于HTTP有一下好处:
一个Web客户端只建立一个TCP连接
WebSocket服务端可以主动推送(push)数据到Web客户端
有更加轻量级的头,减少了数据传输量
特点
- 建立在TCP协议只上,服务端比较容易实现
- 于HTTP协议有良好的兼容性,默认端口也是80和443,握手阶段使用HTTP协议,因此握手时不容易屏蔽,能通过各种HTTP代理服务器
- 数据格式轻量,通信高效且节省带宽
- 支持传输文本数据和二进制数据
- 没有同源限制,客户端可以与任意服务器通信
- 也支持加密传输,WS+SSL,URL形如wss://
技术
- jdk8
- maven
- SpringBoot2.7.4
- thmeleaf
- websocket
实现(简易websocket聊天室)
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 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.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>websocket</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>websocket</name>
<description>websocket</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
websocketHandler.java
package com.example.websocket.handler;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class WebsocketHandler implements WebSocketHandler {
private static final Map<String, WebSocketSession> SESSIONS = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("连接成功"+session.getId());
SESSIONS.put(session.getId(), session);
System.out.println("当前在线人数:"+SESSIONS.size());
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
System.out.println("接收消息"+session.getId());
String msg = message.getPayload().toString();
System.out.println(msg);
// session.sendMessage(message);
sendMessageToAllUsers(session.getId()+":"+msg);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
System.out.println("连接出错"+session.getId());
if (!session.isOpen()) {
SESSIONS.remove(session.getId());
session.close();
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
System.out.println("关闭连接"+session.getId());
if(!session.isOpen()){
SESSIONS.remove(session.getId());
System.out.println("当前在线人数:"+SESSIONS.size());
}
}
@Override
public boolean supportsPartialMessages() {
return false;
}
/**
* sendMessageToUser:发给指定用户
*
*/
public void sendMessageToUser(String userId, String contents) {
WebSocketSession session = SESSIONS.get(userId);
if (session != null && session.isOpen()) {
try {
TextMessage message = new TextMessage(contents);
session.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* sendMessageToAllUsers:发给所有的用户
*
*/
public void sendMessageToAllUsers(String contents) {
Set<String> userIds = SESSIONS.keySet();
for (String userId : userIds) {
this.sendMessageToUser(userId, contents);
}
}
}
WebsocketInterceptor.java
package com.example.websocket.interceptor;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;
@Component
public class WebsocketInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
System.out.println("前置拦截");
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
System.out.println("后置拦截");
}
}
WebsocketConfig.java
package com.example.websocket.config;
import com.example.websocket.handler.WebsocketHandler;
import com.example.websocket.interceptor.WebsocketInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Component
@EnableWebSocket
public class WebsocketConfig implements WebSocketConfigurer {
@Autowired
private WebsocketHandler websocketHandler;
@Autowired
private WebsocketInterceptor websocketInterceptor;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(websocketHandler, "/ws").setAllowedOrigins("*").addInterceptors(websocketInterceptor);
}
}
WebsocketController.java
package com.example.websocket.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class WebsocketController {
@GetMapping("/")
public String init() {
return "websocket";
}
@GetMapping("/chat")
public String chat() {
return "chatRoom";
}
}
WebsocketApplication.java
package com.example.websocket;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WebsocketApplication {
public static void main(String[] args) {
SpringApplication.run(WebsocketApplication.class, args);
}
}
前端
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My WebSocket Test</title>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css" integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous">
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
</head>
<body>
Welcome<br/>
<div id="message"></div>
<input id="text" type="text" />
<button class="btn btn-primary" onclick="send()">Send</button>
<button class="btn btn-danger"onclick="closeWebSocket()">Close</button>
</body>
<script type="text/javascript">
var websocket = null;
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
websocket = new WebSocket("ws://localhost:8080/ws");
}
else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
}
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//关闭连接
function closeWebSocket(){
websocket.close();
}
//发送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
message.value("");
}
</script>
</html>
测试
先启动服务
然后我们就可以愉快聊天了
结尾
到这我们的简单版的ws长连接聊天室就做好了,欢迎大家点赞,关注涛哥!