Spring Websocket 实践操作

Spring Websocket 实践操作

​一、Websocket与http

webSocket是HTML5的一种新协议,它实现了服务端与客户端的全双工通信,建立在传输层,tcp协议之上,即浏览器与服务端需要先建立tcp协议,再发送webSocket连接建立请求。它是一个持久层的协议,相对于HTTP这种非持久的协议来说,因为http本身无状态,若要保持状态,则需要通过cache session进行,造成每次数据请求时会携带大量信息。

websocket好处:

  • 一个Web客户端只建立一个TCP连接;

  • Websocket服务端可以推送(push)数据到web客户端;

  • 有更加轻量级的头,减少数据传送量。通过协议promote的方式将http升级成websocket。

二、轮询请求后台服务器对比

(1)ajax轮询

ajax轮询就是让浏览器隔个几秒就发送一次请求,询问服务器是否有新的信息。

(2)long poll(长轮询)

long poll的原理跟ajax轮询差不多,都说采用轮询方式,但是采用的是阻塞模型。也就是说客户端发起请求,如果没消息,就一直不返回Response给客户端。知道有消息才返回,返回完之后,客户端再次建立连接。

上面两种方式是不断建立HTTP连接,关闭HTTP协议,由于HTTP是非状态性的,每次都要重新传输鉴别信息,来告诉服务端你是谁。然后等待服务端处理,可以体现HTTP协议的另外一个特点,被动性。

(3)WebSocket

WebSocket解决了HTTP的被动性和反复解析HTTP协议消耗资源的问题。

  • 解决HTTP的被动性的问题:当服务器完成协议升级后(HTTP->Websocket),服务端就可以主动推送信息给客户端啦。解决了上面同步有延迟的问题。

  • 解决服务器上消耗资源的问题:其实我们所用的程序是要经过两层代理的,即HTTP协议在Nginx等服务器的解析下,然后再传送给相应的Handler(php等)来处理。简单地说,我们有一个非常快速的 接线员(Nginx) ,他负责把问题转交给相应的 客服(Handler) 。

    由于Websocket只需要一次HTTP握手,所以说整个通讯过程是建立在一次连接/状态中,也就避免了HTTP的非状态性,服务端会一直知道你的信息,直到你关闭请求,这样就解决了接线员要反复解析HTTP协议,还要查看identity info的信息。

目前唯一的问题是:不兼容低版本的IE。

三、Spring Websocket

背景:spring 让工作变得更加简单方便。由于使用原生的API太麻烦,而且不同的Web 容器拥有对于API的不同的实现,这个对于使用者来说特别不友好,spring 将这些不同点进行屏蔽,抽象出一套自己的称呼,比如WebsocketSession,将这一套东西转换为Tomcat 支持的WebSocket 或者Jboss 支持的Websocket 或者Jetty 支持的Websocket。

3.1使用@EnableWebSocket注解,开启对WebSocket的支持功能

@EnableWebSocket的源码如下:

@Retention(RetentionPolicy.RUNTIME)   //定义注解在JVM运行时保留
@Target(ElementType.TYPE)    //定义注解应用于类
@Documented
@Import(DelegatingWebSocketConfiguration.class) //引用DelegatingWebSocketConfiguration类,开启使用webSocket协议
public @interface EnableWebSocket {
}

@EnableWebSocket中引用的DelegatingWebSocketConfiguration类的的源码如下:

@Configuration
public class DelegatingWebSocketConfiguration extends WebSocketConfigurationSupport {

    private final List<WebSocketConfigurer> configurers = new ArrayList<WebSocketConfigurer>();


    @Autowired(required = false)
    public void setConfigurers(List<WebSocketConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addAll(configurers);
        }
    }

   //注册websocket
    @Override
    protected void registerWebSocketHandlers(WebSocketHandlerRegistry                 registry) {
        for (WebSocketConfigurer configurer : this.configurers) {
            configurer.registerWebSocketHandlers(registry);
        }
    }
}

3.2 简单的Websocket使用流程

3.2.1 后台实现

(1)修改pom.xml文件,引入Websocket依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>4.2.2.RELEASE</version>
</dependency>

(2)Websocket控制器

@EnableWebMvc
@EnableWebSocket
public class MyConfigurerAdapter extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
   @Inject
   private MySocketHandler mySocketHandler;

   @Override
   public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
       //前台:可以使用websocket环境
       registry.addHandler(mySocketHandler, "/my/console").setAllowedOrigins("*").addInterceptors(new HandshakeInterceptor());
     
       
       //前台:不可以使用websocket环境,则使用sockjs进行模拟连接
       registry.addHandler(mySocketHandler, "/sockjs/my/console").setAllowedOrigins("*").addInterceptors(new HandshakeInterceptor()).withSockJS();
     
  }
}

