Spring boot 实现 WebSocket服务端
这里写目录标题
- Spring boot 实现 WebSocket服务端
- 声明
- 准备工作
- 1. 配置
- 2. WebSocket服务类
- 3. 编写客户端,测试websocket服务
- 总结
声明
此文档适合初次搭建websocket的情况,包含以下内容
- websocket启动需要的配置
- ws地址
- 注入业务service
- 上传内容过大问题,如:base64的图片
websocket服务端
websocket客户端
准备工作
引入Maven依赖
<!-- log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<!-- spring 的websocket-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 测试socket使用-->
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.3.8</version>
</dependency>
添加application.yml配置文件
server:
port: 8800
servlet:
context-path: /myserver
spring:
application:
name: websocketTest
websocket的项目配置添加完成
1. 配置
创建配置类 WebSocketConfiguration.class 实现了 ServletContextInitializer 接口
类上添加注解 @Configuration, 表明这是个配置类
package com.cloud.config;
import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.web.util.WebAppRootListener;
import javax.servlet.ServletContext;
/**
* <p>
* websocket支持
* </p>
*
* @author Administrator
* @date 2021-04-20
*/
@Configuration
public class WebSocketConfiguration implements ServletContextInitializer {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
ServerEndpointExporter serverEndpointExporter = new ServerEndpointExporter();
return serverEndpointExporter;
}
/**
* 启动加载
*
* @param servletContext
*/
@Override
public void onStartup(ServletContext servletContext) {
servletContext.addListener(WebAppRootListener.class);
// 接收base64的字符串,等于50M
servletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize", "52428800");
servletContext.setInitParameter("org.apache.tomcat.websocket.binaryBufferSize", "52428800");
}
/**
* 注入业务service
*
* @param sxBlxxjlbService
*/
@Autowired
public void setWebSocketServer(MyService myService) {
WebSocketServer.myService = myService;
}
}
2. WebSocket服务类
创建websocket服务实现类
package com.websocket.websocket;
import com.websocket.service.MyService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.websocket.EncodeException;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* <p>
* websocket服务实现
* </p>
*
* @author Administrator
* @date 2021-04-20
*/
@Component
@ServerEndpoint(value = "/websocketServer/{userId}")
public class WebSocketServer {
private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class);
/**
* 业务service
*/
public static MyService myService;
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
*/
private static ConcurrentMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 接收userId
*/
private String userId;
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.session = session;
this.userId = userId;
if (webSocketMap.containsKey(userId)) {
// 重新连接,踢掉上次的连接信息
WebSocketServer webSocketServer = webSocketMap.get(userId);
webSocketServer.onClose();
}
webSocketMap.put(userId, this);
LOGGER.info("用户登录:" + userId);
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if (webSocketMap.containsKey(userId)) {
try {
// 踢掉已经连接的人
this.session.close();
webSocketMap.remove(userId);
} catch (IOException e) {
e.printStackTrace();
}
}
LOGGER.info("用户退出:" + userId);
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message) {
LOGGER.debug("用户消息:" + userId + ",报文:" + message);
// 根据当前的userId类型判断转发到pc端还是app
try {
sendInfo(this.userId, "你好,我是" + myService.getMyIdentity());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* @param error 错误
*/
@OnError
public void onError(Throwable error) {
try {
String errorMsg = "用户错误:" + userId + ",原因:" + error.getMessage();
sendMessage(errorMsg);
LOGGER.error(errorMsg);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 实现服务器主动推送-string
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 实现服务器主动推送-object
*/
public void sendMessage(Object message) throws EncodeException, IOException {
this.session.getBasicRemote().sendObject(message);
}
/**
* 发送自定义消息
*/
public static String sendInfo(String userId, String message) throws IOException {
if (webSocketMap.containsKey(userId)) {
webSocketMap.get(userId).sendMessage(message);
return "发送成功";
}
String errorMsg = "用户" + userId + ",不在线!";
LOGGER.info(errorMsg);
return errorMsg;
}
/**
* 发送自定义消息
*/
public static void sendInfo(String userId, Object message) throws IOException, EncodeException {
if (webSocketMap.containsKey(userId)) {
webSocketMap.get(userId).sendMessage(message);
return;
}
LOGGER.info("用户" + userId + ",不在线!");
}
}
至此WebSocket的代码已经写完!
- websocket的地址 ws://+IP地址+项目端口号+servlet.context-path+websocket名称
如:ws://127.0.0.1:8800/myserver/websocketServer/123456789
其中websocketServer是WebSocketServer 类上的注解@ServerEndpoint(value = “/websocketServer/{userId}”)
servlet.context-path 如果没有配置就不用添加
- 注入业务service实现
首先在websocket配置类中,使用依赖注入
/** * 注入业务service * * @param sxBlxxjlbService */ @Autowired public void setWebSocketServer(MyService myService) { WebSocketServer.myService = myService; }
然后在websocket服务实现类中,添加静态的业务service实现
/** * 业务service */ public static MyService myService;
这样就实现了在websocket服务中注入业务service,否则会报错。
3. 编写客户端,测试websocket服务
websocket测试代码
采用WebSocketClient作为客户端测试
package com.websocket.websocket;
import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.net.URISyntaxException;
/**
* <p>
* 我的测试
* </p>
*
* @author Administrator
*/
public class TestMain {
/**
* 我的测试
*
* @param args
*/
public static void main(String[] args) {
try {
String url = "ws://127.0.0.1:8800/myserver/websocketServer/123456789";
WebSocketClientTest webSocketTest = new WebSocketClientTest(new URI(url));
webSocketTest.connect();
while (!webSocketTest.getReadyState().equals(WebSocket.READYSTATE.OPEN)) {
System.out.println("请稍后,连接中...");
Thread.sleep(1000);
}
webSocketTest.send("你好,服务器,我是客户端");
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class WebSocketClientTest extends WebSocketClient {
/**
* Constructs a WebSocketClient instance and sets it to the connect to the
* specified URI. The channel does not attampt to connect automatically. The connection
* will be established once you call <var>connect</var>.
*
* @param serverUri the server URI to connect to
*/
public WebSocketClientTest(URI serverUri) {
super(serverUri);
}
/**
* Called after an opening handshake has been performed and the given websocket is ready to be written on.
*
* @param handshakedata The handshake of the websocket instance
*/
@Override
public void onOpen(ServerHandshake handshakedata) {
}
/**
* Callback for string messages received from the remote host
*
* @param message The UTF-8 decoded message that was received.
* @see #onMessage(ByteBuffer)
**/
@Override
public void onMessage(String message) {
System.out.println(message);
}
/**
* Called after the websocket connection has been closed.
*
* @param code The codes can be looked up here: {@link CloseFrame}
* @param reason Additional information string
* @param remote
**/
@Override
public void onClose(int code, String reason, boolean remote) {
}
/**
* Called when errors occurs. If an error causes the websocket connection to fail {@link #onClose(int, String, boolean)} will be called additionally.<br>
* This method will be called primarily because of IO or protocol errors.<br>
* If the given exception is an RuntimeException that probably means that you encountered a bug.<br>
*
* @param ex The exception causing this error
**/
@Override
public void onError(Exception ex) {
}
}
}
总结
- 使用spring的websocket需要服务配置
- 对于业务service实现需要在配置类中引入,不能直接在WebSocket服务中注入,会报错