使用websocket建立长连接

场景:对于某些实时性要求比较高的前后台交互,可以使用websocket进行全双工的交互,websocket可以使用H5的标准或者是Spring的实现分别如下

1.使用H5的标准如下:

a.引入依赖

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-websocket</artifactId>
     <version>${spring-boot.version}</version>
</dependency>

b.注入ServerEndpoint

@Configuration
public class WebSocketConfig1 {

    /**
     * 1.首先需要注入ServerEndpoint,这个Bean会自动注册使 用了@ServerEndpoint注解申明的Websocket endpoint
     * 要注意,如果使用独立的servlet容器,而不是直接使用springboot的内置容器,就不要注入ServerEndpointExporter
     * 因为他将由容器自己提供和管理
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

c.编写连接处理逻辑类

可以采用@ServerEndpoint(value = "/websocket/{c2c}")来携带参数,以便根据此标记向不同类型的连接发送不同的消息

/**
 * 1个session对应一个WebSocketController1对象
 */
@ServerEndpoint(value = "/websocket/{c2c}")
@Component
@EnableScheduling
public class WebSocketController1 {

    /**
     * 静态变量用来记录当前的在线连接数,应该将其设计为线程安全的
     */
    private static AtomicLong onlineCount = new AtomicLong(0);

    /**
     * 线程安全的set,用来存放每个客户端对应的socket对象
     */
    private static CopyOnWriteArraySet<WebSocketController1> webSockets = new CopyOnWriteArraySet<>();

    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    private Session session;

    private String c2c;