(3)Websocket处理器

@Service
public class MySocketHandler implements WebSocketHandler {
   private Map<String, WebSocketSession> mySession = new Hashtable<>();
   @Inject
private MyService myService;   //自己个人实现的类
   private static Logger logger = Logger.getLogger(MySocketHandler.class);

   //webscoket建立连接之后的处理行数
   @Override
   public void afterConnectionEstablished( WebSocketSession session) throws Exception {
   logger.info("----------webscoket已连接");
  }

   //客户端发送服务器的消息时的处理函数,在这里收到消息之后可以发送消息给前端
   @Override
   public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
logger.info("----------服务器已接收到消息");
       //前端发送的消息只能是字符串格式(可以理解成前端的传参),后台需要解析字符串
       String params = (String) message.getPayload();
       ObjectMapper mapper = new ObjectMapper();
       HashMap<String, Object> map = mapper.readValue(params, HashMap.class);
       String name = (String) map.get("name");
       String id = (String) map.get("id");
       Map<String, Object> result = myServer.selectReportData(name,id);  
       TextMessage reply = new TextMessage(JSONObject.toJSONString(result));  //结果转成JSONObject字符串,放在TextMessage对象中
       try{
      session.sendMessage(reply);   //把数据发送给前端
      }catch(IOException e){
      logger.debug("----------发送信息给前端出错");
      }
  }
   
   //处理传输错误
   @Override
   public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
  logger.info("----------出现传输错误");
  }

   //websocket关闭连接
   @Override
   public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
       logger.info("----------关闭Websocket连接");
  }

  //支持部分消息,默认return false
   @Override
   public boolean supportsPartialMessages() {
       logger.info("----------支持部分信息");
       return false;
  }
}

(4)拦截器

public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor {
 
   // 握手前
   @Override
   public boolean beforeHandshake(ServerHttpRequest request,
                 ServerHttpResponse response, WebSocketHandler wsHandler,
                 Map<String, Object> attributes) throws Exception {
       //在访问接口时候需要带apiKey,如:XXXX链接?apiKey=xxx
       String apiKey = ((ServletServerHttpRequest) request).getServletRequest().getParameter("apiKey");
       attributes.put("apiKey", apiKey);
       return super.beforeHandshake(request, response, wsHandler, attributes);
  }

   // 握手后
   @Override
   public void afterHandshake(ServerHttpRequest request,
                      ServerHttpResponse response, WebSocketHandler wsHandler,
                      Exception ex) {
       super.afterHandshake(request, response, wsHandler, ex);
  }
}

3.2.1 前端的实现

  • 使用原生的Websocket来处理

//点击按钮触发Websocket
$("#export_btn").click(function(){
    var socket;
   if(typeof(WebSocket) == "undefined") {
       console.log("您的浏览器不支持WebSocket");
    }else{
       //实现化WebSocket对象,指定要连接的服务器地址与端口建立连接:ws://路径:端口/项目名/配置的websocket地址
       socket = new WebSocket("ws://localhost:8080/myweb/my/console?apiKey=F5EB285989C74F4B");  
       var params = {
           name: "karry",
           id: "wdfse13352ssasd"
      }

       //打开事件
       socket.onopen = function() {
           console.log("Socket 已打开");
           //发送请求参数
           socket.send(JSON.stringify(params));  
      };

       //获得消息事件
       socket.onmessage = function(msg) {
           console.log(msg.data);
           //发现后台消息进入, 开始处理前端触发逻辑
      };

       //关闭事件
       socket.onclose = function() {
           console.log("Socket已关闭");
      };

       //发生了错误事件
       socket.onerror = function() {
           alert("Socket发生了错误");
           //此时可以尝试刷新页面
      }
  }
})
  • 使用SocketJs来处理

$(function () {
   socket = new SockJS(""); //创建连接
   socket.onopen = function("/my/console?apiKey=F5EB285989C74F4B") {
        socket.send("你好世界")
  };
   socket.onmessage = function(e) {
       //有消息传递到了,可以进行处理
       console.log('message', e.data);
       alert(e.data)
  };
   socket.onclose = function() {
       console.log('close');
  };
   $("#send").click(function () {
           if(socket !=undefined){
           socket.send("message");
      }else{
           console.log("socket is null")
      }
  });
});

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值