Spring Boot 集成websocket

1、引入依赖
    
       org.springframework.boot
       spring-boot-starter-websocket
    
2、定义websocket配置
 

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfiguration {
    /**
     * 	注入ServerEndpointExporter,
     * 	这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
3、定义websocket服务端
 

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.validation.constraints.NotNull;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

@Component
@Slf4j
@ServerEndpoint("/websocket/{uid}")
public class WebSocketService {
    private static final long SESSION_TIMEOUT = 60000;


    /**
     * 用来存放每个客户端对应的WebSocketServer对象
     */
    private static final Map WEB_SOCKET_MAP = new ConcurrentHashMap<>();


    /**
     * 连接建立成功调用的方法
     * @param session
     * @param uid
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("uid") String uid) throws IOException {
        //判断此uid是否已有会话,如果已有会话,将之前的会话断掉
        Session sessionExist = WEB_SOCKET_MAP.get(uid);
        if (Objects.nonNull(sessionExist) && sessionExist.isOpen()) {
            log.warn("uid:{} 存在存量会话,即将将其断开", uid);
            sessionExist.close();
            log.warn("uid:{} 存在存量会话,已将其断开", uid);
        }
        session.setMaxIdleTimeout(SESSION_TIMEOUT);
        WEB_SOCKET_MAP.put(uid, session);
        log.info("websocket连接成功编号uid: " + uid + ",当前在线数: " + getOnlineClients());
        try {
            sendMessage(session, "连接成功");
        } catch (IOException e) {
            log.error("websocket发送连接成功错误编号uid: " + uid + ",网络异常!!!");
        }
    }

    /**
     * 连接关闭调用的方法
     *
     */
    @OnClose
    public void onClose(Session session, @PathParam("uid") String uid) {
        try {
            WEB_SOCKET_MAP.remove(uid);
            log.info("websocket退出编号uid: " + uid + ",当前在线数为: " + getOnlineClients());
        } catch (Exception e) {
            log.error("websocket编号uid连接关闭错误: " + uid + ",原因: " + e.getMessage());
        }
    }

    /**
     * 收到客户端消息后调用的方法
     * @param message 客户端发送过来的消息
     * @param session
     */
    @OnMessage
    public void onMessage(String message, Session session, @PathParam("uid") String uid) {
        log.info("websocket收到客户端编号uid消息: " + uid + ", 报文: " + message);
    }

    /**
     * 发生错误时调用
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error, @PathParam("uid") String uid) {
        log.error("websocket编号uid错误: " + uid+ "原因: " + error.getMessage());
        error.printStackTrace();
    }

    /**
     * 向指定uid,发送消息
     * @param uid
     * @param message
     * @return boolean
     */
    public boolean sendMessage2Uid(@NotNull String uid, String message) {
        Session session = WEB_SOCKET_MAP.get(uid);
        if (Objects.nonNull(session)) {
            try {
                this.sendMessage(session, message);
                log.info("websocket发送消息编号uid为: " + uid + "发送消息: " + message);
                return true;
            } catch (Exception e) {
                log.error("websocket发送消息失败编号uid为: " + uid + "消息: " + message);
                return false;
            }
        } else {
            log.error("websocket未连接编号uid号为: " + uid + "消息: " + message);
            return false;
        }
    }


    public boolean sendMessage2Group(List uids, String message) {
        log.info("websocket发送group消息, uids:{}, 消息内容:{}", uids, message);
        if (CollectionUtils.isEmpty(uids)) {
            return false;
        }

        for (String uid : uids) {
            this.sendMessage2Uid(uid, message);
        }

        return true;
    }

    /**
     * 广播发送消息
     * @param message
     */
    public void sendMessage2All(String message) {
        WEB_SOCKET_MAP.forEach((k, v) -> {
            Session session = WEB_SOCKET_MAP.get(k);
            try {
                this.sendMessage(session, message);
                log.info("websocket群发消息编号uid为: " + k + ",消息: " + message);
            } catch (IOException e) {
                log.error("群发自定义消息失败: " + k + ",message: " + message);
            }
        });
    }

    /**
     * 服务端群发消息-心跳包
     * @param message
     * @return int
     */
    public synchronized int sendPing(String message) {
        if (WEB_SOCKET_MAP.size() == 0) {
            return 0;
        }
        StringBuffer uids = new StringBuffer();
        AtomicInteger count = new AtomicInteger();
        WEB_SOCKET_MAP.forEach((uid, server) -> {
            count.getAndIncrement();
            if (WEB_SOCKET_MAP.containsKey(uid)) {
                Session session = WEB_SOCKET_MAP.get(uid);
                try {
                    this.sendMessage(session, message);
                    if (count.equals(WEB_SOCKET_MAP.size() - 1)) {
                        uids.append("uid");
                        return; // 跳出本次循环
                    }
                    uids.append(uid).append(",");
                } catch (IOException e) {
                    WEB_SOCKET_MAP.remove(uid);
                    log.info("客户端心跳检测异常移除: " + uid + ",心跳发送失败,已移除!");
                }
            } else {
                log.info("客户端心跳检测异常不存在: " + uid + ",不存在!");
            }
        });
        log.info("客户端心跳检测结果: " + uids + "连接正在运行");
        return WEB_SOCKET_MAP.size();
    }

    /**
     * 向
     *
     * @param message
     * @throws IOException
     */
    private void sendMessage(Session session, String message) throws IOException {
        session.getBasicRemote().sendText(message);
    }



    /**
     * 获取客户端在线数
     * @return
     */
    public static synchronized int getOnlineClients() {
        return WEB_SOCKET_MAP.size();
    }

    /**
     * 连接是否存在
     * @param uid
     * @return boolean
     */
    public static boolean isConnected(String uid) {
        if (WEB_SOCKET_MAP.containsKey(uid)) {
            return true;
        } else {
            return false;
        }
    }
}
  • @ServerEndpoint:将目前的类定义成一个websocket服务器端,注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
  • @OnOpen:当WebSocket建立连接成功后会触发这个注解修饰的方法。
  • @OnClose:当WebSocket建立的连接断开后会触发这个注解修饰的方法。
  • @OnMessage:当客户端发送消息到服务端时,会触发这个注解修改的方法。
  • @OnError:当WebSocket建立连接时出现异常会触发这个注解修饰的方法。
