Hz零基础学习WebSocket

1、websocket

WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。


应用场景:通知通告、弹幕,网页聊天系统,实时监控,股票行情推送等




sockjs:	
			
 1、是一个浏览器JavaScript库,提供了一个类似WebSocket的对象。
 2、提供了一个连贯的跨浏览器的JavaScriptAPI,在浏览器和Web服务器之间创建了一个低延迟,全双工,跨域的通信通道
 3、在底层SockJS首先尝试使用本地WebSocket。如果失败了,它可以使用各种浏览器特定的传输协议,并通过类似WebSocket的抽象方式呈现它们
 4、SockJS旨在适用于所有现代浏览器和不支持WebSocket协议的环境。


stompjs:面向文本的消息传递协议。

STOMP与WebSocket 的关系:
  1、HTTP协议解决了web浏览器发起请求以及web服务器响应请求的细节,假设HTTP协议不存在,
     只能使用TCP套接字来编写web应用,你可能认为这是一件疯狂的事情;
  2、直接使用WebSocket(SockJS)就很类似于使用TCP套接字来编写web应用,因为没有高层协议,
     就需要我们定义应用间发送消息的语义,还需要确保连接的两端都能遵循这些语义;
  3、同HTTP在TCP套接字上添加请求-响应模型层一样,STOMP在WebSocket之上提供了一个基于帧的
     线路格式层,用来定义消息语义.

2、websocket广播、单播、组播

单播(Unicast):
   点对点,私信私聊


广播(Broadcast)(所有人):
   游戏公告,发布订阅


多播也称为组播:
  特定的群体消息通信

3、广播基本Demo

webjars:
   1、方便统一管理
   2、主要解决前端框架版本不一致,文件混乱等问题
   3、把前端资源,打包成jar包,借助maven工具进行管理


<!-- webSocket-->

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

		<dependency>
			<groupId>org.webjars</groupId>
			<artifactId>webjars-locator</artifactId>
		</dependency>

		<dependency>
			<groupId>org.webjars</groupId>
			<artifactId>sockjs-client</artifactId>
			<version>1.0.2</version>
		</dependency>

		<dependency>
			<groupId>org.webjars</groupId>
			<artifactId>stomp-websocket</artifactId>
			<version>2.3.3</version>
		</dependency>

		<dependency>
			<groupId>org.webjars</groupId>
			<artifactId>bootstrap</artifactId>
			<version>3.3.7</version>
		</dependency>

		<dependency>
			<groupId>org.webjars</groupId>
			<artifactId>jquery</artifactId>
			<version>3.1.0</version>
		</dependency>


        <!-- webSockte-->
---不是前后分离的Demo HTML Demo暂不放了 核心代码就这些

---前端使用sockJs+stompJs

//定义一个Stopm客户端
var stompClient = null;


//连接后端websocket 

function connect() {
    var socket = new SockJS('/endpoint-websocket'); //连接上端点(基站)
    
    stompClient = Stomp.over(socket);			//用stomp进行包装,规范协议
    stompClient.connect({}, function (frame) {	
        //订阅后端消息主题 '/topic/game_chat' function是后端推送消息前端接受回调
        stompClient.subscribe('/topic/game_chat', function (result) {
        	console.info(result)
        });
    });
}

//断开连接
function disconnect() {
    if (stompClient !== null) {
        stompClient.disconnect();
    }
}

//前端向后端发送消息
function sendName() {
    var message = "消息测试";
    stompClient.send("/app/v1/chat", {}, JSON.stringify({'content':message}));
}
------后端代码 原生编码

创建接收与发送module
public class InMessage {

   private String fromName;


   private String toName;

   private String content;

   private Date time;

   ..........
   ..........

public class OutMessage {

   private String fromName;

   private String content;

   private Date time = new Date(  );
   ..........
   ..........

创建消息controller

@Controller
public class GameV1Controller {

