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: 8800servlet:context-path: /myserverspring: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 -04-20*/@Configurationpublic class WebSocketConfiguration implements ServletContextInitializer {@Beanpublic ServerEndpointExporter serverEndpointExporter() {ServerEndpointExporter serverEndpointExporter = new ServerEndpointExporter();return serverEndpointExporter;}/*** 启动加载** @param servletContext*/@Overridepublic void onStartup(ServletContext servletContext) {servletContext.addListener(WebAppRootListener.class);// 接收base64的字符串,等于50MservletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize", "52428800");servletContext.setInitParameter("org.apache.tomcat.websocket.binaryBufferSize", "52428800");}/*** 注入业务service** @param sxBlxxjlbService*/@Autowiredpublic 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.ponent;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 -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;@OnOpenpublic 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);}/*** 连接关闭调用的方法*/@OnClosepublic void onClose() {if (webSocketMap.containsKey(userId)) {try {// 踢掉已经连接的人this.session.close();webSocketMap.remove(userId);} catch (IOException e) {e.printStackTrace();}}LOGGER.info("用户退出:" + userId);}/*** 收到客户端消息后调用的方法** @param message 客户端发送过来的消息*/@OnMessagepublic void onMessage(String message) {LOGGER.debug("用户消息:" + userId + ",报文:" + message);// 根据当前的userId类型判断转发到pc端还是apptry {sendInfo(this.userId, "你好,我是" + myService.getMyIdentity());} catch (IOException e) {e.printStackTrace();}}/*** @param error 错误*/@OnErrorpublic 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名称注入业务service实现如:ws://127.0.0.1:8800/myserver/websocketServer/123456789
其中websocketServer是WebSocketServer类上的注解@ServerEndpoint(value = “/websocketServer/{userId}”)
servlet.context-path如果没有配置就不用添加
首先在websocket配置类中,使用依赖注入
/*** 注入业务service** @param sxBlxxjlbService*/@Autowiredpublic 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 .URI;import .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*/@Overridepublic 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)**/@Overridepublic 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**/@Overridepublic 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**/@Overridepublic void onError(Exception ex) {}}}