4、连接验证

至此服务端就写完了,可以通过在线工具或者调试工具进行调试,如果没有调试工具,可以试试
Websocket在线测试工具,Websocket接口测试、模拟请求
Spring Boot 集成websocket_第1张图片
然后在控制台就可以看到打印的log

 

5、服务端发消息到客户端
@RestController
@RequestMapping("/websocket/message/send")
public class WebSocketSendMsgController {

    @Autowired
    private WebSocketService webSocketService;

    @PostMapping("/one")
    public void sendMsgToOne(@RequestBody String userId) {
        webSocketService.sendMessage2Uid(userId, "hello one");
    }

    @PostMapping("/group")
    public void sendMsgToOne(@RequestBody List userId) {
        webSocketService.sendMessage2Group(userId, "hello group");
    }


    @PostMapping("/all")
    public void sendMsgToAll()  {
        webSocketService.sendMessage2All("hello all");
    }
}
发消息到个人(单点发送)
Spring Boot 集成websocket_第2张图片


发消息到群体(群体发送)
Spring Boot 集成websocket_第3张图片


发消息到所有(广播发送)
Spring Boot 集成websocket_第4张图片


Spring Boot 集成websocket_第5张图片

Spring Boot 集成websocket_第6张图片

6、防止会话自动关闭,客户端可以通过发送心跳的方式一直维持链接

其实就是客户端隔一段时间给服务端发一个消息,通过这种方式服务端可以知道客户端是活的,否则通过超时机制,自动断开连接,
这里我没写domo,就不粘了 (在线心跳测试WebSocket在线测试工具)
Spring Boot 集成websocket_第7张图片

你可能感兴趣的:(spring,boot,websocket,后端)