java · 2022-09-30 0

websocket 使用

一、socketio

发出的请求形如:ws://localhost:9092/socket.io/?mac=2&EIO=3&transport=websocket&sid=55ff5714-ffae-4764-aa92-d50c944b62bb

1.maven 依赖

<!-- socketio -->
<dependency>
    <groupId>com.corundumstudio.socketio</groupId>
    <artifactId>netty-socketio</artifactId>
    <version>1.7.21</version>
</dependency>

<!-- slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.21</version>
</dependency>

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.1.7</version>
</dependency>

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.1.7</version>
</dependency>

<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    <scope>compile</scope>
</dependency>

2.前端

index.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>websocket-java-socketio</title>
    <script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
</head>
<body>
    <h1>Socket.io Test</h1>
    <div><p id="status">Waiting for input</p></div>
    <div><p id="message">hello world!</p></div>
    <button id="connect" onclick='connect()'/>Connect</button>
    <button id="disconnect" onclick='disconnect()'>Disconnect</button>
    <button id="send" onclick='send()'/>Send Message</button>
</body>

<script type="text/javascript">
    /**
     * 前端js的 socket.emit("事件名","参数数据")方法,是触发后端自定义消息事件的时候使用的,
     * 前端js的 socket.on("事件名",匿名函数(服务器向客户端发送的数据))为监听服务器端的事件
     **/
    let socket;
    let firstConnect = true;

    function connect() {
        if (firstConnect) {
            socket = io.connect("http://localhost:9092?mac=2");

            socket.on('reconnect', function(){ status_update("Reconnected to Server"); });
            socket.on('reconnecting', function( nextRetry ){ status_update("Reconnecting in " + nextRetry + " seconds"); });
            socket.on('reconnect_failed', function(){ message("Reconnect Failed"); });

            // 监听服务器连接事件
            socket.on('connect', function(){ status_update("Connected to Server"); });
            // 监听服务器关闭服务事件
            socket.on('disconnect', function(){ status_update("Disconnected from Server"); });
            // 监听服务器端发送消息事件
            socket.on('eventName', function(data) {
                message(data)
            });

            firstConnect = false;
        } else {
            if (socket.connected == false) {
                socket.connect();
            }
        }
    }

    function status_update(txt){
        document.getElementById('status').innerHTML = "Status: " + txt;
    }

    function disconnect() {
        if (socket != null) {
            socket.disconnect();
        }
    }

    function message(data) {
        document.getElementById('message').innerHTML = "Server says: " + data;
    }

    // 点击发送消息触发
    function send() {
        socket.emit('eventName', {'msg': "I'm Client!"});
    }
</script>
</html>

3.后端

Main1.java

package org.example;

import ch.qos.logback.classic.Level;
import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.listener.DataListener;
import lombok.extern.slf4j.Slf4j;
import org.example.bean.Message;

@Slf4j
public class Main1 {

    public static void main(String[] args) {
        // 设置 logback 日志级别是 info
        ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) org.slf4j.LoggerFactory
                .getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
        root.setLevel(Level.INFO);

        Configuration config = new Configuration();
        config.setHostname("localhost");
        config.setPort(9092);

        SocketIOServer server = new SocketIOServer(config);

        server.addConnectListener((client) -> {
            String mac = client.getHandshakeData().getSingleUrlParam("mac");
            log.info("客户端: " + client.getSessionId() + " 已连接, mac=" + mac);
        });

        server.addDisconnectListener((client) -> {
            log.info("客户端: " + client.getSessionId() + " 断开连接");
        });

        server.addPingListener((client) -> {
            log.info("客户端: " + client.getSessionId() + " ping连接");
        });

        server.addEventListener("eventName", Message.class, new DataListener<Message>() {
            @Override
            public void onData(SocketIOClient client, Message data, AckRequest ackSender) throws Exception {
                log.info("发来消息: " + data);
                // 回发消息
                client.sendEvent("eventName", "I'm Server!");
                client.sendEvent("other", "I'm other message!");
            }
        });

        server.start();
    }
}

Message.java

package org.example.bean;

import lombok.Data;

@Data
public class Message {

    private String msg;
}

MessageEventHandler.java

package org.example.handler;

import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.annotation.OnConnect;
import com.corundumstudio.socketio.annotation.OnDisconnect;
import com.corundumstudio.socketio.annotation.OnEvent;
import lombok.extern.slf4j.Slf4j;
import org.example.bean.Message;

