Spring-Boot快速集成WebSocket服务端 客户端(支持客户端消息同步回调)

Spring-Boot快速集成WebSocket服务端 客户端(客户端消息同步回调)

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

在这里插入图片描述
所以WebSocket一旦客户端和服务端建立连接就可以一直保持双向通信。现在调用的很多的第三方的一些服务都是WebSocket的连接方式。是我最近项目里的刚需。
废话不多说上代码

WebSocket服务端实现

使用Spring-Boot实现webSocket倒是简单,基于注解开发真的太爽了。

WebSocket服务端 pom.xml 依赖

		<!-- WebSocket服务端依赖-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>

如果不是Sping-Boot框架可能换成其他的socket.api的依赖

我这里定义两个类,一个配置类配置socket的参数,一个WebSocketServer的实现类,使用时会注入IOC容器。

WebSocket 服务端配置类

/**
 * WebSocket 服务端配置类
 */
@Configuration
public class WebSocketServerConfig {

    /**
     * ServerEndpointExporter bean 注入
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        ServerEndpointExporter serverEndpointExporter = new ServerEndpointExporter();
        return serverEndpointExporter;
    }

}

这里其他配置我暂时未配置,但是实际使用时,要配置一些参数优化服务。ServerEndpointExporter是必须要注入的,后续服务端类的实装需要用到。

WebSocket服务端

/**
 *
 * WebSocket服务端
 * @author DavidLei
 *
 */
@Component
@ServerEndpoint("/webSocket/{clientId}")
public class CustomizedWebSocketServer {

    /**
     * 日志
     */
    private Logger logger = LoggerFactory.getLogger(CustomizedWebSocketServer.class);

    /**
     * 在线数
     */
    private static int onlineCount = 0;

    /**
     * 线程安全的存储连接session的Map
     */
    private static Map<String, CustomizedWebSocketServer> clients = new ConcurrentHashMap<String, CustomizedWebSocketServer>();

    /**
     * session
     */
    private Session session;

    /**
     * 客户端端标识
     */
    private String clientId;

    /**
     * 客户端连接时方法
     * @param clientId
     * @param session
     * @throws IOException
     */
    @OnOpen
    public void onOpen(@PathParam("clientId") String clientId, Session session) throws IOException {
        logger.info("onOpen: has new client connect -"+clientId);
        //
        this.clientId = clientId;
        this.session = session;
        addOnlineCount();
        clients.put(clientId, this);
        logger.info("onOpen: now has "+onlineCount+" client online");
    }

    /**
     * 客户端断开连接时方法
     * @throws IOException
     */
    @OnClose
    public void onClose() throws IOException {
        logger.info("onClose: has new client close connection -"+clientId);
        clients.remove(clientId);
        subOnlineCount();
        logger.info("onClose: now has "+onlineCount+" client online");
    }

    /**
     * 收到消息时
     * @param message
     * @throws IOException
     */
    @OnMessage
    public void onMessage(String message) throws IOException {
        logger.info("onMessage: [clientId: " + clientId + " ,message:" + message + "]");
    }

    /**
     * 发生error时
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        logger.info("onError: [clientId: " + clientId + " ,error:" + error.getCause() + "]");
    }

    /**
     * 指定端末发送消息
     * @param message
     * @param clientId
     * @throws IOException
     */
    public void sendMessageByClientId(String message, String clientId) throws IOException {
        for (CustomizedWebSocketServer item : clients.values()) {
            if (item.clientId.equals(clientId) ) {
                item.session.getAsyncRemote().sendText(message);
            }
        }
    }

    /**
     * 所有端末发送消息
     * @param message
     * @throws IOException
     */
    public void sendMessageAll(String message) throws IOException {
        for (CustomizedWebSocketServer item : clients.values()) {
            item.session.getAsyncRemote().sendText(message);
        }
    }
    
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        CustomizedWebSocketServer.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        CustomizedWebSocketServer.onlineCount--;
    }

    public static synchronized Map<String, CustomizedWebSocketServer> getClients() {
        return clients;
    }
}

注意@ServerEndpoint("/webSocket/{clientId}")注解,这里标识的时Socket的地址,以我的配置为例
我的tomcat端口是8082,socket的链接地址就是 ws://localhost:8082/webSocket/12345678

