WebSocket初始

1.1 WebSocket介绍

WebSocket是一种网络通信协议。RFC6455定义了它的通信标准。

WebSocket是HTML5开始的提供的一种在单个TCP连接上进行全双工通讯的协议。

HTTP协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。

这种通信模型有一个弊端: HTTP 协议无法实现服务器主动向客户端发起消息。

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数web应用程序将通过频繁的异步AJAX请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者HTTP 连接始终打开)。

在这里插入图片描述

在这里插入图片描述

1.2 WebSocket协议

本协议有两部分:握手和数据传输。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.3 springboot的webSocket集成

项目使用的spirngboot版本是2.0.0.RELEASE

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>

添加依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

添加配置

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

/**
 * @Author :
 * @Date :Created in 2023/3/23 15:52
 * @Description:Endpoint 端点实例是由Servlet容器创建的(未由spring管理),所以在端点实例里面使用spring的注入 @Autowired … 之类的注解,没用。
 */
@Configuration
public class WebSocketConfiguration {
    @Bean
    public ServerEndpointExporter serverEndpointExporter (){
        return new ServerEndpointExporter();
    }
}

端点创建

import com.common.util.SpringCtxUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

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.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author :
 * @Date :Created in 2023/3/23 15:56
 * @Description:
 */
@ServerEndpoint("/ruleExecuteProcess/{processId}")
@Component
@Slf4j
public class WebSocketServer {

    /**
     * spring管理的都是单例(singleton)和 websocket (多对象)相冲突。
     * 需要了解一个事实:websocket 是多对象的,每个用户的聊天客户端对应 java 后台的一个 websocket 对象,前后台一对一(多对多)实时连接,
     * 所以 websocket 不可能像 servlet 一样做成单例的,让所有聊天用户连接到一个 websocket对象,这样无法保存所有用户的实时连接信息。
     * 可能 spring 开发者考虑到这个问题,没有让 spring 创建管理 websocket ,而是由 java 原来的机制管理websocket ,所以用户聊天时创建的
     * websocket 连接对象不是 spring 创建的,spring 也不会为不是他创建的对象进行依赖注入,所以如果不用static关键字,每个 websocket 对象的 service 都是 null。
     * example结果为null
     * @Autowired
     * private Example example;
     */

    /**
     * 存放连接的客户端信息
     */
    public static final Map<String, Session> SESSION_MAP =new ConcurrentHashMap<>();

    /**
     * 建立连接成功调用的方法
     * @param session
     * @param processId
     */
    @OnOpen
    public void onOpen(Session session,@PathParam("processId")String processId) {
        SESSION_MAP.put(processId,session);
        log.info("连接成功!");
        basicSendMessage(session,"连接成功");
    }

    /**
     * 连接关闭调用的方法
     * @param session
     * @param processId
     */
    @OnClose
    public void onClose(Session session,@PathParam("processId")String processId){
        SESSION_MAP.remove(processId);
        log.info("断开连接!");
    }

    /**
     * 收到客户端消息后调用的方法
     * @param message
     * @param session
     * @param processId
     */
    @OnMessage
    public void onMessage(String message,Session session,@PathParam("processId")String processId){
        log.info("客户端标识:{}--->消息:{}",processId,message);
        basicSendMessage(session,"收到消息!");
        Example example = SpringCtxUtils.getBean(Example.class);
    }

    /**
     * 发生错误时调用
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.info("webSocket发生错误!");
        log.error(error.getMessage(),error);
    }

    /**
     * 服务器向客户端发送消息(异步)
     * @param toSession
     * @param message
     */
    public static void asyncSendMessage(Session toSession,String message) {
        toSession.getAsyncRemote().sendText(message);
    }

    /**
     * 服务器向客户端发送消息(同步)
     * 必须等上一条消息发送完之后才能发送新消息
     * @param toSession
     * @param message
     */
    public static void basicSendMessage(Session toSession,String message) {
        try {
            toSession.getBasicRemote().sendText(message);
        } catch (IOException e) {
            log.error(e.getMessage(),e);
        }
    }

    /**
     * 关闭与客户端连接
     * @param processId
     */
    public static void sessionClose(String processId) {
        Session session = SESSION_MAP.get(processId);
        SESSION_MAP.remove(processId);
        try {
            session.close();
        } catch (IOException e) {
            log.info(e.getMessage() , e);
        }
    }

}

webSocket依赖注释为null解决办法

手动从spring容器中获取

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @Author :
 * @Date :Created in 2023/4/8 13:05
 * @Description:
 * 获取spring容器
 * 当一个类实现了这个接口ApplicationContextAware之后,这个类就可以方便获得ApplicationContext中的所有bean。
 * 换句话说,这个类可以直接获取spring配置文件中所有有引用到的bean对象
 * 前提条件需作为一个普通的bean在spring的配置文件中进行注册
 */
@Component
public class SpringCtxUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringCtxUtils.applicationContext = applicationContext;
    }


    public static <T> T getBean(Class<T> type) {
        try {
            return applicationContext.getBean(type);
        } catch (NoUniqueBeanDefinitionException e) {
            //出现多个,选第一个
            String beanName = applicationContext.getBeanNamesForType(type)[0];
            return applicationContext.getBean(beanName, type);
        }
    }

    public static <T> T getBean(String beanName, Class<T> type) {
        return applicationContext.getBean(beanName, type);
    }
}

1.4 OkHttpClient作为webSocket客户端

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;

import java.util.concurrent.TimeUnit;

/**
 * @Author :
 * @Date :Created in 2023/3/24 15:14
 * @Description:
 */
@Data
@Slf4j
public class WebSocketClient {

    /**
     * websocket请求
     */
    private Request request;

    /**
     * httpclient客户端
     */
    private OkHttpClient client;

    /**
     * websocket客户端
     */
    private WebSocket webSocket;

    /**
     * 构造方法
     * @param bxmQualityIpAndPort
     * @param processId
     */
    public WebSocketClient(String bxmQualityIpAndPort, String processId) {
        client = new OkHttpClient.Builder()
                 // 设置 PING 帧发送间隔
                .pingInterval(5, TimeUnit.SECONDS)
                .build();
        request = new Request.Builder()
                .url("ws://"+bxmQualityIpAndPort+"/ruleExecuteProcess/"+processId)
                .build();
    }

    /**
     * 关闭websocket连接
     */
    public void close() {
        if (webSocket != null) {
            webSocket.close(1000, null);
        }
        client.dispatcher().executorService().shutdown();
    }

    /**
     * 发起websocket请求
     * @param message
     */
    public void start(StringBuilder message){
        webSocket=client.newWebSocket(request, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                super.onOpen(webSocket, response);
                // WebSocket 连接建立
                log.info("建立连接成功!");
                message.append("建立连接成功!");
            }

            @Override
            public void onMessage(WebSocket webSocket, String text) {
                super.onMessage(webSocket, text);
                // 收到服务端发送来的 String 类型消息
                log.info("发送信息:{}!",text);
                message.append(text);
            }

            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                super.onClosed(webSocket, code, reason);
                // WebSocket 连接关闭
                log.info("断开连接!");
                message.append("断开连接!");
            }

            @Override
            public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                super.onFailure(webSocket, t, response);
                // 出错了
                log.info("连接出错!");
                message.append("连接出错!");
            }
        });
    }

    /**
     * 向服务器端发送消息
     * @param message
     */
    public void sendMessage(String message) {
        webSocket.send(message);
    }

}

posted @ 2023-04-14 17:54  KeepArlen  阅读(105)  评论(0)    收藏  举报