IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> WebSocket的简单应用 -> 正文阅读

[网络协议]WebSocket的简单应用

1、由来

我们熟悉的Http协议是一种无状态、无连接、单向的应用层协议,它采用了请求/响应模型。通信请求只能由客户端(浏览器)发起,服务端对请求做出响应处理,Http协议无法实现服务器向客户端发送消息(在服务器端发送变化的时候 比如发送公告)。在这种情况下websocket就应运而生。

Http这种单向请求,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数的web应用程序都是通过频繁的的异步javaScript和ajax请求进行长轮询。效率低下,非常的浪费资源。

webSocket链接允许客户端和服务端进行全双工通信,以便任意一方都可以建立连接将数据推送到另一端。webSocket只需要建立一次来链接,就可以一直保持链接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。

?

2、样式

?

3、简介?

有一些浏览器中缺少对WebSocket的支持,而SockJS是一个浏览器的JavaScript库,它提供了一个类似于网络的对象,SockJS提供了一个连贯的,跨浏览器的JavaScriptAPI,它在浏览器和Web服务器之间创建了一个低延迟、全双工、跨域通信通道。SockJS的一大好处在于提供了浏览器兼容性。即优先使用原生WebSocket,如果浏览器不支持WebSocket,会自动降为轮询的方式。

<script src="/js/appjs/oa/webSocket/sockjs.min.js"></script>
<script src="/js/appjs/oa/webSocket/stomp.min.js"></script>


发起连接

        //创建连接对象    未连接
        var sock = new SockJS("/endpointChat");
        //  获取 STOMP 子协议客户端对象
        var stomp = Stomp.over(sock);
        //方法签名
        stomp.connect(headers, connectCallback, errorCallback);

说明:
1)?socket连接对象也可通过WebSocket(不通过SockJS)连接

var socket=new WebSocket("/spring-websocket-portfolio/portfolio");

其中
headers表示客户端的认证信息,如:

var headers = {

    login: 'mylogin',

    passcode: 'mypasscode',

    // additional header

    'client-id': 'my-client-id'

};

若无需认证,直接使用空对象 “{}” 即可;

connectCallback 表示连接成功时(服务器响应 CONNECTED 帧)的回调方法;
errorCallback 表示连接失败时(服务器响应 ERROR 帧)的回调方法,非必须;

断开连接

stomp.disconnect();

发送信息

连接成功后,客户端可使用 send() 方法向服务器发送信息

client.send( url, headers, body);

?其中
url 为服务器 controller中 @MessageMapping 中匹配的URL,字符串,必须参数;
headers 为发送信息的header,JavaScript 对象,可选参数;
body 为发送信息的 body,字符串,可选参数;

        var payload = JSON.stringify({'message':username})
        stomp.send("/app/welcome",{},payload);


@Controller
public class WebSocketController {

	@MessageMapping("/welcome") // 浏览器发送请求通过@messageMapping 映射/welcome 这个地址。
	//@SendTo("/topic/getResponse") // 服务器端有消息时,会订阅@SendTo 中的路径的浏览器发送消息。
	@SendTo("/queue/notifications")
    public Response say(Message message) throws Exception {
		Thread.sleep(1000);
		return new Response("Welcome, " + message.getMessage() + " !");
	}

}

订阅、接收信息

STOMP 客户端要想接收来自服务器推送的消息,必须先订阅相应的URL,即发送一个 SUBSCRIBE 帧,然后才能不断接收来自服务器的推送消息;
订阅和接收消息通过 subscribe() 方法实现:

subscribe(destination url, callback, headers)

其中
destination url 为服务器 @SendTo 匹配的 URL,字符串;
callback 为每次收到服务器推送的消息时的回调方法,该方法包含参数 message;
headers 为附加的headers,JavaScript 对象;什么作用?
该方法返回一个包含了id属性的 JavaScript 对象,可作为 unsubscribe() 方法的参数;

