目录
- 简介
- Pom文件
- 服务端Service代码
- 服务端配置代码
- 服务端Controller:发送消息给客户端
- 测试
- 开启ws服务端
简介
WebSocket协议通过在客户端和服务端之间提供全双工通信来进行Web和服务器的交互功能。
在ws应用程序中,服务器发布websocket端点。
一个ws连接的建立,需要客户端和服务端维护一个Key来作为该连接的链接凭证。
客户端向服务端发送wsKey,服务器根据wsKey生成wsAccept返回给客户端,客户端进行相同操作,如果最后的值和服务器返回的Accept的值相匹配,表示握手成功。握手后客户端与服务端就互相发送消息
ws支持文本消息(UTF-8编码)、二进制消息
ws的URI形式:
ws://host:port/path?query
wss://host:port/path?query
ws:未加密连接,默认端口80
wss:加密连接,默认端口443
path:表示服务器内端点的位置
query:路径参数信息
话不多说,上代码
Pom文件
<?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.9-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>websocketdemo</name>
<description>Demo project for WebSocket</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
</project>
服务端Service代码
这里的代码是websocket的服务端代码,这里相当于是服务器,负责运行wsServer,等待会话,或者发送消息。
package com.example.demo.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @Description websocket的操作类
* @Author
* @Date 2023/1/30 10:09
* @Version 1.0
*/
@ServerEndpoint("/websocket/{userId}")
@Component
@Slf4j
public class WebSocketServer {
/**
* ServerEndpoint注解
* 主要是将目前的类定义成一个websocket服务器端,
* 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
**/
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
// session集合,存放对应的session
private static ConcurrentHashMap<Integer, Session> sessionPool = new ConcurrentHashMap<>();
// concurrent包的线程安全Set,用来存放每个客户端对应的WebSocket对象。
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
/**
* 建立WebSocket连接
*
* @param session
* @param userId 用户ID
*/
@OnOpen
public void onOpen(Session session, @PathParam(value = "userId") Integer userId) {
log.info("WebSocket建立连接中,连接用户ID:{}", userId);
try {
Session historySession = sessionPool.get(userId);
// historySession不为空,说明已经有人登陆账号,应该删除登陆的WebSocket对象
if (historySession != null) {
webSocketSet.remove(historySession);
historySession.close();
}
} catch (IOException e) {
log.error("重复登录异常,错误信息:" + e.getMessage(), e);
}
// 建立连接
this.session = session;
webSocketSet.add(this);
sessionPool.put(userId, session);
log.info("建立连接完成,当前在线人数为:{}", webSocketSet.size());
}
/**
* 发生错误
*
* @param throwable e
*/
@OnError
public void onError(Throwable throwable) {
throwable.printStackTrace();
}
/**
* 连接关闭
*/
@OnClose
public void onClose() {
webSocketSet.remove(this);
log.info("连接断开,当前在线人数为:{}", webSocketSet.size());
}
/**
* 接收客户端消息
*
* @param message 接收的消息
*/
@OnMessage
public void onMessage(String message) {
log.info("收到客户端发来的消息:{}", message);
}
/**
* 推送消息到指定用户
*
* @param userId 用户ID
* @param message 发送的消息
*/
public static void sendMessageByUser(Integer userId, String message) {
log.info("用户ID:" + userId + ",推送内容:" + message);
Session session = sessionPool.get(userId);
try {
session.getBasicRemote().sendText(message);
} catch (IOException e) {
log.error("推送消息到指定用户发生错误:" + e.getMessage(), e);
}
}
/**
* 群发消息
*
* @param message 发送的消息
*/
public static void sendAllMessage(String message) {
log.info("发送消息:{}", message);
for (WebSocketServer webSocket : webSocketSet) {
try {
webSocket.session.getBasicRemote().sendText(message);
} catch (IOException e) {
log.error("群发消息发生错误:" + e.getMessage(), e);
}
}
}
}
服务端配置代码
这里是进行config的配置项
首先是对application进行配置
server:
port: 8080
websocket:
port: 6001
action: ws://127.0.0.1
thread:
boss: 12
work: 12
然后新建一个配置类
package com.example.demo.config;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
/**
* @Description websocket的配置文件, 开启websocket支持
* @Author
* @Date 2023/1/30 10:03
* @Version 1.0
*/
@Configuration
public class WebSocketConfig implements ServletContextInitializer {
/**
* 扫描所有带有@ServerEndpoint的注解成为websocket,
* 如果使用外置的tomcat就不需要该配置文件
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
}
}
服务端Controller:发送消息给客户端
这里的代码就是测试发信息的,你可以通过controller进行推送消息给当前链接的ws客户端
package com.example.demo.controller;
import com.example.demo.service.WebSocketServer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
/**
* @Description 服务端向客户端发送消息
* @Author
* @Date 2023/1/30 13:39
* @Version 1.0
*/
@RestController
public class WsController {
//给指定用户发消息
@GetMapping("/sendone/{message}/{userId}")
public String sendmessage(@PathVariable("message") String message, @PathVariable("userId") Integer userId) throws IOException {
WebSocketServer.sendMessageByUser(userId,message);
return "ok";
}
//这个可以后台群发,所有用户都能看到
@GetMapping("/sendall/{message}")
public String sendmessageall(@PathVariable("message") String message) throws IOException {
WebSocketServer.sendAllMessage(message);
return "ok";
}
}
测试
一般来说,测试需要js的前端代码实现,这里为了简化,我们直接调用网页形式进行测试
开启ws服务端
- 把上述所有的springboot项目启动,主要就是把WebSocketServer启动起来
- 打开链接http://www.jsons.cn/websocket/
输入你的ws地址,1是你的客户端ID,这个是自定义的,这个网页模仿的就是客户端。
/websocket 这个则是按照ws服务端代码中的规定写路由的 - 点击链接
正常你会看到
然后就可以愉快的客户端向服务端发信息了。
服务端向客户端发信息也很简单,之前写的那个controller就是干这个的,不再演示。