欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Websocket的多客户端通信Demo

程序员文章站 2022-03-15 22:24:00
...

公司在做平台搭建,需要一个websocket的基础应用,由于websocket的实际业务场景多且复杂,所以并没有真的抽象出来一个底层的demo,只是做了一个比较基础的案例,适合初学者参考,并且标记了一些坑,是很多网上案例的bug。亲测成功的,希望对大家有帮助啦!

环境配置

1.首先介绍一下我的环境配置:windows7,jdk1.8,tomcat8.0.3,maven3.6,编辑器Idea
2.我用的框架是springboot2.0,所以对于websocket支持直接添加maven依赖:

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.java-websocket</groupId>
            <artifactId>Java-WebSocket</artifactId>
            <version>1.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>

3.由于tomcat8是自带websocket包,所以我们只能引入一种,否则会jar包冲突

核心代码

1.websocket配置类,启动websocket服务,注解千万别丢了,否则就是无效的

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

2.服务类,因为websocket采用的是ws协议,所以实现类就相当于我们的controller层,这里需要用到两个注解,分别是:@ServerEndpoint和@Component。

@ServerEndpoint("/ws/{sid}") 
@Component
public class SocketServer {
    private static int onlineCount = 0;
    private static Map<String, SocketServer> clients = new ConcurrentHashMap<>();
    private Session session;
    private String sid;


    /**
     * 连接建立成功调用的方法
     * @param sid
     * @param session
     * @throws IOException
     */
    @OnOpen
    public void onOpen(@PathParam("sid") String sid, Session session) throws IOException {
        this.sid = sid;

        this.session = session;
        addOnlineCount();//在线数加1
        clients.put(sid, this);//加入map中
        System.out.println("连接建立成功");
    }


    /**
     * 连接关闭调用的方法
     * @throws IOException
     */
    @OnClose
    public void onClose() throws IOException {
        clients.remove(sid);//从map中删除
        subOnlineCount();//在线数-1
        System.out.println("连接关闭成功");
    }


    /**
     * 连接错误方法
     * @param error
     */

    @OnError
    public void onError( Throwable error) {
        System.out.println("连接异常");
        error.printStackTrace();
    }

    /**
     * 收到客户端消息后调用的方法
     * @param message
     * @throws IOException
     */
    @OnMessage
    public void onMessage(String message,@PathParam("sid") String sid){
        //解析字符串
        if(null!=message && !"".equals(message)){
            //根据sid拆分得到消息发送类型
            String type=sid.substring(sid.length() - 1);
            if("A".equals(type)) //群发
                for (String key : clients.keySet()) {
                    try {
                        clients.get(key).sendMessage(message);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            else
                try {
                    sendMessageAll(message,sid);
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }


    /**
     * 指定用户发送
     * @param message
     * @param sid
     * @throws IOException
     */
        public void sendMessageAll(String message,@PathParam("sid") String sid) throws IOException {
        try {
            if (clients.get(sid) != null) {
                clients.get(sid).sendMessage(message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     *
     * 实现服务器主动推送
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException {
        this.session.getAsyncRemote().sendText(message);//异步防止阻塞
        System.out.println("发送消息成功!");
    }


    /**
     * 获取连接池中所有在线人数
     * @return
     */
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }


    /**
     * 向连接池中添加连接
     * @return
     */
    public static synchronized void addOnlineCount() {
        SocketServer.onlineCount++;
    }

    /**
     * 移除连接池中的连接
     * @return
     */
    public static synchronized void subOnlineCount() {
        SocketServer.onlineCount--;
    }


    /**
     * 获取连接池中所有连接
     * @return
     */
    public static synchronized Map<String, SocketServer> getClients() {
        return clients;
    }
}

其中,@ServerEndpoint 注解是一个类范围的注解,主要是将当前的类定义成一个websocket服务器端,注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
@Component 注解至关重要,网上很多案例是没有加这个注解的,它是把普通pojo实例化到spring容器中,相当于配置文件中的bean标签。它本身就是单例的,所以千万不要将服务类改写成单例,曾经我想封装,把改成了单例模式,之后报错找了好久才发现是冲突了。

3.消息推送,这里可以简单写个方法调用服务类里的onMessage()方法即可

@RestController
@RequestMapping("/test")
public class test {
    @GetMapping("socket/{sid}")
    public void getSocket(String message, @PathVariable String sid)throws IOException {
        SocketServer server=new SocketServer();
        server.onMessage(message,sid);
    }
}

4.前台页面,这里只要注意请求的路径格式和参数就行,我这边需要两个参数:sid:session用户编号 type:发送的消息类型(A:群发 B:指定用户)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>UmaSoft</title>
</head>
<body>
    <input type="text" id="msg" style="width: 200px;" />
    <input type="button" value="发送" onclick="send()" />
    <input type="button" value="重新连接" onclick="re()" />
    <br />
    <br />
    <textarea cols="80" rows="100" id="content"></textarea>
<script type="text/javascript">
    var websocket = null;
    var sid = new Date().getTime();
    localStorage["user"] = sid;
    var type='B';
    openWebSocket();
    function openWebSocket() {
        //判断当前浏览器是否支持WebSocket
        if (!!window.WebSocket && window.WebSocket.prototype.send){
            var wsUrl = "ws://localhost:8080/ws/"+ sid+type;
            websocket = new WebSocket(wsUrl);
            //连接发生错误的回调方法
            websocket.onerror = function() {
                setMessageInnerHTML("WebSocket连接发生错误");
            };

            //连接成功建立的回调方法
            websocket.onopen = function() {
                setMessageInnerHTML(localStorage["user"] + "连接成功");
            }

            //接收到消息的回调方法
            websocket.onmessage = function(event) {
                setMessageInnerHTML(event.data);
            }

            //连接关闭的回调方法
            websocket.onclose = function() {
                setMessageInnerHTML(localStorage["user"] + "连接关闭");
            }
        } else {
            alert('当前浏览器 Not support websocket')
        }
    }

    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function() {
        closeWebSocket();
    }

    //关闭WebSocket连接
    function closeWebSocket() {
        websocket.close();
    }

    function re() {
        closeWebSocket();
        websocket = null;
        openWebSocket();
    }

    var input = document.getElementById('content');
    var text = document.getElementById('msg');

    function setMessageInnerHTML(msg) {
        if (input.value != '')
            input.value = input.value + '\r\n' + msg;
        else
            input.value = msg;
    }

    function send() {
        // var jsonStr =[{content:text.value}];
        // websocket.send(jsonStr);
        websocket.send(text.value);
    }
</script>
</body>
<!-- websocket -->
<script src="./js/jquery.min.js" type="text/javascript"></script>
<!--<script src="./js/ws.js" type="text/javascript"></script>-->
</html>

5.使用方法
前台:请求路径格式:ws://项目路径+端口号/ws/"+ sid+type
后台:引入SocketServer类,实例化后,根据自己的业务调用里面的方法

好啦,上述5步就可以搭建一个简单的通信功能啦,希望对小伙伴有用,有疑问或者错误欢迎提出,一起进步!