例:

            stomp.subscribe('/topic/getResponse', function (message) { //订阅/topic/getResponse 目标发送的消息。这个是在控制器的@SendTo中定义的。
            if (message.body) {

            alert("got message with body " + message.body)

            } else {

            alert("got empty message");

            }
            });

取消订阅?

var subscription = client.subscribe(...);

 
subscription.unsubscribe();

JSON 支持

STOMP 帧的 body 必须是 string 类型,若希望接收/发送 json 对象,可通过 JSON.stringify() and JSON.parse() 实现;
例:?

var quote = {symbol: 'APPL', value: 195.46};

client.send("/topic/stocks", {}, JSON.stringify(quote));

 
client.subcribe("/topic/stocks", function(message) {

var quote = JSON.parse(message.body);

alert(quote.symbol + " is at " + quote.value);

});

4、使用

导入依赖

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

配置类

/**
 * 通过EnableWebSocketMessageBroker 开启使用STOMP协议来传输基于代理(message broker)的消息,
 * 此时浏览器支持使用@MessageMapping 就像支持@RequestMapping一样。
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    /**
     * 扫描@ServerEndpoint,将@ServerEndpoint修饰的类注册为websocket
     * 如果使用外置tomcat,则不需要此配置
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter()
    {
        return new ServerEndpointExporter();
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) { //endPoint 注册协议节点,并映射指定的URl

        //注册一个名字为"endpointChat" 的endpoint,并指定 SockJS协议。   点对点-用
        registry.addEndpoint("/endpointChat").withSockJS();
    }


    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 设置消息代理前缀
        // 即如果消息的前缀是 /topic ,就会将消息转发给消息代理(broker),
        // 再由消息代理将消息广播给当前连接的客户端。
        //点对点式增加一个/queue 消息代理
        registry.enableSimpleBroker("/queue", "/topic");


        //客户端向服务端发起请求时,需要以/app为前缀。
        registry.setApplicationDestinationPrefixes("/app");

    }
}

方式一 监控连接

后台自己实现Endpoint,前端使用内置的WebSocket。

@ServerEndpoint("/websocket")
@Component  //放到spring容器中
@Slf4j
public class WebSocketServer{

    /**
     * 所有连接的客户端
     */
    private static ConcurrentHashMap<String,Session> clients = new ConcurrentHashMap<>();


    /**
     * 建立连接时调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        clients.put(session.getId(),session);
        //向特定用户发送消息,使用的session是接收方的session
        session.getAsyncRemote().sendText("已加入群聊");
    }


    /**
     * 连接关闭时调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        clients.remove(session.getId());
        session.getAsyncRemote().sendText("已退出群聊");
    }


    /**
     * 收到客户端发送过来的消息时调用的方法
     * @param msg 客户端用户发送过来的消息,二进制可以声明为byte[]
     */
    @OnMessage
    public void onMessage(String msg) {
        //群发消息
        for (Session session : clients.values()) {
            session.getAsyncRemote().sendText(msg);
        }
    }


    /**
     * 发生错误时调用的方法
     */
    @OnError
    public void onError(Session session, Throwable e) {
        log.error("发送错误的sessionId:"+session.getId()+",错误信息:"+e.getMessage());
    }

}
<script>
    let socket;

    //手动打开连接
    function openSocket() {
        if(typeof(WebSocket) == "undefined") {
            console.log("您使用的浏览器不支持WebSocket");
        }else{
            //连接到websocket的某个endpoint
            socket = new WebSocket("ws://127.0.0.1:8080/websocket");
            //以下几个方法相当于事件监听,在特定事件触发时会自动调用
            socket.onopen = () => {
                console.log("已连接到websocket");
            };
            socket.onmessage = resp => {
                console.log("接收到服务端信息:" + resp.data);
            };
            socket.onclose = () => {
                console.log("已断开websocket连接");
            };
            socket.onerror = () => {
                console.log("websocket发生错误");
            }
        }
    }

    //手动关闭连接
    function closeSocket() {
        socket.close();
    }

    //发送消息到服务器
    function sendMsg(msg) {
        //参数不一定要是字符串类型,可以是任意类型(二进制数据)
        socket.send(msg);
    }

