websocket一基础

背景


http通信的痛点


单向通信


http的连接是单向的,即客户端可以给服务端主动发送消息,服务端做起响应。但是服务端无法主动向客户端发送消息。


多次建立tcp连接


另外http在每次客户端和服务端的交互中需要在基于tcp的基础上进行握手和挥手的环节,必然会造成额外资源的开销。


历史解决方案


http长链接解决多次tcp连接问题


在http1.1中,出现了http长连接,其特点是保持连接特性,当一次http交互完后该TCP通道并不会关闭,而是会保持一段时间(在不同服务器上时间不一样,可以设置),如果在这段时间内再次发起了http请求就可以直接复用,而不用重新进行握手,从而减少了资源浪费。目前http1.1中,都是默认使用长连接,在请求头中加上
connection:keep-alive
长连接默认保持连接有效时间是2h


轮询解决单向通信问题


由客户端主动每间断一些时间便向服务端发起请求,询问服务端是否有消息进行同步。从而在一定的时间容错范围内,让服务端的消息同步给客户端。


阻塞式响应解决单向通信问题


客户端主动发起请求,服务端收到请求后如果没有响应消息,则进行阻塞,知道服务端有需要响应的信息之后,返回给客户端。然后客户端收到响应之后再次发送消息给服务端进行阻塞,如此反复。


websocket方案


websocket是一种全双工通信的解决方案,即客户端和服务端均可以主动发送消息。


websocket支持


前端


websocket基础需要依赖于html5。之前的版本并没有对websocket进行支持。
目前,支持Html5的浏览器包括Firefox(火狐浏览器)、IE9及其更高版本、Chrome(谷歌浏览器)、Safari、Opera等;国内的傲游浏览器(Maxthon)、以及基于IE或Chromium(Chrome的工程版或称实验版)所推出的360浏览器、搜狗浏览器、QQ浏览器、猎豹浏览器等国产浏览器同样具备支持HTML5的能力。


后端


tomcat 接入了websocket,并支持jsr356规范。可以通过war包的方式或者springboot项目的形式去集成websocket。


websocket协议分析


RFC6455
RFC6455中文版
ws协议分析
RFC6455中定义了webscoket基于tcp以及http的握手、挥手以及协议帧信息


握手


客户端


GET /chat HTTP/1.1
Host: server.example.com
Upgrade: webSocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: websocket
Sec-WebSocket-Version: 13
在http的基础上进行升级(upgrade),升级成websocket协议,websocket协议的版本是13。
Sec-WebSocket-Key 此参数为客户端传递的密钥,会由此生成服务端产生的密钥,并由客户端判断是否进行connection。


服务端


HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: websocket

HTTP状态码响应为101 代表websocket协议升级成功。
Sec-WebSocket-Accept:表示服务器是否接收这个连接,如果有这个字段,这个字段的值必须为客户端提供的|Sec-WebSocket-Key|字段的值与预先定义好的GUID值进行哈希,在进行base64编码。任何其他的值都表明服务器没有接受客户端发起的请求。


协议帧


RFC6455 中有详细的定义


挥手


挥手过程要比打开过程简单的多。 任何一端都可以发送一个Close帧来开始挥手过程,Close帧可能带有部分数据(比如描述关闭的原因以及状态码)。任何一端收到一个Close帧,如果之前没有回复过的话,需要发送Close帧。主动关闭的一端在收到对端返回的响应后,在确定没有数据需要继续接收之后,开始关闭底层连接(shutdown)。


JSR356


JSR356简介


JSR356
JSR356 是一种java语言的websocket协议实现规范。
典型的如tomcat遵循了JSR356协议。


两种支持方式


注解

@ServerEndpoint("/hello") 
public class MyEndpoint { }


暴露地址:ws://ip:port/mycontextroot/hello

// 建立连接
@OnOpen
public void myOnOpen (Session session) {
   System.out.println ("WebSocket opened: "+session.getId());
}

// 消息接收
@OnMessage
public void myOnMessage (String txt) {
   System.out.println ("WebSocket received message: "+txt);
}