    /**
     * @MessageMapping路径映射 针对于websocket消息映射
     * @SendTo  发送给前端一个topic主题
     * @param inMessage
     * @return
     */
    @MessageMapping("/v1/chat")
    @SendTo("/topic/game_chat")
    public OutMessage onVOneMessage(InMessage inMessage){

        return new OutMessage(inMessage.getContent());

    }
}


创建webSocket配置文件

package com.lmr.config;

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

/**
 * Created by ldd on 2019/12/30.
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{


    //注册端点(基站) 任何发布订阅消息时吗,需要连接此端点
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        /**
         *  /endpoint-websocket:基站名称
         *  setAllowedOrigins( "*" ):非必须 *表示允许其他域进行连接
         *  withSockJS:表示支持sockJs连接
         */
         registry.addEndpoint( "/endpoint-websocket" ).setAllowedOrigins( "*" ).withSockJS();
    }

    /**
     * 配置消息代理(中介)
     * enableSimpleBroker:服务端推送给客户端的路径前缀
     * setApplicationDestinationPrefixes:客户端发送给服务端的路径前缀
     *
     * @param registry
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker( "/topic" );
        registry.setApplicationDestinationPrefixes( "/app" );
    }
}




4、@SendTo注解和SimpMessagingTemplate的区别

1、SendTo 不通用,固定发送给指定的订阅者
2、SimpMessagingTemplate 灵活,支持多种发送方式
-----使用SimpMessagingTemplate模板推送消息

----前端只需要修改sendName方法
function sendName() {
    var objMessage = {
        toName:"/topic/game_chat",
        content:$("#content").val()
    }
    stompClient.send("/app/v2/chat", {}, JSON.stringify(objMessage));
}
-----后端服务器代码编写

创建WebSocketService

package com.lmr.service;

import com.lmr.module.InMessage;
import com.lmr.module.OutMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;

/**
 * Created by ldd on 2019/12/31.
 */
@Service
public class WebSocketService {

    //注入消息模板
    @Autowired
    private SimpMessagingTemplate messagingTemplate;


    public void sendTopicMessage(InMessage inMessage){

        messagingTemplate.convertAndSend( inMessage.getToName(),new OutMessage( inMessage.getContent() +"Template") );
    }
}




创建V2 Controller
package com.lmr.controller.v2;

import com.lmr.module.InMessage;
import com.lmr.service.WebSocketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;

/**
 * Created by ldd on 2019/12/30.
 */
@Controller
public class GameV2Controller {


    @Autowired
    private WebSocketService webSocketService;


    /**
     * @MessageMapping路径映射 针对于websocket消息映射
     * @param inMessage
     * @return
     */
    @MessageMapping("/v2/chat")
    public void onVOneMessage(InMessage inMessage){

        webSocketService.sendTopicMessage( inMessage );
    }
}



5、websocket 4类的监听器介绍和使用 

注意点:
   1、需要监听器类需要实现接口ApplicationListener<T> T表示事件类型,下列几种都是对应的websocket事件类型
   

 websocket模块监听器类型:
	SessionSubscribeEvent 	订阅事件
	SessionUnsubscribeEvent	取消订阅事件
	SessionDisconnectEvent 	断开连接事件
	SessionConnectEvent 	建立连接事件


@Component
public class SessionConnectEventListener implements ApplicationListener<SessionConnectEvent>{

    /**
     * 连接事件监听器
     * @param sessionConnectEvent
     */

    @Override
    public void onApplicationEvent(SessionConnectEvent sessionConnectEvent) {
        StompHeaderAccessor wrap = StompHeaderAccessor.wrap( sessionConnectEvent.getMessage() );
        System.out.println("连接事件监听器"+wrap.getMessage());
    }
}



@Component
public class SessionDisconnectEventListener implements ApplicationListener<SessionDisconnectEvent>{

    /**
     * 断开连接事件监听器
     * @param sessionDisconnectEvent
     */

    @Override
    public void onApplicationEvent(SessionDisconnectEvent sessionDisconnectEvent) {
        StompHeaderAccessor wrap = StompHeaderAccessor.wrap( sessionDisconnectEvent.getMessage() );
        System.out.println("断开连接事件监听器"+wrap.getMessage());
    }
}



@Component
public class SessionSubscribeEventListener implements ApplicationListener<SessionSubscribeEvent>{

    /**
     * 订阅事件监听器
     * @param sessionSubscribeEvent
     */