我因为业务需要我这里实现了两个方法 sendMessageByClientId sendMessageAll
一个是向连接到服务端的指定客户端发送信息,一个是向所有在线客户端发送信息。

WebSocket客户端实现

WebSocket客户端 pom.xml 依赖

		<!--WebSocket客户端 核心依赖包-->
		<dependency>
			<groupId>org.java-websocket</groupId>
			<artifactId>Java-WebSocket</artifactId>
			<version>1.3.8</version>
		</dependency>

定义WebSocket客户端

/**
 * 自定义WebSocket客户端
 */
public class CustomizedWebSocketClient extends WebSocketClient {

    /**
     * 日志
     */
    private Logger logger = LoggerFactory.getLogger(CustomizedWebSocketClient.class);
    
    /**
     * 线程安全的Boolean -是否受到消息
     */
    public AtomicBoolean hasMessage = new AtomicBoolean(false);

    /**
     * 线程安全的Boolean -是否已经连接
     */
    private AtomicBoolean hasConnection = new AtomicBoolean(false);

    /**
     * 构造方法
     *
     * @param serverUri
     */
    public CustomizedWebSocketClient(URI serverUri) {
        super(serverUri);
        logger.info("CustomizeWebSocketClient init:" + serverUri.toString());
    }

    /**
     * 打开连接是方法
     *
     * @param serverHandshake
     */
    @Override
    public void onOpen(ServerHandshake serverHandshake) {
        logger.info("CustomizeWebSocketClient onOpen");
    }

    /**
     * 收到消息时
     *
     * @param s
     */
    @Override
    public void onMessage(String s) {
        hasMessage.set(true);
        logger.info("CustomizeWebSocketClient onMessage:" + s);
    }

    /**
     * 当连接关闭时
     *
     * @param i
     * @param s
     * @param b
     */
    @Override
    public void onClose(int i, String s, boolean b) {
        this.hasConnection.set(false);
        this.hasMessage.set(false);
        logger.info("CustomizeWebSocketClient onClose:" + s);
    }

    /**
     * 发生error时
     *
     * @param e
     */
    @Override
    public void onError(Exception e) {
        logger.info("CustomizeWebSocketClient onError:" + e);
    }

    @Override
    public void connect() {
        if(!this.hasConnection.get()){
            super.connect();
            hasConnection.set(true);
        }
    }
}

WebSocket客户端配置类

/**
 * WebSocket客户端配置类
 */
@Configuration
public class WebSocketClientConfig {

    /**
     * socket连接地址
     */
    @Value("${com.dl.socket.url}")
    private String webSocketUri;

    /**
     * 注入Socket客户端
     * @return
     */
    @Bean
    public CustomizedWebSocketClient initWebSocketClient(){
        URI uri = null;
        try {
            uri = new URI(webSocketUri);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
        CustomizedWebSocketClient webSocketClient = new CustomizedWebSocketClient(uri);
        //启动时创建客户端连接
         webSocketClient.connect();
        return webSocketClient;
    }

}

webSocketUri 的yml配置

#socket客户端连接地址
com.dl.socket.url: ws://localhost:8082/webSocket//12345678

到目前为止正常的需求实装完了

实装测试api

@Controller
@RequestMapping(value = "/socket")
public class WebSocketController {

    @Autowired
    private CustomizedWebSocketServer websocketServerCustomized;

    @Autowired
    private CustomizedWebSocketClient socketClient;

    @ResponseBody
   @PostMapping(value = "/message")
    public void getSocketMessage(HttpServletRequest request) throws IOException {
       JSONObject json = new JSONObject();
       json.put("to", request.getSession().getId());
       json.put("msg", "欢迎连接WebSocket!!!!");
       websocketServerCustomized.sendMessageAll(json.toJSONString());
    }
 }   

客户端后台log
在这里插入图片描述
客户端后台log
在这里插入图片描述
正常的收发是完全ok的

客户端消息同步回调

但是这远远不够,我还有其他的业务需求,现在需要调用第三方的socket接口来实时反馈数据,但是第三方的接口后台有着很多运算,往往反馈数据很慢。但是前台调用我的api时有需要返回这些反馈数据。所以需要接口同步回调数据。

加强版的客户端

/**
 * 自定义WebSocket客户端
 * @author machenike
 */
public class CustomizedWebSocketClient extends WebSocketClient {

