websocket原理和应用入门

995 阅读5分钟

原理

WebSocket是一种在单个TCP连接上进行全双工通信的协议。

WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。

在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

http协议中有keep-alive,只是把多个HTTP请求合并到一个tcp连接里进行传输, Websocket 是一个新协议,跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范,也就是说它是HTTP协议上的一种补充。



Websocket协议


HTTP协议中,HTTP的生命周期通过 Request 来界定,一个 Request对应 一个 Response,客户端发起一个Request,在收到Response之后,一次HTTP请求生命周期结束。而且必须客户端先发送一个Request,服务端才能给客户端发送Response,服务器端属于被动的,不能主动发起。WebSocket的基于长连接,可以双向通信,生命周期如下:

有如下特点:


(1)建立在 TCP 协议之上,服务器端的实现比较容易。

(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

(3)数据格式比较轻量,性能开销小,通信高效。

(4)可以发送文本,也可以发送二进制数据。

(5)没有同源限制,客户端可以与任意服务器通信。

(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

WebSocket借用HTTP协议来完成首次握手,随后使用WebSocket进行通信。

HTTP握手请求如下:


GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: example.com


Upgrade: websocket Connection: Upgrade 表示协议升级为websocket协议。

Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== 客户端也就是浏览器或者其他终端随机生成一组16位的随机base64编码的串

Sec-WebSocket-Protocol: chat, superchat 使用协议

Sec-WebSocket-Version: 13 当前使用协议的版本号

握手返回如下:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat


状态码和描述 101 Switching Protocols表示协议切换成功

Upgrade: websocket Connection: Upgrade 表示协议升级成功为websocket 

Sec-WebSocket-Accept 这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key

Sec-WebSocket-Protocol 则是表示最终使用的协议


前端代码:

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) {
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
console.log( "Received Message: " + evt.data);
ws.close();
};

ws.onclose = function(evt) {
console.log("Connection closed.");
};


运行代码


不支持WebSocket

不支持WebSocket的场景有:

  1. 浏览器不支持
  2. Web容器不支持,如tomcat7以前的版本不支持WebSocket
  3. 防火墙不允许
  4. Nginx没有开启WebSocket支持

基于 SockJS 的 WebSocket


在不支持WebSocket的情况下,也可以很简单地实现WebSocket的功能的,方法就是使用 SockJS。它会优先选择WebSocket进行连接,但是当服务器或客户端不支持WebSocket时,会自动在 XHR流、XDR流、iFrame事件源、iFrame HTML文件、XHR轮询、XDR轮询、iFrame XHR轮询、JSONP轮询 这几个方案中择优进行连接。


服务端
在启动WebSocket的配置中,你需要做的所有事情就是加上 withSockJS()

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
// withSockJS 声明启用支持 sockJS
webSocketHandlerRegistry.addHandler(marcoHandler(), "/echo").withSockJS();

客户端
在客户端需要引入SockJS库,然后把 new WebSocket(url); 替换成 new SockJS(url);

SockJS类和WebSocket类是兼容的,所以可以直接替换

<script type="text/javascript" src="/resources/js/sockjs-1.0.0.min.js"></script>
var sock = new SockJS(url);


应用

以下是一个基于spring、sockjs的一个样例

  1. 引入依赖
    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId></dependency>
  2. 添加配置

    @Configuration@EnableWebSocketMessageBrokerpublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) {
          registry        .addEndpoint("/websocket/tracker")        .setAllowedOrigins("*")        .withSockJS();
     } @Override public void configureMessageBroker(MessageBrokerRegistry config) {
            config.enableSimpleBroker("/topic", "/user");        config.setUserDestinationPrefix("/user");        config.setApplicationDestinationPrefixes("/app");
     }} 

    1)@EnableWebSocketMessageBroker注解用于开启使用STOMP协议来传输基于代理(MessageBroker)的消息,这时候控制器(controller)开始支持@MessageMapping,就像是使用@requestMapping一样。

    2).addEndpoint("/websocket/tracker") 注册一个 Stomp 端点,客户端连接服务端是使用;.setAllowedOrigins("*")表示允许跨域;.withSockJS()表示指定使用SockJS协议。SockJs是一个WebSocket的通信js库,Spring对这个js库进行了后台的自动支持,如果使用它不需要进行过多配置。

    3)config.enableSimpleBroker("/topic", "/user") 启用一个简单的message broker并配置一个或多个前缀来过滤针对代理的目的地,客户端订阅这个地址,服务端可以向改地址发送消息;

    config.setApplicationDestinationPrefixes("/app");


  3. 提供接口
     @MessageMapping("/hello") @SendTo("/topic/greetings") public Greeting greeting(HelloMessage message) throws Exception {        System.out.println("收到:" + message.toString() + "消息");        return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!"); }

    1)@MessageMapping用于设置URL映射地址,浏览器向服务器发起请求,需要通过该地址,客户端发送消息时,需要访问/app/hello。

    2)@SendTo("/topic/greetings") 设置目的地,服务器想客户端发送消息,这里的目的地是站在服务端的角度对客户端而言。客户端也需要设置相同的地址,而且必须使用/topic前戳,前面也已经讲述。

  4. 发送消息
     SimpMessagingTemplate.convertAndSendToUser(String user, String destination, Object payload)
    SimpMessagingTemplate.convertAndSend(D destination, Object payload)