</script>

方式二?

1、启用STOMP功能

? ? STOMP 的消息根据前缀的不同分为三种。如下,以 /app 开头的消息都会被路由到带有@MessageMapping 或 @SubscribeMapping 注解的方法中;以/topic 或 /queue 开头的消息都会发送到STOMP代理中,根据你所选择的STOMP代理不同,目的地的可选前缀也会有所限制;以/user开头的消息会将消息重路由到某个用户独有的目的地上。

?

2、处理来自客户端的STOMP消息

? ? 服务端处理客户端发来的STOMP消息,主要用的是?@MessageMapping 注解。如下:

	@MessageMapping("/welcome") // 浏览器发送请求通过@messageMapping 映射/welcome 这个地址。
	@SendTo("/queue/notifications") // 服务器端有消息时,会订阅@SendTo 中的路径的浏览器发送消息。
    public Response say(Message message) throws Exception {
		Thread.sleep(1000);
		return new Response("Welcome, " + message.getMessage() + " !");
	}

?

?2.3、尤其注意,这个处理器方法有一个返回值,这个返回值并不是返回给客户端的,而是转发给消息代理的,如果客户端想要这个返回值的话,只能从消息代理订阅。@SendTo 注解重写了消息代理的目的地,如果不指定@SendTo,帧所发往的目的地会与触发处理器方法的目的地相同,只不过会添加上“/topic”前缀。

? ? 2.4、如果客户端就是想要服务端直接返回消息呢?听起来不就是HTTP做的事情!即使这样,STOMP 仍然为这种一次性的响应提供了支持,用的是@SubscribeMapping注解,与HTTP不同的是,这种请求-响应模式是异步的...

   @SubscribeMapping("/getShout")
   public Shout getShout(){
       Shout shout = new Shout();
       shout.setMessage("Hello STOMP");
       return shout;
   }

3、发送消息到客户端

3.1 在处理消息之后发送消息

? ? 正如前面看到的那样,使用?@MessageMapping 或者?@SubscribeMapping 注解可以处理客户端发送过来的消息,并选择方法是否有返回值。

? ? 如果 @MessageMapping 注解的控制器方法有返回值的话,返回值会被发送到消息代理,只不过会添加上"/topic"前缀。可以使用@SendTo 重写消息目的地;

? ? 如果 @SubscribeMapping 注解的控制器方法有返回值的话,返回值会直接发送到客户端,不经过代理。如果加上@SendTo 注解的话,则要经过消息代理。

3.2 在应用的任意地方发送消息

? ? spring-websocket 定义了一个?SimpMessageSendingOperations 接口(或者使用SimpMessagingTemplate ),可以实现自由的向任意目的地发送消息,并且订阅此目的地的所有用户都能收到消息。

  @Autowired
  private SimpMessagingTemplate template;
 
 
  /**
  * 广播消息,不指定用户,所有订阅此的用户都能收到消息
  * @param shout
  */
  @MessageMapping("/broadcastShout")
  public void broadcast(Shout shout) {
      template.convertAndSend("/topic/shouts", shout);
  }
  • convertAndSendToUser方法

????除了convertAndSend()以外,SimpMessageSendingOperations 还提供了convertAndSendToUser()方法。按照名字就可以判断出来,convertAndSendToUser()方法能够让我们给特定用户发送消息。

    @MessageMapping("/singleShout")
    public void singleUser(Shout shout, StompHeaderAccessor stompHeaderAccessor) {
        String message = shout.getMessage();
        LOGGER.info("接收到消息:" + message);
        Principal user = stompHeaderAccessor.getUser();
        simpMessageSendingOperations.convertAndSendToUser(user.getName(), "/queue/shouts", shout);
    }

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-08-19 19:37:39  更:2022-08-19 19:39:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 22:35:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码