HTTP 协议有一个缺陷:通信只能由客户端发起,HTML5 定义了 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。可以替代长轮询,用于对实时性要求比较高的场景:
SpringBoot 中使用 WebSocket 非常简单:
pom.xml 中加入引用
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-websocket</artifactId>
- </dependency>
新建 WebSocketConfig 配置类
- @Configuration
- @EnableWebSocket
- public class WebSocketConfig {
-
- /**
- * 如果直接使用 springboot 的内置容器,而不是使用独立的 servlet 容器,就要注入 ServerEndpointExporter,外部容器则不需要。
- */
- @Bean
- public ServerEndpointExporter serverEndpointExporter() {
- return new ServerEndpointExporter();
- }
-
- }
-
-
定义接受和处理消息的处理类
注意 @ServerEndpoint 的使用
- @Component
- @ServerEndpoint("/websocket/{username}")
- public class ChatRoomServerEndpoint {
-
- public static final Map<String, Session> ONLINE_USER_SESSIONS = new ConcurrentHashMap<>();
-
- @OnOpen
- public void openSession(@PathParam("username") String username, Session session) {
- ONLINE_USER_SESSIONS.put(username, session);
- String message = "[" + username + "] 客户端信息!";
- sendMessageAll("服务器连接成功!");
- sendMessage(session, "");
- System.out.println("连接成功" + message);
- }
-
- @OnMessage
- public void onMessage(@PathParam("username") String username, String message) {
- System.out.println("服务器收到:" + "[" + username + "] : " + message);
- sendMessageAll("我已收到你的消息》》[" + username + "] : " + message);
- }
-
- @OnClose
- public void onClose(@PathParam("username") String username, Session session) {
- //当前的 Session 移除
- ONLINE_USER_SESSIONS.remove(username);
- //并且通知其他人当前用户已经断开连接了
- sendMessageAll("[" + username + "] 断开连接!");
- try {
- session.close();
- } catch (IOException e) {
- }
- }
-
- @OnError
- public void onError(Session session, Throwable throwable) {
- try {
- session.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- // 单用户推送
- public void sendMessage(Session session, String message) {
- if (session == null) {
- return;
- }
- final RemoteEndpoint.Basic basic = session.getBasicRemote();
- if (basic == null) {
- return;
- }
- try {
- basic.sendText(message);
- } catch (IOException e) {
- System.out.println("sendMessage IOException " + e);
- }
- }
-
- // 全用户推送
- public void sendMessageAll(String message) {
- ONLINE_USER_SESSIONS.forEach((sessionId, session) -> sendMessage(session, message));
- }
- }
以上就实现了 websocket server 的集成,后面就是使用不同的技术实现客户端了(html、android 等)
当然可以编写 java 版的用于测试连接,这里我们使用 Java-WebSocket:
pom.xml 文件中添加引用
- <!-- https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket -->
- <dependency>
- <groupId>org.java-websocket</groupId>
- <artifactId>Java-WebSocket</artifactId>
- <version>1.5.1</version>
- </dependency>
-
-
编写测试类
- @RunWith(Runner.class)
- class WebSocketClientTests {
-
- private final Logger log = LoggerFactory.getLogger(this.getClass());
-
- WebSocketClient webSocketClient;
- @Test
- void testClient() {
- try {
- webSocketClient = new WebSocketClient(new URI("ws://localhost:8080/websocket/test" + System.currentTimeMillis()),new Draft_6455()) {
- @Override
- public void onOpen(ServerHandshake handshakedata) {
- log.info("[websocket] 连接成功");
- webSocketClient.send("你好,我是客户端 1111");
- }
-
- @Override
- public void onMessage(String message) {
- log.info("[websocket] 收到消息={}",message);
-
- }
-
- @Override
- public void onClose(int code, String reason, boolean remote) {
- log.info("[websocket] 退出连接");
- }
-
- @Override
- public void onError(Exception ex) {
- log.info("[websocket] 连接错误={}",ex.getMessage());
- }
- };
- webSocketClient.connect();
-
- while (!webSocketClient.isClosed()) {
-
- }
- log.info("[主程序] 退出");
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- }
-
-
运行 SpringBoot 服务端和测试类,即可看到效果
- ws://localhost:8080/websocket/xxx
客户端需使用 websocket 的协议 ws,由于是用 SpringBoot 集成的 websocket,故 url 地址与正常的 http 的类似,后面的 websocket/test 是取决于 @ServerEndpoint 的定义(源码里为 @ServerEndpoint(“/websocket/{username}”),故客户端可以使用 websocket/xxx),不同 ServerEndpoint 处理不同策略
如果 SpringBoot 的 application.yml 指定了
- server:
- servlet:
- context-path: /spring-websocket
- port: 8888
则此时客户端连接的 url 应改为:
- ws://localhost:8888/spring-websocket/websocket/xxx