    /**
     * 连接建立成功调用的方法
     *
     * @param c2c     交易对
     * @param session 会话
     */
    @OnOpen
    public void onOpen(@PathParam("c2c") String c2c, Session session) {
        this.session = session;
        this.c2c = c2c;

        webSockets.add(this);

//        以下代码非线程安全的,需要将这一块代码修正为线程安全的
//        if(websocketMap.containsKey(c2c)){
//            websocketMap.get(c2c).add(this);
//        }else{
//            List<WebSocketController1> list=new LinkedList<>();
//            list.add(this);
//            websocketMap.put(c2c,list);
//        }

        System.out.println("有新连接加入!当前在线人数为" + addOnlineCount());
        try {
            //通知成功建立
            sendMessage("成功建立连接");
        } catch (IOException e) {
            System.out.println("IO异常");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSockets.remove(this);
        System.out.println("有一连接关闭!当前在线人数为" + subOnlineCount());
    }

    /**
     * @return
     */
    @OnMessage
    public void onMessage(String message) {
        System.out.println("来自客户端的消息:" + message);
        try {
            this.sendMessage(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 发生错误时调用
     *
     * @return
     */
    @OnError
    public void onError(Session session, Throwable error) throws IOException {
        System.out.println("发生异常");
        session.getBasicRemote().sendText("异常");
        error.printStackTrace();
    }

    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    /**
     * 此接口根据接受到的消息来自动的触发消息发送
     *
     * @throws IOException
     */
    @Scheduled(fixedRate = 1000)
    public static void sendInfo() throws IOException {
//        同时也可以采用伦循的方式发送消息
//        webSockets.stream().filter(socket -> socket.getC2c().equals("c2c")).forEach();
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        for (WebSocketController1 item : webSockets) {
            try {
                if (item.session.isOpen()) {
                    item.sendMessage(df.format(new Date()));
                } else {
                    webSockets.remove(item);
                }
            } catch (IOException e) {
                continue;
            }
        }
    }


    public static Long getOnlineCount() {
        return onlineCount.get();
    }

    public static Long addOnlineCount() {
        return onlineCount.incrementAndGet();
    }

    public static Long subOnlineCount() {
        return onlineCount.decrementAndGet();
    }

    public String getC2c() {
        return c2c;
    }

}

测试可以采用 jmeter或者在线测试 ws://127.0.0.1:8088/websocket/{c2c}

---------------------------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------------------------

2.采用spring提供的支持

a.引入依赖

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-websocket</artifactId>
     <version>${spring-boot.version}</version>
</dependency>

b.编写配置类

@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebSocketConfig3 extends WebMvcConfigurerAdapter implements WebSocketConfigurer {

    /**
     * 此类为自定义的连接处理类
     */
    @Autowired
    private SystemWebSocketHandler systemWebSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        System.out.println("......注册......");

        //这个类的标红的代码 解释 问题2

        //配置webSocket路径
        registry.addHandler(systemWebSocketHandler, "/websocket1").addInterceptors(new MyHandshakeInterceptor()).setAllowedOrigins("*");
        registry.addHandler(systemWebSocketHandler, "/websocket2").addInterceptors(new MyHandshakeInterceptor()).setAllowedOrigins("*");

        //配置webSocket路径 支持前端使用socketJs
        registry.addHandler(systemWebSocketHandler, "/sockjs/websocket").setAllowedOrigins("*").addInterceptors(new MyHandshakeInterceptor()).withSockJS();
    }

}

c.定义连接的拦截器

public class MyHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
    @Override
    public boolean beforeHandshake(ServerHttpRequest request,
                                   ServerHttpResponse response, WebSocketHandler wsHandler,
                                   Map<String, Object> attributes) throws Exception {
        //System.out.println("Before Handshake");
        //获得 session 和request对象(解释问题3)
        ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
        //获得session
        //  HttpSession session = servletRequest.getServletRequest().getSession(false);
        //获得httpServletRequest
        HttpServletRequest httpRequest = servletRequest.getServletRequest();
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }

    @Override
    public void afterHandshake(ServerHttpRequest request,
                               ServerHttpResponse response, WebSocketHandler wsHandler,
                               Exception ex) {
        //System.out.println("After Handshake");
        super.afterHandshake(request, response, wsHandler, ex);
    }
}

d.实现连接处理类

@Service
@EnableScheduling
public class SystemWebSocketHandler implements WebSocketHandler {

    private static AtomicLong onlineCount = new AtomicLong(0);

    public SystemWebSocketHandler() {
    }

    /**
     * 所有的socket连接
     */
    private static final ArrayList<WebSocketSession> users = new ArrayList<WebSocketSession>();


    /**
     * 连接建立成功之后,向用户发送回应
     * 也就是在连接成功建立的时候是能够携带参数的
     *
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.out.println("++++ " + addOnlineCount());
//        System.out.println("......连接之后After......" + "ConnectionEstablished");
//        System.out.println("getId:" + session.getId());
//        System.out.println("getLocalAddress:" + session.getLocalAddress().toString());
//        System.out.println("getUri:" + session.getUri().toString());
//        System.out.println("getPrincipal:" + session.getPrincipal());
        //连接进来的用户添加到用户集合中方便以后发送消息
        users.add(session);
        session.sendMessage(new TextMessage("你好:  webSocket connect 成功!!! write by spring boot"));
    }

    /**
     * 处理接收到的消息 消息的类型封装在WebSocketMessage<?>中,如果要传递指定的消息类型 就指定 ? 的类型即可
     *
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        System.out.println("发送信息" + message.toString() + " .session. " + session.getId());
        session.sendMessage(new TextMessage(message.getPayload() + "世界和平"));
    }

    /**
     * 发生异常的处理
     *
     * @param session
     * @param exception
     * @throws Exception
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        if (session.isOpen()) {
            session.close();
        }
        users.remove(session);
        System.out.println("异常出现handleTransportError" + exception.getMessage());
        throw new NullPointerException();
    }

    /**
     * 连接被关闭的处理
     *
     * @param session
     * @param closeStatus
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        System.out.println("--- " + subOnlineCount());
        users.remove(session);
        //System.out.println("关闭afterConnectionClosed" + closeStatus.getReason());
    }

    /**
     * 判断是否是支持的消息类型
     *
     * @return
     */
    @Override
    public boolean supportsPartialMessages() {
        return false;
    }


    /**
     * 每秒钟推送一次,推送给所有的客户端
     */
//    @Scheduled(fixedRate = 1000)
    public void sendMessageToUsers() {
        System.out.println("调度方法被执行");
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        TextMessage message = new TextMessage(df.format(new Date()));
        for (WebSocketSession user : users) {
            try {
                if (user.isOpen()) {
                    user.sendMessage(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 给指定用户发消息
     *
     * @param username
     * @param message
     */
    public void sendMessageToUser(String username, TextMessage message) {
        for (WebSocketSession user : users) {
            if (user.getAttributes().get("username").equals(username)) {
                try {
                    if (user.isOpen()) {
                        user.sendMessage(message);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                break;
            }
        }
    }

    public static Long addOnlineCount() {
        return onlineCount.incrementAndGet();
    }

    public static Long subOnlineCount() {
        return onlineCount.decrementAndGet();
    }
}
e.测试  ws://127.0.0.1:8088/websocket1?name=123  (可采用此形式传递参数)



  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值