websocket在springboot中的使用

websocket在springboot中的使用


  1.websocket和socket的区别

   WebSocket 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。谈到Websocket,就不难想到Socket,几乎每一个第一次接触WebSocket的人都会内心生出一个疑问:WebSocket和Socket的区别在哪里?
websocket在springboot中的使用_第1张图片
  Socket其实并不是一个协议,而是为了方便使用TCP或UDP而抽象出来的一层,是位于应用层和传输控制层之间的一组接口。

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

  当两台主机通信时,必须通过Socket连接,Socket则利用TCP/IP协议建立TCP连接。TCP连接则更依靠于底层的IP协议,IP协议的连接则依赖于链路层等更低层次。

  WebSocket则是一个典型的应用层协议。

区别
  Socket是传输控制层协议,WebSocket是应用层协议。

  2.websocket机制

   以下简要介绍一下 WebSocket 的原理及运行机制。

   WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是:

   WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 一样;
WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信。
非 WebSocket 模式传统 HTTP 客户端与服务器的交互如下图所示:
websocket在springboot中的使用_第2张图片
   图 1. 传统 HTTP 请求响应客户端服务器交互图

   使用 WebSocket 模式客户端与服务器的交互如下图:
websocket在springboot中的使用_第3张图片
   图 2.WebSocket 请求响应客户端服务器交互图

   上图对比可以看出,相对于传统 HTTP 每次请求-应答都需要客户端与服务端建立连接的模式,WebSocket 是类似 Socket 的 TCP 长连接的通讯模式,一旦 WebSocket 连接建立后,后续数据都以帧序列的形式传输。在客户端断开 WebSocket 连接或 Server 端断掉连接前,不需要客户端和服务端重新发起连接请求。在海量并发及客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。

  3.websocket使用

   Websocket的使用相当简单,并且几乎所有浏览器都支持。首先需要在pom.xml中导入websocket的依赖文件,如下所示:

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-websocketartifactId>
dependency>

   其次需要创建一个配置类来配置socket的路径监听。

@Configuration
public class WebsocketConfig {

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

最后编写服务端,

@ServerEndpoint("/websocket")
@Component
@Slf4j
public class MyWebsocketServer {
    /**
     * 存放所有在线的客户端
     */
    private static Map<String, Session> clients = new ConcurrentHashMap<>();

    @OnOpen
    public void onOpen(Session session) {
        log.info("有新的客户端连接了: {}", session.getId());
        //将新用户存入在线的组
        clients.put(session.getId(), session);
    }

    /**
     * 客户端关闭
     * @param session session
     */
    @OnClose
    public void onClose(Session session) {
        log.info("有用户断开了, id为:{}", session.getId());
        //将掉线的用户移除在线的组里
        clients.remove(session.getId());
    }

    /**
     * 发生错误
     * @param throwable e
     */
    @OnError
    public void onError(Throwable throwable) {
        throwable.printStackTrace();
    }

    /**
     * 收到客户端发来消息
     * @param message  消息对象
     */
    @OnMessage
    public void onMessage(String message) {
        log.info("服务端收到客户端发来的消息: {}", message);
        this.sendAll(message);
    }

    /**
     * 群发消息
     * @param message 消息内容
     */
    private void sendAll(String message) {
        for (Map.Entry<String, Session> sessionEntry : clients.entrySet()) {
            sessionEntry.getValue().getAsyncRemote().sendText(message);
        }
    }
}

   最后附上我最近项目实际使用的websocket服务端。

@Controller
@Slf4j
@ServerEndpoint("/websocket/{userId}/{areaId}")
public class WebSocket {
    private static String liveMessageTopic = "liveMessageTopic";
    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;
    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<WebSocket>();
    //private static MessageService messageService;
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    private String userId=null;
    private String areaId=null;
    //private DocSensitiveService docSensitiveService;
    private static RedisUtil redisUtil= SpringUtils.getBean(RedisUtil.class);
    @Autowired
    private NotificationServices notificationServicesA;
    private static NotificationServices notificationServices;

    @Autowired
    private MessageSendService messageSendServiceA;
    private static MessageSendService messageSendService;

