文章目录

目录

文章目录

前言

一、WebSocket 是什么?

二、使用步骤

1.导入依赖

2.配置文件

2.1开启对WebSocket的支持

2.2 WebSocket服务端

2.3.前端页面-发起Socket请求

2.4.访问服务端接口发送信息给客户端

3 演示

3.1启动服务

3.2 发起socket请求

3.3 服务端向客户端发送信息

3.4 客户端向服务端发送信息

3.5 关闭客户端

总结


前言

       我做的项目中需要前端和后端建立一个长连接。在公厕中有人按下报警设备的按钮后,我会接收到报警信息后会第一时间推送给前端,然后才把报警信息存到库里面,这样方式告诉前端有报警来了,需要让人去处理下,而不是让前端去轮训查看报警记录的接口检查是否有新生成的报警记录。由于 WebSocket 只需要一次 HTTP 握手,服务端就能一直与客户端保持通信,直到关闭连接,这样就解决了服务器需要反复解析 HTTP 协议,减少了资源的开销。

一、WebSocket 是什么?

         WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

        WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

二、使用步骤

1.导入依赖

SpringBoot2.0 对 WebSocket 的支持简直太棒了,直接就有包可以引入 。

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

2.配置文件

2.1开启对WebSocket的支持

package com.wjtsc.socket;

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

/**
 * @ Description: 开启WebSocket支持
 */
@Configuration
public class WebSocketConfig {

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

2.2 WebSocket服务端

package com.wjtsc.socket;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * WebSocket 服务类
 **/
@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {
    static Log log = LogFactory.getLog(WebSocketServer.class);
    // 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;
    //concurrent 包的线程安全 Set ,用来存放每个客户端对应的 MyWebSocket 对象。
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();

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

    // 接收 sid
    private String sid = "";

    // 创建一个数组用来存放所有需要向客户端发送消息的窗口号
    private static List<String> list = new ArrayList();

    public static List<String> getList() {
        return list;
    }


    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid){
        this.session = session;
        this.sid = sid;
        list.add(sid);
        webSocketSet.add(this);
        addOnlineCount();   //在线数加1
        try {
            sendMessage("WebSocket连接成功!");
            log.info("有新窗口开始监听:" + sid + ",当前在线人数为:" + getOnlineCount());
        } catch (IOException e) {
            log.error("websocket IO Exception");
        }

        /*if (!list.contains(sid)){
            list.add(sid);
            webSocketSet.add(this);  //加入set中
            addOnlineCount();   //在线数加1
            try {
                sendMessage("连接成功!");
                log.info("有新窗口开始监听:" + sid + ",当前在线人数为:" + getOnlineCount());
            } catch (IOException e) {
                log.error("websocket IO Exception");
            }
        }else {
            try {
                list.add(sid);
                sendMessage("连接失败!已经有该窗口连接了!");
                log.error("有一个重复的窗口开始监听:" + sid +" 未将它添加至客户端");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }*/

    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this); // 从 set 中删除
        list.remove(sid);
        subOnlineCount(); // 在线数减 1
        log.info(" 有一连接关闭,窗口为:" + sid + "!当前在线人数为 " + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
//        log.info(" 收到来自窗口 " + sid + " 的信息 :" + message);
//        // 群发消息
//        for (WebSocketServer item : webSocketSet) {
//            try {
//                item.sendMessage(message);
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
//        }

        log.info(" 收到来自窗口 " + sid + " 的信息 :" + message);

        String returnMessage = "你刚才说:" + message;
        try {
            session.getBasicRemote().sendText(returnMessage);
        } catch (IOException e) {
            System.out.println("返回数据失败");
        }



    }

