WebSocket和Stomp协议

1. webSocket介绍

我们知道http协议是无状态协议,每次请求都只能由客户端发起,服务器进行响应。但是服务器不能主动发送消息给客户端。这种单向的协议在很多的业务场景中不适用,比如消息推送,实时消息详情等。在使用websocket前,我们通常可以使用轮询或者长链接来实现这种实时消息的需求。

1.1. 轮询

由客户端或者浏览器定时发request,然后服务器返回最新的数据给客户端。缺点很明显,浏览器需要不断向服务器发送请求,然而http的request的header非常长,但是实际需要的业务数据却是一个很小的值。需要消耗很多服务器资源和带宽资源。


01

1.2. 长链接

http 1.1 默认保持长链接,数据传输完成保持tcp链接不断开,等待在相同域名下继续使用这个通道传输数据。客户端的长链接不能无限期的拿着,会有一个超时时间,服务器有时候会告诉客户端超时时间,下图中的Keep-Alive: timeout=20,表示这个TCP通道可以保持20秒。另外还可能有max=XXX,表示这个长连接最多接收XXX次请求就断开。对于客户端来说,如果服务器没有告诉客户端超时时间也没关系,服务端可能主动发起四次握手断开TCP连接,客户端能够知道该TCP连接已经无效;另外TCP还有心跳包来检测当前连接是否还活着。


02

1.3. websocket

Websocket是html5提出的一个协议规范,是为解决客户端与服务端实时通信。本质上是一个基于tcp,先通过HTTP/HTTPS协议发起一条特殊的http请求进行握手后创建一个用于交换数据的TCP连接。只需要要做一个握手的动作,在建立连接之后,双方可以在任意时刻,相互推送信息。同时,服务器与客户端之间交换的头信息很小。


03

看起来websocket和http的长链接都可以应用于实时消息的场景,但是http的长链接对每个请求仍然要单独发 header,Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache,默认为15s)中设定这个时间。keep-alive双方并没有建立正真的连接会话,服务端可以在任何一次请求完成后关闭。而WebSocket 它本身就规定了是真正的、双工的长连接,两边都必须要维持住连接的状态。

通过服务端和客户端的交互报文来对比webSocket通讯和传统的http的不同:

在客户端,实例化一个新的WebSocket客户端对象,请求类似的wss:yourdomain:port/path的webSocket url。客户端的webScoket对象会自动解析并识别为WebSocket请求,并链接服务端口,执行双方握手过程,客户端发送数据格式类似:

GET /webfin/websocket/ HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
Origin: http://localhost:8080
Sec-WebSocket-Version: 13

客户端发起的websocket链接报文类似于传统的http报文,Upgrade:websocket表明这是一个websocket�请求,Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==是客户端发送的一个base64编码秘文

服务端收到报文后返回类似的数据格式:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=

两端的WebSocket连接握手成功, 后续就可以进行TCP通讯了.查阅WebSocket协议栈了解WebSocket客户端和服务端更详细的交互数据格式。

PingPong:一个关于维持链接的websocket设计技术细节

虽然说webSocket解决了服务器和客户端的链接问题,但是网络应用除了客户端和服务器还存在中间的网络链路,一个http/websocket链接往往还需要进过无数的路由,防火墙,在这个过程中,中间节点的处理方法可能会让人想不到,这些坑爹的中间节点可能会认为一份连接在一段时间内没有数据发送就等于失效,它们会自作主张的切断这些连接。在这种情况下,不论服务器还是客户端都不会收到任何提示,它们只会一厢情愿的以为彼此间的红线还在,徒劳地一边又一边地发送抵达不了彼岸的信息。而计算机网络协议栈的实现中又会有一层套一层的缓存,除非填满这些缓存,你的程序根本不会发现任何错误。

解决方案:

WebSocket 的设计者们也早已想过。就是让服务器和客户端能够发送 Ping/Pong Frame。这种 Frame 是一种特殊的数据包,它只包含一些元数据而不需要真正的 Data Payload,可以在不影响 Application 的情况下维持住中间网络的连接状态。

总结:在实时消息的应用场景下,比起轮询,长链接等方案,webSocket确实给我们提供了一个比较完美的解决方案。

2.STOMP传输协议介绍

STOMP 中文为: 面向消息的简单文本协议