@Slf4j
public class MessageEventHandler {

    /**
     * 客户端连接的时候触发
     */
    @OnConnect
    public void onConnect(SocketIOClient client) {
        String mac = client.getHandshakeData().getSingleUrlParam("mac");
        log.info("客户端: " + client.getSessionId() + " 已连接, mac=" + mac);
    }

    /**
     * 客户端关闭连接时触发
     */
    @OnDisconnect
    public void onDisconnect(SocketIOClient client) {
        log.info("客户端: " + client.getSessionId() + " 断开连接");
    }

    /**
     * 客户端事件
     */
    @OnEvent(value = "eventName")
    public void onEvent(SocketIOClient client, AckRequest request, Message data) {
        log.info("发来消息: " + data);
        // 回发消息
        client.sendEvent("eventName", "I'm Server!");
        client.sendEvent("other", "I'm other message!");
    }
}

Main2.java

package org.example;

import ch.qos.logback.classic.Level;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketIOServer;
import org.example.handler.MessageEventHandler;

public class Main2 {

    public static void main(String[] args) {
        // 设置 logback 日志级别是 info
        ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) org.slf4j.LoggerFactory
                .getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
        root.setLevel(Level.INFO);

        MessageEventHandler eventHandler = new MessageEventHandler();

        Configuration config = new Configuration();
        config.setHostname("localhost");
        config.setPort(9092);

        SocketIOServer server = new SocketIOServer(config);

        server.addListeners(eventHandler, MessageEventHandler.class);

        server.start();
    }
}

二、tomcat-websocket

发出的请求形如:ws://localhost:8080/websocket/zhangsan

1.maven 依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.2</version>
    <relativePath/>
</parent>

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

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    <optional>true</optional>
</dependency>

2.前端

index.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>websocket-java</title>
</head>
<body>
    <h1>Websocket Test</h1>
    <div><p id="status">Waiting for input</p></div>
    <div><p id="message">hello world!</p></div>
    <button id="connect" onclick='connect()'/>Connect</button>
    <button id="disconnect" onclick='disconnect()'>Disconnect</button>
    <button id="send" onclick='send()'/>Send Message</button>
</body>

<script type="text/javascript">
    let websocket = null;
    let firstConnect = true;

    // 创建一个数组对象用于存放当前的连接的状态,以便在页面上实时展示出来当前的状态
    let statusArr = [
        { state: 0, value: 'Connecting to Server' },
        { state: 1, value: 'Connected to Server' },
        { state: 2, value: 'Disconnecting from Server' },
        { state: 3, value: 'Disconnected from Server' },
    ]

    function connect() {
        if (firstConnect) {
            // socket.io 的写法
            // socket = io.connect("http://localhost:9092?mac=2");
            websocket = new WebSocket("ws://localhost:8080/websocket/zhangsan");

            // 监听连接状态的变化
            websocket.onopen = (event) => status_update();

            // 监听关闭时的状态变化
            websocket.onclose = (event) => status_update();

            // 监听错误的状态变化
            websocket.onerror = (event) => status_update();

            // 监听接收消息的情况
            websocket.onmessage = (res) => {
                console.log(res);
                message(res.data);
            }

            firstConnect = false;
        } else {
            // 关闭状态
            if (websocket.readyState == 3) {
                // websocket.open();
            }
        }
    }

    function status_update() {
        let state = websocket.readyState;
        let txt;

        for (let i = 0; i < statusArr.length; i++) {
            if (statusArr[i].state == state) {
                txt = statusArr[i].value;
                break;
            }
        }

        document.getElementById('status').innerHTML = "Status: " + txt;
    }

    function disconnect() {
        if (websocket != null) {
            websocket.close();
        }
    }

    function message(data) {
        document.getElementById('message').innerHTML = "Server says: " + data;
    }

    // 点击发送消息触发
    function send() {
        let json = {'msg': "I'm Client!"};
        let jsonStr = JSON.stringify(json);

        websocket.send(jsonStr);
        // socket.io 的写法
        // socket.emit('eventName', {'msg': "I'm Client!"});
    }
</script>
</html>

3.后端

WebSocketConfig.java

@Slf4j
@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

WebSocket.java

@Component
@Slf4j
@ServerEndpoint("/websocket/{username}") //暴露的ws应用的路径
public class WebSocket {