    /**
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error(" 发生错误 ");
        error.printStackTrace();
    }

    /**
     * 实现服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

//    public static void sendInfo(String message){
//        // 对list进行遍历 对其里边的sid进行推送消息
//        for (String sid : list ) {
//            log.info(" 推送消息到窗口 " + sid + " ,推送内容 :" + message);
//            sendMessage(message);
//        }
//    }


    /**
     * 群发自定义消息
     */
    public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException {
        log.info(" 推送消息到窗口 " + sid + " ,推送内容 :" + message);
        for (WebSocketServer item : webSocketSet) {
            try {
                // 这里可以设定只推送给这个 sid 的,为 null 则全部推送
                if (sid == null) {
                    item.sendMessage(message);
                } else if (item.sid.equals(sid)) {
                    item.sendMessage(message);
                }
            } catch (IOException e) {
                continue;
            }
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }




}

2.3.前端页面-发起Socket请求

<!DOCTYPE html>
<meta charset="utf-8"/>
<title>WebSocket 测试</title>
<body>
<h2>WebSocket 测试 乔丹</h2>
<HEADER class="header">
    <a class="back" ></a>
    <h3 class="tit">服务端:</h3>
</HEADER>
<div id="message">

</div>

<HEADER class="header1">
    <a class="back" ></a>
    <h3 class="tit">客户端:</h3>
</HEADER>

<div id="footer">
    <input id="text" class="my-input" type="text" />
    <button onclick="send()" >发&nbsp;送</button>
</div>

<div id="footer1">
	<br/>
    <button onclick="closeWebSocket()" >关闭websocket连接</button>
    <button onclick="openWebSocket()" >建立websocket连接</button>
</div>


<script language="javascript" type="text/javascript">
    var websocket = null;
    //判断当前浏览器是否支持WebSocket,是则创建WebSocket
    if ('WebSocket' in window) {
        console.log("浏览器支持Websocket");
        websocket = new WebSocket("ws://localhost:8888/websocket/乔丹");
    } else {
        alert('当前浏览器 Not support websocket')
    }

    //连接发生错误的回调方法
    websocket.onerror = function () {
        console.log("WebSocket连接发生错误");
        setMessageInnerHTML("WebSocket连接发生错误");
    };
    //连接成功建立的回调方法
    websocket.onopen = function () {
//	setMessageInnerHTML("WebSocket连接成功");
        console.log("WebSocket连接成功");
    }
    //接收到消息的回调方法
    websocket.onmessage = function (event) {
        if (event.data) {
            setMessageInnerHTML(event.data);
        }
        console.log(event.data);
    }
    //连接关闭的回调方法
    websocket.onclose = function () {
        console.log("WebSocket连接关闭");
    }

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

    //发送消息
    function send() {
        var message = document.getElementById('text').value;
        websocket.send(message);
    }


    // 建立连接的方法
	function openWebSocket() {
		websocket = new WebSocket("ws://localhost:8888/websocket/乔丹");
        websocket.onopen = function () {
//	setMessageInnerHTML("WebSocket连接成功");
        console.log("WebSocket连接成功");
		}
    }


    //将消息显示在网页上

    function setMessageInnerHTML(innerHTML) {

        document.getElementById('message').innerHTML += innerHTML + '<br/>';

    }


    //如果websocket连接还没断开就关闭了窗口,后台server端会抛异常。
    //所以增加监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接
    window.onbeforeunload = function () {
        closeWebSocket();
    }

</script>
</body>
<div id="output"></div>
</html>

2.4.访问服务端接口发送信息给客户端

package com.wjtsc.controller;

import com.wjtsc.socket.WebSocketServer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.List;

@RestController
@RequestMapping("/test")
public class SendMessageController {


    @GetMapping("/sendMsg/{content}")
    public void sendMsg(@PathVariable("content") String content){
        List<String> list = WebSocketServer.getList();
        list.stream().forEach(sid ->{
            try {
                WebSocketServer.sendInfo("服务端说:" + content, sid);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

    }




}

3 演示

3.1启动服务

3.2 发起socket请求

  

3.3 服务端向客户端发送信息

3.4 客户端向服务端发送信息

3.5 关闭客户端


总结

        建立一个socket并不是很难,当然我的websocket配置类中是可以重复监听相同名字的客户端的,如果不允许出现重复的服务端名字,只需要修改配置类中@OnOpen的逻辑,相信你修改一下问题不大。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