    /**
     * 日志
     */
    private Logger logger = LoggerFactory.getLogger(CustomizedWebSocketClient.class);

    /**
     * 消息回调接口
     */
    private WebSocketClientSyncCallback callback = null;

    /**
     * 线程安全的Boolean -是否受到消息
     */
    public AtomicBoolean hasMessage = new AtomicBoolean(false);

    /**
     * 线程安全的Boolean -是否已经连接
     */
    private AtomicBoolean hasConnection = new AtomicBoolean(false);

    /**
     * 构造方法
     *
     * @param serverUri
     */
    public CustomizedWebSocketClient(URI serverUri) {
        super(serverUri);
        logger.info("CustomizeWebSocketClient init:" + serverUri.toString());
    }

    /**
     * 打开连接时
     *
     * @param serverHandshake
     */
    @Override
    public void onOpen(ServerHandshake serverHandshake) {
        logger.info("CustomizeWebSocketClient onOpen");
        hasConnection.set(true);
    }

    /**
     * 收到消息时
     *
     * @param s
     */
    @Override
    public void onMessage(String s) {
        hasMessage.set(true);
        if(callback !=null) {
            callback.callback(s);
        }
        logger.info("CustomizeWebSocketClient onMessage:" + s);
    }

    /**
     * 当连接关闭时
     *
     * @param i
     * @param s
     * @param b
     */
    @Override
    public void onClose(int i, String s, boolean b) {
        this.hasConnection.set(false);
        this.hasMessage.set(false);
        logger.info("CustomizeWebSocketClient onClose:" + s);
    }

    /**
     * 发生error时
     *
     * @param e
     */
    @Override
    public void onError(Exception e) {
        logger.info("CustomizeWebSocketClient onError:" + e);
    }


    /**
     * 带有回调的消息发送接口
     * @param text
     * @param callback
     * @throws NotYetConnectedException
     */
    public void send(String text, WebSocketClientSyncCallback callback) throws NotYetConnectedException {
        logger.info("CustomizeWebSocketClient send:" + text);
        hasMessage.set(false);
        //设定回调接口
        this.callback = callback;
        super.send(text);
        //计算等待;10s返回消息 超过10s直接退出
        for (int count = 0; ; ) {
            logger.debug("socketClient wait:"+count+" second, hasMessage:"+hasMessage);
            //判断是否收到消息||socket返回数据超时
            if (hasMessage.get()||count>10) {
                break;
            } else if (count <=10) {
                try {
                   TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count++;
            }
        }
    }

    @Override
    public void connect() {
        logger.info("CustomizeWebSocketClient connect");
            super.connect();
    }


    @Override
    public void reconnect() {
        logger.info("CustomizeWebSocketClient reconnect");
        super.reconnect();
    }

    /**
     * 定时判断连接状态:尝试重连 1分钟
     */
    @Scheduled(cron = "0 0/1 * * * ? ")
    public void autoConnect(){
        logger.info("CustomizeWebSocketClient autoConnect: [hasConnection:"+hasConnection+"]");
        if(!hasConnection.get()){
            this.reconnect();
        }
    }
}

这里我重载了send方法,可以传入回调接口。同时还加上了线程等待,注意这里的判断参数,要换成线程安全的AtomicBoolean
当触发onMessage方法时置成true,默认为false,客户端send时置成false。

socket客户端消息同步回调接口

/**
 * socket客户端消息同步回调接口
 */
public interface WebSocketClientSyncCallback {

    /**
     * socket客户端消息回调
     * @param message
     */
    void callback(String message);

}

实装测试api

    @ResponseBody
    @PostMapping(value = "/clientCallback")
    public String testClientCallback(HttpServletRequest request)  {

        try {
            socketClient.send("test message",new WebSocketClientSyncCallback(){
                @Override
                public void callback(String message) {
                    callbackMessage=message;
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

        return  callbackMessage;
    }

socket服务端后台log
在这里插入图片描述

客户端后台log
在这里插入图片描述
可见后台socket客户端等待了3s后收到服务端反馈的数据,实现了同步回调。

  • 19
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值