// 带返回的消息通信
@OnMessage
public String myOnMessage (String txt) {
   return txt.toUpperCase();
}

// 记录session,直接发送消息
RemoteEndpoint.Basic other = session.getBasicRemote();
other.sendText ("Hello, world");

// 连接关闭
@OnClose
public void myOnClose (CloseReason reason) {
   System.out.prinlnt ("Closing a WebSocket due to "+reason.getReasonPhrase());
}



接口

public class myOwnEndpoint extends javax.websocket.Endpoint {
   public void onOpen(Session session, EndpointConfig config) {...}
   public void onClose(Session session, CloseReason closeReason) {...}
   public void onError (Session session, Throwable throwable) {...}
}

// 接收消息,需要通过onOpen时注册
public void onOpen (Session session, EndpointConfig config) {
   final RemoteEndpoint.Basic remote = session.getBasicRemote();
   session.addMessageHandler (new MessageHandler.Whole<String>() {
      public void onMessage(String text) {
                 try {
                     remote.sendString(text.toUpperCase());
                 } catch (IOException ioe) {
                     // handle send failure here
                 }
             }

   });
}


消息类型


其本质是websocket内置的消息类型。当然也可以自定义。
字符串
接收:onMessage接收消息为String即可
发送:session.getBasicRemote().sendText(text);
二进制/流
接收:onMessage接收消息为byte[]即可
发送:session.getBasicRemote().sendBinary(byteBuffer);
ping消息
接收:此消息会由中间件内置实现,无需处理
发送:session.getBasicRemote().sendPing(byteBuffer);


编解码

@ServerEndpoint(value="/endpoint", encoders = MessageEncoder.class, decoders= MessageDecoder.class)
public class MyEndpoint {
...
}

class MessageEncoder implements Encoder.Text<MyJavaObject> {
   @override
   public String encode(MyJavaObject obj) throws EncodingException {
      ...
   }
}

class MessageDecoder implements Decoder.Text<MyJavaObject> {
   @override 
   public MyJavaObject decode (String src) throws DecodeException {
      ...
   }

   @override 
   public boolean willDecode (String src) {
      // return true if we want to decode this String into a MyJavaObject instance
   }
}



springboot中整合websocket


此demo主要是遵循JSR356 进行示意。
spring也有自己的实现,可以参考进行开发。spring整合websocket官方文档
 

package com.example.websocketdemo.useannotation;

import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;

@ServerEndpoint("/hello")
@Component
public class AnnotationEndpoint {

    @OnOpen
    public void myOnOpen (Session session) {
        System.out.println ("WebSocket opened: "+session.getId());
    }

    /**
     * 也可以带返回值
     * @param session
     * @param txt
     */
//    @OnMessage
//    public void myOnMessage (Session session, String txt) {
//        System.out.println ("WebSocket received message: "+txt);
//    }

    // 发送消息的两种方式
    /**
     * 也可以带返回值
     * @param session
     * @param txt
     */
    @OnMessage
    public String myOnMessage (Session session, String txt) throws IOException {
        // 返回方式1
//        RemoteEndpoint.Basic other = session.getBasicRemote();    // 同步
//        RemoteEndpoint.Async other = session.getBasicRemote();    // 异步
//        other.sendText ("Hello, world");

        // 返回方式2
        System.out.println ("WebSocket received message: "+txt);
        return "这样就可以返回啦";
    }

    @OnClose
    public void myOnClose (CloseReason reason) {
        System.out.println ("Closing a WebSocket due to "+reason.getReasonPhrase());
    }
}

tomcat端点注册

package com.example.websocketdemo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebsocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
      	// 主要是把端点信息注册到tomcat上
        return new ServerEndpointExporter();
    }

}

注:需要引入 spring-boot-starter-websocket 包

思考


当浏览器不支持websocket时,如何处理
点对点消息如何主动发送
广播消息如何主动发送
分布式如何处理上述问题
如何进行鉴权


参考文档


JSR356
RFC6455
RFC6455中文版
ws协议分析
tomcat特定配置
spring整合websocket官方文档

具体的实战会在下一篇分享。

包括stomp、amqp协议,rabbitmq接入

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值