    // 非Controller 直接@Autowired无法实例化,需要初始化一下
    @PostConstruct
    public void init() {
        notificationServices = notificationServicesA;
        messageSendService=messageSendServiceA;
    }
    /**
     * 建立连接
     *
     * @param session
     */
    @OnOpen
    public void onOpen(@PathParam("userId") String userId,@PathParam("areaId") String areaId,Session session) {

        for (WebSocket str : webSocketSet) {
            if (str.userId.equals(userId)&&str.areaId.equals(areaId))
                //判断是否为同一用户,区分用户的标识为两个一个为userId是用来做投递用户的,以及区分区域的areaId是用来区大屏区域的,只有两个都相同才判断为同一用户
                //如果要重构代码建议重写equals判断这样整体效率会得到飞速提示
                try {
                    this.session = str.session;
                    Message message = new Message();
                    message.setSendUser("-2");//-2代表服务器
                    message.setContent("有其他人登录了账号你已经被强制下线");
                    Gson gson = new Gson();
                    String s = gson.toJson(message);
                    str.sendMessage(s);
                    webSocketSet.remove(str);  //从set中删除
                    str.session.close();//关闭session连接
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    log.error("websocket 重复登录异常");
                }
        }
        //新的连接建立
        this.session = session;
        webSocketSet.add(this);     //加入set中
        addOnlineCount();           //在线数加1
        log.info("有新窗口开始监听:" + userId + ",当前在线人数为" + getOnlineCount());
        this.userId = userId;
        this.areaId = areaId;//初始化两个标识
        try {
            Message message = new Message();
            message.setSendUser("-2");//-2代表服务器
            message.setContent("ws连接成功,你好用户" + String.valueOf(userId));
            Gson gson = new Gson();
            String str = gson.toJson(message);
            sendMessage(str);
            //发送消息给用户证明连接成功
            message.setContent("ws连接成功,你好用户" + areaId);
            str = gson.toJson(message);
            sendMessage(str);
            //发送消息给用户证明连接成功

            //发送未接收到的消息
            List<MessageSend> messageSendList=messageSendService.getMessageSendListByUser(userId);
            if(messageSendList.size()>0){
                // 发送未接受的消息
                try {
                    for (MessageSend messageSend : messageSendList) {
                        sendMessage(messageSend.getContent());
                    }
                    // 删除刚刚发送的消息
                    messageSendService.delete(messageSendList);
                } catch (IOException e) {
                    log.error("websocket 历史记录异常 history getting erroy");
                }
            }
        } catch (IOException e) {
            log.error("websocket 登录消息异常");
        }
        List<Message> message2= notificationServices.getMessage(userId);
        //查找当前是是否有历史消息
        if(message2!=null) {
            try {
                Gson gson = new Gson();
                for(int i=0;i<message2.size();i++) {
                    Message message=message2.get(i);
                    String content=gson.toJson(message);
                    content=content.replace("\\","");
                    content=content.replace("\"{","{");
                    content=content.replace("}\"","}");
                    sendMessage(content);
                }
            } catch (IOException e) {
                log.error("websocket 历史记录异常 history getting erroy");
            }
            notificationServices.delMessage(userId);
        }
    }

    @OnError
    public void onError(javax.websocket.Session session, Throwable error) {
        log.error("服务端发生了错误"+error);
        error.printStackTrace();
    }
    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息*/
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到来自窗口"+userId+"的信息:"+message);
        //群发消息
        for (WebSocket item : webSocketSet) {
            try {
                if(item.userId.equals(userId))
                    item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
    /**
     * 实现服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }
    /**
     * @Method 推送消息到指定userId处
     * @Author QuanJiaXing
     * @Version  1.0
     * @Description
     * @Return
     * @Exception
     * @Date 2020/7/7 22:16
     */
    public static int sendInfo(String message,@PathParam("userId") String userId) throws IOException {
        log.info("推送消息到窗口"+userId+",推送内容:"+message);
        for (WebSocket item : webSocketSet) {
            try {
                //这里可以设定只推送给这个sid的,为0则全部推送
                if(userId.equals(0)) {
                    item.sendMessage(message);
                    return 0;
                }else if(item.userId.equals(userId)){
                    item.sendMessage(message);
                    return 1;
                }
            } catch (IOException e) {
                continue;
            }
        }
        return -1;
    }
    public static int sendInfoByAreaId(String message,@PathParam("areaId") String areaId) throws IOException {
        int flag=0;
        for (WebSocket item : webSocketSet) {
            try {
                //这里可以设定只推送给这个sid的,为null则全部推送
                if(areaId.equals(0)) {
                    item.sendMessage(message);
                    log.info("推送消息到窗口"+areaId+",推送内容:"+message);
                    return flag;
                }else if(item.areaId.equals(areaId)){
                    item.sendMessage(message);
                    log.info("推送消息到窗口"+areaId+",推送内容:"+message);
                    flag=1;
                }
            } catch (IOException e) {
                continue;
            }
        }
        return flag;
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  //从set中删除
        subOnlineCount();           //在线数减1
        log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
    }
    public static CopyOnWriteArraySet<WebSocket> getWebSocketSet() {
        return webSocketSet;
    }
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

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

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

}

实际运行效果:
websocket在springboot中的使用_第4张图片
   图 3.服务端向客户端发送通知
websocket在springboot中的使用_第5张图片
   图 4.客户端接收到通知

  4.结束语

   前面的关于websocket和socket的区别的原文地址如下:
  https://www.php.cn/faq/467152.html
websocket在springboot中的使用_第6张图片
   感谢大佬科普,本人本科刚毕业的小菜鸡,做此记录,大佬勿怪,溜了溜了!

你可能感兴趣的:(SpringBoot,WebSocket)