websocket定义了两种传输信息类型:文本信息和二进制信息。类型虽然被确定,但是他们的传输体是没有规定的。所以,需要用一种简单的文本传输类型来规定传输内容,它可以作为通讯中的文本传输协议。

STOMP是基于帧的协议,客户端和服务器使用STOMP帧流通讯

一个STOMP客户端是一个可以以两种模式运行的用户代理,可能是同时运行两种模式。

  • 作为生产者,通过SEND框架将消息发送给服务器的某个服务
  • 作为消费者,通过SUBSCRIBE制定一个目标服务,通过MESSAGE框架,从服务器接收消息。

例如:

COMMAND
header1:value1
header2:value2

Body^@

注:帧以commnand字符串开始,以EOL结束。其中包括可选回车符(13字节),紧接着是换行符(10字节)。command下面是0个或多个<key>:<value>格式的header条目, 每个条目由EOL结束。一个空白行(即额外EOL)表示header结束和body开始。body连接着NULL字节。本文档中的例子将使用^@代表NULL字节。NULL字节可以选择跟多个EOLs。欲了解更多关于STOMP帧的详细信息,请参阅STOMP1.2协议规范

2.1 STOMP 1.2 协议

STOMP 1.2 clients 必须设置以下headers:

1.accept-version: clients支持的STOMP的版本号。

2.host:client希望连接的虚拟主机名字

可选择设置以下headers:

1.login: 用于在server验证的用户id

2.passcode: 用于在server验证的密码

3.heart-beat: 心跳设置

注:STOMP协议大小写敏感

2.2 常用Command

  • CONNECT
  • CONNECTED
  • SEND
  • SUBSRIBE
  • UNSUBSRIBE
  • BEGIN
  • COMMIT
  • ABORT
  • ACK
  • NACK
  • DISCONNECT

2.2.1 CONNECT

STOMP客户端通过初始化一个数据流或者TCP链接发送CONNECT帧到服务端,例如:

CONNECT

accept-version:1.2
host:stomp.test

^@

2.2.2 CONNECTED

如果服务端接收了链接意图,它回回复一个CONNECTED帧:

CONNECTED

version:1.2

^@

正常链接后客户端和服务端就可以正常收发信息了。

2.2.3 SEND

客户端主动发送消息到服务器,例如:

SEND
destination:/queue/a
content-type:text/plain

I am send body
^@

注: 必须包含destination目标地址,如果没有content-type,默认表示传递的二进制.

2.2.4 SUBSCRIBE

客户端注册给定的目的地,被订阅的目的地收到的任何消息将通过MESSAGE Frame发送给client。 ACK 控制着确认模式。

SUBSCRIBE
id:0
destination:/queue/foo
ack:client

^@

id:一个单连接可以对应多个开放的servers订阅,这个id用来客户端和服务端处理与订阅消息和取消订阅相关的动作。

ack:可用的值有auto, client,client-individual, 默认为auto.

ackauto时,client收到server发来的消息后不需要回复ACK帧.server假定消息发出去后client就已经收到。这种模式下可能导致服务端向客户端发送的消息丢失

ackclient时, 客户端收到服务端信息之后必须回复ACK帧。如果在收到客户端回复的ACK之前连接断开,服务端会认为这个消息没有被处理而改发给其他客户端。客户端回复的ACK会被当做累加的处理。这意味着对信息的确认操作不仅仅是确认了这单个的消息,还确认了这个订阅之前发送的所有消息(即接收到一个确认消息就会把之前的消息一起确认掉,批量操作)。

由于client不能处理某些消息,所以client应该发送NACK帧去告诉server它不能消费这些消息。

ack模式是client-individual,确认操作就跟client模式一样,除了ACKNACK不是累加的。这意味着当后来的一个消息得到ACKNACK之后,之前的那个消息没有被ACKNACK,它需要单独的确认。

2.2.5 UNSUBSRIBE

UNSUBSCRIBE用来移除一个已经存在订阅,一旦一个订阅被从连接中取消,那么客户端就再也不会收到来自这个订阅的消息。

UNSUBSCRIBE

id:0

^@

由于一个连接可以添加多个服务端的订阅,所以id头是UNSUBSCRIBE必须包含的,用来唯一标示要取消的是哪一个订阅。id的值必须是一个已经存在的订阅的标识。