    @Override
    public void onApplicationEvent(SessionSubscribeEvent sessionSubscribeEvent) {
        StompHeaderAccessor wrap = StompHeaderAccessor.wrap( sessionSubscribeEvent.getMessage() );
        System.out.println("订阅事件监听器"+wrap.getMessage());
    }
}



@Component
public class SessionUnsubscribeEventListener implements ApplicationListener<SessionUnsubscribeEvent>{

    /**
     * 取消订阅事件监听器
     * @param sessionUnsubscribeEvent
     */

    @Override
    public void onApplicationEvent(SessionUnsubscribeEvent sessionUnsubscribeEvent) {
        StompHeaderAccessor wrap = StompHeaderAccessor.wrap( sessionUnsubscribeEvent.getMessage() );
        System.out.println("取消订阅事件监听器"+wrap.getMessage());
    }
}

6、websocket结合SpringBoot相关拦截器使用

拦截器介绍,springBoot结合websocket相关拦截器使用,握手拦截器的开发和使用
  HandshakeInterceptor


实现步骤:
   1、编写一个类,实现一个接口HandshakeInterceptor;
   2、在websocket配置里面启用.addInterceptors(new 自定义类())



注意:
  1、Http握手拦截器,前端连接基站才会触发
  2、前端推送后台消息@MessageMapping 不会触发握手拦截器
 



package com.lmr.intecepter;

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Map;

/**
 * Created by ldd on 2020/1/2.
 *
 *
 * Http握手拦截器,前端连接基站才会触发
 * 前端推送后台消息@MessageMapping 不会触发握手拦截器
 */
@Component
public class HttpHandShakeIntecepter implements HandshakeInterceptor{

    @Override
    public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {

        ServletServerHttpRequest servletRequest = (ServletServerHttpRequest)serverHttpRequest;
        HttpServletRequest request = servletRequest.getServletRequest();
        HttpSession session = request.getSession();
        System.out.println(session.getId());
        Cookie[] cookies = request.getCookies();
        for(int i=0;i<cookies.length;i++){
            System.out.println("cookies-name:"+cookies[i].getName()+"cookies-value:"+cookies[i].getValue());
        }
        System.out.println("webSocket前置拦截器");
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, @Nullable Exception e) {
        System.out.println("webSocket后置拦截器");
    }
}





package com.lmr.config;

import com.lmr.intecepter.HttpHandShakeIntecepter;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

/**
 * Created by ldd on 2019/12/30.
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{


    //注册端点(基站) 任何发布订阅消息时吗,需要连接此端点
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        /**
         *  /endpoint-websocket:基站名称
         *  setAllowedOrigins( "*" ):非必须 *表示允许其他域进行连接
         *  withSockJS:表示支持sockJs连接
         */
         registry.addEndpoint( "/endpoint-websocket" ).addInterceptors( new HttpHandShakeIntecepter() ).setAllowedOrigins( "*" ).withSockJS();
    }

    /**
     * 配置消息代理(中介)
     * enableSimpleBroker:服务端推送给客户端的路径前缀
     * setApplicationDestinationPrefixes:客户端发送给服务端的路径前缀
     *
     * @param registry
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker( "/topic","/chat" );
        registry.setApplicationDestinationPrefixes( "/app" );
    }
}





7、channel频道拦截器使用

使用方法:

  1、ChannelInterceptorAdapter 频道拦截器适配器,具体实现的接口是ChannelIntecepter
  2、需要ChannelInterceptorAdapter子类重写override对应的方法,实现自己的逻辑,
     主要是public void postSend(Message<?> message, MessageChannel channel, boolean sent) 
  3、ChannelInterceptorAdapter子类需要在配置Websocket的配置里面加入
  4、在配置类里面加入
	  @Override
	  public void configureClientInboundChannel(ChannelRegistration registration) {
		registration.interceptors( new SocketChannelIntecepter());
	  }

	  @Override
	  public void configureClientOutboundChannel(ChannelRegistration registration) {
		registration.interceptors( new SocketChannelIntecepter());
	  }




package com.lmr.intecepter;

import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * Created by ldd on 2020/1/2.
 * 频道拦截器
 */
@Component
public class SocketChannelIntecepter extends ChannelInterceptorAdapter {


    /**
     * 在消息被实际发送到频道之前调用
     */
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        System.out.println( "消息被实际发送到频道之前调用->preSend" );