    @OnOpen
    public void onOpen(Session session, @PathParam("username") String username) {
        log.info("method: onOpen, username: {}", username);
    }

    @OnClose
    public void onClose(Session session, @PathParam("username") String username) {
        log.info("method: onClose, username: {}", username);
    }

    @OnError
    public void onError(Throwable error, Session session, @PathParam("username") String username) {
        log.info("method: onError, username: {}", username);
    }

    @OnMessage
    public void onMsg(Session session, String message, @PathParam("username") String username) throws IOException {
        log.info("method: onMsg, username: {}, message: {}", username, message);
        session.getAsyncRemote().sendText("I'm Server!");
    }
}

Boot.java

@SpringBootApplication
public class Boot {

    public static void main(String[] args) {
        SpringApplication.run(Boot.class, args);
    }
}

三、Java-WebSocket

发出的请求形如:ws://localhost:8080/zhangsan

1.maven 依赖

<!-- Java-WebSocket -->
<dependency>
    <groupId>org.java-websocket</groupId>
    <artifactId>Java-WebSocket</artifactId>
    <version>1.5.3</version>
</dependency>

<!-- slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.21</version>
</dependency>

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.1.7</version>
</dependency>

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.1.7</version>
</dependency>

<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.28</version>
</dependency>

2.服务端

Server.java

@Slf4j
public class Server extends WebSocketServer {

    public Server(InetSocketAddress address) {
        super(address);
    }

    @Override
    public void onOpen(WebSocket conn, ClientHandshake handshake) {
        log.info("- - - onOpen - - -");

        String resourceDescriptor = handshake.getResourceDescriptor();

        Map<String, String> fieldMap = new HashMap<>();
        Iterator<String> httpFields = handshake.iterateHttpFields();

        while (httpFields.hasNext()) {
            String name = httpFields.next();
            fieldMap.put(name, handshake.getFieldValue(name));
        }
        log.info("resourceDescriptor: {}, HttpFields: {}", resourceDescriptor, fieldMap);
    }

    @Override
    public void onClose(WebSocket conn, int code, String reason, boolean remote) {
        log.info("- - - onClose - - -");
        log.info("code: {}, reason: {}", code, reason);
    }

    @Override
    public void onError(WebSocket conn, Exception ex) {
        log.info("- - - onError - - -");
        ex.printStackTrace();
    }

    @Override
    public void onMessage(WebSocket conn, String message) {
        log.info("- - - onMessage - - -");

        String resourceDescriptor = conn.getResourceDescriptor();
        log.info("resourceDescriptor: {}, message: {}", resourceDescriptor, message);

        conn.send("Server Say: " + message);
    }

    @Override
    public void onStart() {
        log.info("- - - onStart - - -");
    }
}

ServerMain.java

public class ServerMain {

    public static void main(String[] args) {
        ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) org.slf4j.LoggerFactory
                .getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
        root.setLevel(Level.INFO);

        InetSocketAddress address = new InetSocketAddress(8080);

        Server server = new Server(address);
        server.start();
    }
}

3.客户端

Client.java

@Slf4j
public class Client extends WebSocketClient {

    public Client(URI serverURI) {
        super(serverURI);
    }

    @Override
    public void onOpen(ServerHandshake handshake) {
        log.info("- - - onOpen - - -");

        Map<String, String> fieldMap = new HashMap<>();
        Iterator<String> httpFields = handshake.iterateHttpFields();

        while (httpFields.hasNext()) {
            String name = httpFields.next();
            fieldMap.put(name, handshake.getFieldValue(name));
        }
        System.out.println(fieldMap);
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        log.info("- - - onClose - - -");
        log.info("code: {}, reason: {}", code, reason);
    }

    @Override
    public void onError(Exception ex) {
        log.info("- - - onError - - -");
        ex.printStackTrace();
    }

    @Override
    public void onMessage(String message) {
        log.info("- - - onMessage - - -");
        log.info("message: {}", message);
    }
}

ClientMain.java

public class ClientMain {

    public static void main(String[] args) throws Exception {
        ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) org.slf4j.LoggerFactory
                .getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
        root.setLevel(Level.INFO);

        Client client = new Client(new URI("ws://localhost:8080/zhangsan"));
        client.connect();

        while (true) {
            if (client.isOpen()) {
                client.send("Hello");
            }
            Thread.sleep(1000);
        }
    }
}