2.2.6 ACK

ACK是用来在clientclient-individual模式下确认已经收到一个订阅消息的操作。在上述模式下任何订阅消息都被认为是没有被处理的,除非客户端通过回复ACK确认。

ACK

id:12345

transaction:tx1

^@

ACK中必须包含一个id头,头域内容来自对应的需要确认的MESSAGE的ack头。可以选择的指定一个transaction头,标示这个消息确认动作是这个事务内容的一部分。

2.2.7 NACK

NACK是ACK的反向,它告诉服务端客户端没有处理该消息。服务端可以选择性的处理该消息,重新发送到另一个客户端或者丢弃它或者把他放到无效消息队列中记录。

NACK包含和ACK相同的头信息:id(必须)和transaction(非必须)。

2.2.8 BEGIN

BEGIN用于开启一个事务-transaction。这种情况下的事务适用于发送消息和确认已经收到的消息。在一个事务期间,任何发送和确认的动作都会被当做事务的一个原子操作。

BEGIN

transaction:tx1

^@

帧中transaction头是必须的,并且transaction的标示会被用在SENDCOMMITABORTACKNACK中,使之与该事务绑定。同一个链接中的不同事务必须使用不同的标示。
当客户端发送一个DISCONNECT或者TCP链接由于任何原因断开时,任何打开的但是还没有被提交的事务都会被默认的立即中断。

2.2.9 COMMIT

用来提交一个事务到处理队列中,帧中的transaction头是必须得,用以标示是哪个事务被提交。

COMMIT

transaction:tx1

^@

2.2.10 ABORT

ABORT用于中止正在执行的事务,帧中的transaction头是必须得,用以标示是哪个事务被终止。

ABORT

transaction:tx1

^@

2.2.11 DISCONNECT

客户端可以通过DISCONNECT帧表示正常断开链接

2.2.12 MESSAGE

MESSAGE用于传输从服务端订阅的消息到客户端。

MESSAGE中必须包含destionation头,用以表示这个消息应该发送的目标。如果这个消息被使用STOMP发送,那么这个destionation应该与相应的SEND帧中的目标一样。

MESSAGE中必须包含message-id头,用来唯一表示发送的是哪一个消息,以及subscription头用来表示接受这个消息的订阅的唯一标示。

如果收到的订阅消息明确表示需要确认,那么MESSAGE中应该包含一个任意值的ack头,这个值被用来在回复确认时标示这条信息。

MESSAGE如果有body内容,则必须包含content-lengthcontent-type头。

MESSAGE

content-length:100

content-type:text/plain

destination:/queue/a

message-id:007

subscription:0

Hello queue a

^@

2.2.13 ERROR

如果连接过程中出现什么错误,服务端就会发送ERROR。在这种情况下,服务端发出ERROR之后必须马上断开连接。

2.2.14 RECEIPT

每当服务端收到来自客户端的需要receipt的帧时发送给客户端

3.结合webScocket和Stomp协议

蜗牛在正文的实时消息模块就结合了webScocket和Stomp。基于websocket的一层STOMP封装,让业务端只需关心数据本身,不需要太过关心文本协议。当然还是需要了解一些STOMP协议各个Frame的概念和应用场景。

现将项目中的工程抽成一个公有库,NESTOMP内含接入文档和Demo。希望以后在有类似于实时消息场景的需求上给大家一个方案预选。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 摘要 STOMP是一个简单的可互操作的协议, 被用于通过中间服务器在客户端之间进行异步消息传递。它定义了一种在客户...
    Leon622阅读 21,950评论 3 7
  • 原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-WebSo...
    敢梦敢当阅读 8,882评论 0 50
  • WebSocket学习 为什么需要WebSocket 以往使用的HTTP协议存在一个缺陷,通信只能由客户端发起。 ...
    ChaLLengerZeng阅读 1,709评论 0 1
  • 1.TCP报头格式 UDP报头格式 TCP报头格式 UDP报头格式 具体的各部分解释看 TCP报文格式详解 - ...
    杰伦哎呦哎呦阅读 2,431评论 0 5
  • 英语: 2013年第三篇,我们的未来一片光明,第四篇,州政府的权利,联邦政府的权利,最高法院,三权分立,check...
    疯狂太阳花阅读 143评论 0 0