        return super.preSend( message, channel );
    }

    /**
     * 发送消息调用后立即调用
     */
    @Override
    public void postSend(Message<?> message, MessageChannel channel,
                         boolean sent) {
        System.out.println( "发送消息调用后立即调用->postSend" );

        StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap( message );//消息头访问器

        if (headerAccessor.getCommand() == null) return;// 避免非stomp消息类型,例如心跳检测


        Map<String, Object> objectMap = headerAccessor.getSessionAttributes();
        if(objectMap == null || objectMap.size() <= 0) return;

        String sessionId = objectMap.get( "sessionId" ).toString();
        System.out.println( "SocketChannelIntecepter -> sessionId = " + sessionId );

        switch (headerAccessor.getCommand()) {
            case CONNECT:
                System.out.println("连接成功");
                break;
            case DISCONNECT:
                System.out.println("断开连接");
                break;
            case SUBSCRIBE:
                System.out.println("订阅消息");
                break;

            case UNSUBSCRIBE:
                System.out.println("取消订阅");
                break;
            default:
                break;
        }

    }


    /**
     * 在完成发送之后进行调用,不管是否有异常发生,一般用于资源清理
     */
    @Override
    public void afterSendCompletion(Message<?> message, MessageChannel channel,
                                    boolean sent, Exception ex) {
        System.out.println( "完成发送之后进行调用->afterSendCompletion" );
        super.afterSendCompletion( message, channel, sent, ex );
    }
}








package com.lmr.config;

import com.lmr.intecepter.HttpHandShakeIntecepter;
import com.lmr.intecepter.SocketChannelIntecepter;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

/**
 * Created by ldd on 2019/12/30.
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{


    //注册端点(基站) 任何发布订阅消息时吗,需要连接此端点
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        /**
         *  /endpoint-websocket:基站名称
         *  setAllowedOrigins( "*" ):非必须 *表示允许其他域进行连接
         *  withSockJS:表示支持sockJs连接
         */
         registry.addEndpoint( "/endpoint-websocket" ).addInterceptors( new HttpHandShakeIntecepter() ).setAllowedOrigins( "*" ).withSockJS();
    }

    /**
     * 配置消息代理(中介)
     * enableSimpleBroker:服务端推送给客户端的路径前缀
     * setApplicationDestinationPrefixes:客户端发送给服务端的路径前缀
     *
     * @param registry
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker( "/topic","/chat" );
        registry.setApplicationDestinationPrefixes( "/app" );
    }




    //配置客户端进入通道
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors( new SocketChannelIntecepter() );
    }

    //配置客户端退出通道
    public void configureClientOutboundChannel(ChannelRegistration registration) {
        registration.interceptors( new SocketChannelIntecepter() );
    }


}





----连接


C54EF41FACA3D49B02C3948434A69EF8
cookies-name:JSESSIONIDcookies-value:C54EF41FACA3D49B02C3948434A69EF8
webSocket前置拦截器
webSocket后置拦截器
消息被实际发送到频道之前调用->preSend
发送消息调用后立即调用->postSend
完成发送之后进行调用->afterSendCompletion
连接事件监听器null
消息被实际发送到频道之前调用->preSend
发送消息调用后立即调用->postSend
完成发送之后进行调用->afterSendCompletion
消息被实际发送到频道之前调用->preSend
发送消息调用后立即调用->postSend
完成发送之后进行调用->afterSendCompletion
订阅事件监听器null




---发送消息
消息被实际发送到频道之前调用->preSend
发送消息调用后立即调用->postSend
完成发送之后进行调用->afterSendCompletion
接收消息1111
消息被实际发送到频道之前调用->preSend
发送消息调用后立即调用->postSend
完成发送之后进行调用->afterSendCompletion




---断开连接
消息被实际发送到频道之前调用->preSend
发送消息调用后立即调用->postSend
完成发送之后进行调用->afterSendCompletion
消息被实际发送到频道之前调用->preSend
断开连接事件监听器null
发送消息调用后立即调用->postSend
消息被实际发送到频道之前调用->preSend
完成发送之后进行调用->afterSendCompletion
发送消息调用后立即调用->postSend
完成发送之后进行调用->afterSendCompletion
消息被实际发送到频道之前调用->preSend
发送消息调用后立即调用->postSend
完成发送之后进行调用->afterSendCompletion






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值