WebSocket

59 篇文章 0 订阅

一、双向通信常用解决方案

常用单向通信,前端发出请求,服务端作出响应。很多场景下需要服务端主动向客户端发出通知,常见解决方案:
1、轮询
 	1)简单轮询:间隔固定时间,向服务端发送请求来刷新页面数据。缺点:建立连接,资源消耗
 	2)变间隔轮询:间隔不固定,若返回数据无变化,则延长请求间隔时间,反之有变化则缩短请求间隔时间
 	3)带缓存的轮询:设置数据失效时间,优先从客户端缓存中获取,失效后从服务端获取。确定:实时性差
 2、长连接:客户端与服务端建立一个隐藏的长连接,用于服务端向客户端发送通知,基于http层面,对服务端性能影响较大
 3、WebSocket

二、WebSocket实现原理

1、websocket简介
  1)本质上是一个TCP连接,浏览器和服务器通过套接字建立一个持久连接
  2)支持全双工通信
  3)对代理、路由器、防火墙透明
  4)流量小、网络负载小、无头部信息、Cookie、身份验证、安全开销
  5)按业务场景可分为:广播式和点对点式(通过服务端维护websocket session实现)
2、生命周期
  1)TCP三次握手:建立TCP连接
  2)websocket握手:对握手端点url发起一个ws协议的get请求,可传参数,头信息如图所示
在这里插入图片描述
  3)双向消息推送
  4)探活:ping/pong
  5)关闭:任何一方发送Close控制帧

三、Demo

  实现websocket服务有多种方式,常见有基于spring的实现(4.x及以上)、基于tomcat的实现(7.x及以上)、基于netty的实现、基于springboot的实现,本文给出两个基于springboot的demo
1、Demo1:底层使用spring提供的websocket
  1)依赖

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

  2)开启配置

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

/**
 * @EnableWebSocketMessageBroker开启使用STOMP协议来传输基于代理(message broker)的消息,此时控制器支持使用@MessageMapping
 */
@EnableWebSocketMessageBroker
@Configuration
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    /**
     * Register STOMP endpoints mapping each to a specific URL and (optionally)
     * enabling and configuring SockJS fallback options.
     * 注册STOMP协议的节点(endpoint),并映射指定的URL
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/endpointTopic").withSockJS(); // 注册广播节点,并指定使用SocketJS协议
        registry.addEndpoint("/endpointP2P").withSockJS(); // 注册点对点节点,并指定使用SocketJS协议
    }

    /**
     * Configure message broker options.
     * 配置消息代理
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        /**
         * Enable a simple message broker and configure one or more prefixes to filter
         * destinations targeting the broker (e.g. destinations prefixed with "/topic").
         */
        registry.enableSimpleBroker("/topic", "/queue");
    }

}

  3)控制器业务代码

import com.zhanghao.demo.common.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;

@Controller
public class WebSocketController {

    /**
     * 通过SimpMessagingTemplate向客户端发送消息
     */
    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;

    /**
     * 广播
     * @param name
     * @return
     * @throws Exception
     */
    @MessageMapping("/visit") // 客户端向服务端发送请求的地址
    @SendTo("/topic/notify") // 当服务端有消息时,会对订阅了@SendTo中的路径的浏览器发送消息
    public Result testTopic(String name) throws Exception {
        String str = "welcome " + name + " visit!";
        return new Result(str);
    }

    /**
     * 点对点
     * @param msg
     * @return
     * @throws Exception
     */
    @MessageMapping("/sendMsg")
    public void testP2P(String msg) throws Exception {
        String toUser = "toUser"; // 接收消息的人,实际中应根据参数等进行解析
        String queueURL = "/queue/sendMsg"; // 订阅消息
        simpMessagingTemplate.convertAndSendToUser(toUser, queueURL, msg);
    }

}

2、Demo2:底层使用JDK自带websocket
  1)依赖

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

  2)开启websocket

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@SpringBootApplication
public class WebsocketServer {

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

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

}

  3)连接管理、数据通信

import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

@Component
@ServerEndpoint("/ws/test")
public class WebsocketEndpointTest {
    private Set<Session> sessionSet = new HashSet<>();

    /**
     * 握手成功后执行
     * @param session
     */
    @OnOpen
    public void onOpen(Session session) {
        // 可通过session.getRequestParameterMap获取建立连接时的订制参数,并通过sessionId与参数进行关联
        sessionSet.add(session);
    }

    /**
     * 连接断开时执行
     */
    @OnClose
    public void onClose(Session session) {
        sessionSet.remove(session);
    }

    /**
     * 发生错误时执行
     * @param session
     * @param error
     */

    @OnError
    public void onError(Session session, Throwable error) {
        // do something
    }

    /**
     * 收到客户端消息时执行
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        // do something
    }

    /**
     * 广播
     * @param message
     */
    public void sendTopicMessage(String message) {
        sessionSet.parallelStream().filter(Session::isOpen).collect(Collectors.toList())
                .forEach(session -> {
                    try {
                        session.getBasicRemote().sendText(message);
                    } catch (IOException e) {
                        // ERROR INFO
                    }
                });
    }

    /**
     * 点对点发送
     * @param message
     * @param session
     */
    public void sendPointMessage(String message, Session session) {
        try {
            session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            // ERROR INFO
        }
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值