springboot WebSocket的使用

1、前言

在springboot中开发websock相关功能,报Error during WebSocket handshake Unexpected response code 404

2、原因分析

发现是websocket绑定终结点错误,需要添加WebSocketConfig

package com.ruoyi.framework.config;

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

@Configuration
public class WebSocketConfig {

    /**
     * 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

还需ShiroConfig权限通过

filterChainDefinitionMap.put("/ws/**", "anon");

3、补充websock和smartSocket结合使用

package com.ruoyi.project.front.socket;

import org.smartboot.socket.MessageProcessor;
import org.smartboot.socket.StateMachineEnum;
import org.smartboot.socket.transport.AioQuickServer;
import org.smartboot.socket.transport.AioSession;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutionException;

@Component
@ServerEndpoint("/ws/webSocket")
public class AudioWebSocket implements MessageProcessor<String> {
    //███████████████ websocket ████████████████████████████████████████████████████████████████████████████████████

    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    /**
     * 在线数量
     */
    private static int onlineCount = 0;

    /**
     * 获取在线数量
     */
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    /**
     * 增加在线数量
     */
    public static synchronized void addOnlineCount() {
        AudioWebSocket.onlineCount++;
    }

    /**
     * 减去在线数量
     */
    public static synchronized void subOnlineCount() {
        AudioWebSocket.onlineCount--;
    }

    /**
     * websocket集合
     */
    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
    private static CopyOnWriteArraySet<AudioWebSocket> webSocketSet = new CopyOnWriteArraySet<>();
    /**
     * webSocketSession
     */
    //与某个客户端的连接会话,需要通过它来给客户端发送数据 websocket
    private Session webSocketSession;

    @OnOpen
    public void onOpen(Session session) throws IOException, ExecutionException, InterruptedException {
        System.out.println("websocket connect");
        this.webSocketSession = session;
        webSocketSet.add(this);     //加入set中
        addOnlineCount();           //在线数加1
    }

    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  //从set中删除
        subOnlineCount();           //在线数减1
    }

    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("来自客户端的消息:" + message);
        // if (message.startsWith("CC")) {
        //     Timer timer = new Timer();
        //     timer.schedule(new TimerTask() {
        //         @Override
        //         public void run() {
        //             sendMessageToAllClient(SimpleDateFormat.getDateInstance().format(new Date()) + "222");
        //         }
        //     }, 0, 1000);
        // }
    }

    public synchronized void sendMessageToAllClient(String message) {
        for (AudioWebSocket item : webSocketSet) {
            try {
                item.webSocketSession.getBasicRemote().sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
                continue;
            }
        }
    }


    //███████████████ AioSession ████████████████████████████████████████████████████████████████████████████████████

    public static void aioStart() {
        try {
            aioQuickServer = new AioQuickServer<>(12000, new SinglechipFormProtocol(), new AudioWebSocket());
            aioQuickServer.setReadBufferSize(2048);
            aioQuickServer.start();
            System.out.println("通信模块 服务启动 ");
        } catch (Exception e) {
            e.printStackTrace();
            // LogHelper.info("通信模块  socket服务关闭   ");
            aioQuickServer.shutdown();
        }
    }

    public static void defend() {
        new Thread(() -> {
            aioStart();
        }).start();
    }

    /**
     * 服务端变量
     */
    public static AioQuickServer<String> aioQuickServer;
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    /**
     * aioSession
     */
    private static AioSession<String> aioSession;


    @Override
    public void process(AioSession<String> aioSession, String s) {
        System.out.println("aioSession  " + s);
        sendMessageToAllClient(s);
    }

    @Override
    public void stateEvent(AioSession<String> aioSession, StateMachineEnum stateMachineEnum, Throwable throwable) {
        switch (stateMachineEnum) {
            case NEW_SESSION:
                this.aioSession = aioSession;
                System.out.println("通信模块 NEW_SESSION state:" + stateMachineEnum);
                break;
            case SESSION_CLOSED:
                System.out.println("通信模块 SESSION_CLOSED state:" + stateMachineEnum);
                this.aioSession = null;
                break;
            default:
                System.out.println("通信模块 other state:" + stateMachineEnum + " " + throwable.toString());
                break;
        }
    }
}

SinglechipFormProtocol

package com.ruoyi.project.front.socket;

import org.smartboot.socket.Protocol;
import org.smartboot.socket.transport.AioSession;

import java.nio.ByteBuffer;

public class SinglechipFormProtocol implements Protocol<String> {

	private static final int INT_LENGTH = 4;

	@Override
	public String decode(ByteBuffer readBuffer, AioSession<String> session, boolean bool) {
		// TODO Auto-generated method stub
		int length = readBuffer.limit();

		if (readBuffer.remaining() < INT_LENGTH) {
			return null;
		}

		byte[] bytes = new byte[length];
		readBuffer.get(bytes, 0, length);

		String retStr = new String(bytes);

		// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

		// retStr = sdf.format(new Date())+ ","+ retStr;

		return retStr;
	}

	@Override
	public ByteBuffer encode(String str, AioSession<String> session) {
		// TODO Auto-generated method stub
		byte[] dd = str.getBytes();
		ByteBuffer b = ByteBuffer.allocate(INT_LENGTH);
		b.put(dd);
		b.flip();
		return b;
	}
}

pom.xml

        <dependency>
            <groupId>org.smartboot.socket</groupId>
            <artifactId>aio-core</artifactId>
            <version>1.3.12</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
posted @ 2020-12-28 20:31  一只桔子2233  阅读(663)  评论(0)    收藏  举报