最近工作中使用到Stomp协议,现在做个总结!
Stomp是一个简单的消息文本协议,它的设计核心理念就是简单与可用性,官方文档:[url]http://stomp.github.com/stomp-specification-1.1.html[/url]
现在我们就来实践一下Stomp协议,你需要的是:
1.一个支持stomp消息协议的messaging server(譬如activemq,rabbitmq);
2.一个终端(譬如linux shell);
3.一些基本命令与操作(譬如nc,telnet)
[b][size=medium]1.建立连接[/size][/b]
当我们(Client端)向服务器发送一个CONNECT Frame,就向服务器发起了一个连接请求,此时服务器端返回一个CONNECTED Frame表示建立连接成功,其中头字段version表示采用的stomp协议版本(这里默认是1.0)
[img]http://dl.iteye.com/upload/attachment/0076/5485/fe7ab85e-7311-3dda-8e98-de5489b6cd63.jpg[/img]
[size=x-small]ps: ^@符号 ctrl+@键(用来提交请求),删除之前输入的数据 ctrl+n+backspace键[/size]
当然Client端也可指定所支持的协议版本(accept-version字段,多个版本按递增顺序排列,并用逗号分隔);
[img]http://dl.iteye.com/upload/attachment/0076/5493/34146e53-85dc-3399-b0db-1065bc9a8678.jpg[/img]
服务器此时返回的CONNECTED Frame中会列出它所支持的协议版本号中最高的那个(如上图的version:1.1)
如果服务器端不支持客户端所列举的协议版本(比如这里的2.1),那么服务器会返回一个ERROR Frame并且列举出服务器自己所支持的协议版本(如下图的version:1.0,1.1)
[img]http://dl.iteye.com/upload/attachment/0076/5496/c095ff8e-de28-393e-aa12-be6435f00369.jpg[/img]
[b][size=medium]2.消息传递[/size][/b]
客户端一旦与服务器端建立连接,那么就可发送下列Frame进行消息传递
[table]
|[b][size=x-small]SEND[/size][/b]|
|[b][size=x-small]SUBSCRIBE[/size][/b]|
|[b][size=x-small]UNSUBSCRIBE[/size][/b]|
|[b][size=x-small]ACK[/size][/b]|
|[b][size=x-small]NACK[/size][/b]|
|[b][size=x-small]BEGIN[/size][/b]|
|[b][size=x-small]COMMIT[/size][/b]|
|[b][size=x-small]ABORT[/size][/b]|
|[b][size=x-small]DISCONNECT[/size][/b]|
[/table]
SEND Frame 用来将客户端消息发送到目的地(destination),因此它必须指定一个destination头字段,另外在所有头字段之后,新起一个空行,之后就是需要发送的消息(譬如这里的 hello stomp!)
[img]http://dl.iteye.com/upload/attachment/0076/5504/8c34b256-ae33-337c-9963-ea5c99c723a4.jpg[/img]
此时"hello stomp!" 这条消息就被发送到了队列 my_queue中去
现在我们再起一个客户端clinet_a,来接受这个队列(myqueue)中的消息
[img]http://dl.iteye.com/upload/attachment/0076/5506/ac407f1a-8681-363b-95bb-e4e1306277e4.jpg[/img]
SUBSCRIBE Frame 表示客户端希望订阅某一个目的地(destination)的消息(这里是/queue/my_queue),其中头字段id表示在一个会话连接里,唯一标示一个订阅者(subscription),头字段destination标示该订阅者(subscription)需要订阅的目的地(这里是一个队列,/queue/my_queue)
紧接着,我们就接受到服务器端发送来的消息(MESSAGE Frame),其中message_id:唯一标示了这条消息(后面我们会使用这个消息id进行ack,uack操作),content-length:标示消息体的长度,在所有这些头后面,新起一个空行就是消息内容(如这里的hello stomp!)
如此时我们希望订阅另一个destination,该如何办呢?,是不是再发送一个SUBSCRIBE Frame就好了?
[img]http://dl.iteye.com/upload/attachment/0076/5508/8ba4858c-f9db-3436-bfa6-cac411c57243.jpg[/img]
结果发现服务器端返回了ERROR Frame 告诉我们SUBSCRIBE失败,原来同一个subscription id只能订阅一个destination,要想订阅令一个destination,必须先发送UNSUBSCRIBE Frame,然后再SUBSCRIBE 到新的目的地
[img]http://dl.iteye.com/upload/attachment/0076/5510/28060320-383b-30d0-abdc-d43b871799d5.jpg[/img]
UNSCUBSCRIBE Frame中的id标示需要取消订阅的subscription,然后我们在订阅到新的destination(这里是/queue/other_queue),可以发现订阅成功,服务器没有再发送ERROR Frame
至此,我们就模拟出了一个PTP(point-to-point)消息模型,下面我们也模拟下另外一个pub/sub消息模型:
准备工作,新起两个连接订阅到/topic/my_topic上,如下图:
clinet_a
[img]http://dl.iteye.com/upload/attachment/0076/5512/a327f707-a6c4-3d4d-8fef-0c92865695e4.jpg[/img]
client b
[img]http://dl.iteye.com/upload/attachment/0076/5515/79dee18d-068d-36b2-8114-fda77216bab5.jpg[/img]
Send端发送广播消息:
[img]http://dl.iteye.com/upload/attachment/0076/5518/31084fbf-e343-3727-ab9b-a40240b12a1c.jpg[/img]
我们去检查下两个消息接收客户端,果然发现收到了这条广播消息^_^
client a
[img]http://dl.iteye.com/upload/attachment/0076/5521/12a6bb8c-a4b9-3201-9d96-aa3bdf7006ff.jpg[/img]
client b
[img]http://dl.iteye.com/upload/attachment/0076/5523/295f8b9a-b9fa-383c-b9fd-85f36562a7d4.jpg[/img]
默认情况下,只要服务器端发送消息,就认为客户端接收成功(即ack模式为auto),若我们需要更严格的消息保证,则必须采用client模式,即由客户端确认消息的接受
服务器发送了一个消息到/queue/ackqueue
[img]http://dl.iteye.com/upload/attachment/0076/5528/c45d3ae0-2211-3fc4-b3f4-685f5a9e2ddf.jpg[/img]
此时我们的客户端client_a确实接受到了该消息
[img]http://dl.iteye.com/upload/attachment/0076/5530/3e792cb6-fbbb-3f5a-a380-7e0164e4860e.jpg[/img]
虽然该消息已经发送到客户端,但是由于该消息没有确认(ack),则该消息还保存在队列/queue/ackqueue中(直到客户端确认才删除),我们可以通过rabbitmq提供的命令来查看(sudo rabbitmqctl list_queues name [color=blue]messages_ready[/color] [color=red]messages_unacknowledged[/color]):
[img]http://dl.iteye.com/upload/attachment/0076/5532/4fdcdc0b-ad63-3d8f-86f4-7051dbce52c4.jpg[/img]
若客户端在确认消息前与客户端断开连接,那么服务器可能(根据不同server的设计而不同)会选择将该消息发送给另一个subscription(这里是client_b)
[img]http://dl.iteye.com/upload/attachment/0076/5534/8dcb5910-0bd8-31cc-b47f-125b9f75c8ce.jpg[/img]
可以看出rabbit是选择将它发送给另一个subscription, 我们在使用命令查ack_queue队列中是否还有未确认的消息
[img]http://dl.iteye.com/upload/attachment/0076/5536/ccf73230-1861-34e3-bdcd-bd49b5a1623a.jpg[/img]
结果发现没有了 ^_^
若是客户端希望确认一个消息,该如何做呢?,只要发送一个ACK Frame即可!
在ACK Frame中,subscription标示是谁确认消息,message-id标示是确认哪条消息(即MESSAGE Frame中携带的message-id)
[img]http://dl.iteye.com/upload/attachment/0076/5538/e2307ae3-6940-3574-9d99-43354707114d.jpg[/img]
另外我在rabbitmq的测试结果发现,ack具有累积效应,譬如接收了10条消息,如果你ack了第8条消息,那么1-7条消息都会被ack,只有9-10两条消息还保持未ack状态
除了有ACK Frame,还有NACK Frame,它表示客户端未成功接收某条消息,这时候服务器可以选择重发或者丢弃(对于rabbitmq,我的测试结果是选择重发)
[b][size=medium]3.断开连接[/size][/b]
说完了连接的建立,消息的发送与接收,现在我们来看看客户端如何与服务器断开连接的,更重要的是如何安全的断开连接
[img]http://dl.iteye.com/upload/attachment/0076/5541/8294e46f-56e7-325a-8842-063928af90f4.jpg[/img]
通过发送DISCONNNECT Frame表示向服务器发送一个断开连接请求,其中receipt表示服务器收到请求后请告知客户端,并返回一个相同的receipt-id
至此关于Stmop绝大部分概念,我们已经实践完毕,如需更详细的还请翻阅官方文档 ^_^
Stomp是一个简单的消息文本协议,它的设计核心理念就是简单与可用性,官方文档:[url]http://stomp.github.com/stomp-specification-1.1.html[/url]
现在我们就来实践一下Stomp协议,你需要的是:
1.一个支持stomp消息协议的messaging server(譬如activemq,rabbitmq);
2.一个终端(譬如linux shell);
3.一些基本命令与操作(譬如nc,telnet)
[b][size=medium]1.建立连接[/size][/b]
当我们(Client端)向服务器发送一个CONNECT Frame,就向服务器发起了一个连接请求,此时服务器端返回一个CONNECTED Frame表示建立连接成功,其中头字段version表示采用的stomp协议版本(这里默认是1.0)
[img]http://dl.iteye.com/upload/attachment/0076/5485/fe7ab85e-7311-3dda-8e98-de5489b6cd63.jpg[/img]
[size=x-small]ps: ^@符号 ctrl+@键(用来提交请求),删除之前输入的数据 ctrl+n+backspace键[/size]
当然Client端也可指定所支持的协议版本(accept-version字段,多个版本按递增顺序排列,并用逗号分隔);
[img]http://dl.iteye.com/upload/attachment/0076/5493/34146e53-85dc-3399-b0db-1065bc9a8678.jpg[/img]
服务器此时返回的CONNECTED Frame中会列出它所支持的协议版本号中最高的那个(如上图的version:1.1)
如果服务器端不支持客户端所列举的协议版本(比如这里的2.1),那么服务器会返回一个ERROR Frame并且列举出服务器自己所支持的协议版本(如下图的version:1.0,1.1)
[img]http://dl.iteye.com/upload/attachment/0076/5496/c095ff8e-de28-393e-aa12-be6435f00369.jpg[/img]
[b][size=medium]2.消息传递[/size][/b]
客户端一旦与服务器端建立连接,那么就可发送下列Frame进行消息传递
[table]
|[b][size=x-small]SEND[/size][/b]|
|[b][size=x-small]SUBSCRIBE[/size][/b]|
|[b][size=x-small]UNSUBSCRIBE[/size][/b]|
|[b][size=x-small]ACK[/size][/b]|
|[b][size=x-small]NACK[/size][/b]|
|[b][size=x-small]BEGIN[/size][/b]|
|[b][size=x-small]COMMIT[/size][/b]|
|[b][size=x-small]ABORT[/size][/b]|
|[b][size=x-small]DISCONNECT[/size][/b]|
[/table]
SEND Frame 用来将客户端消息发送到目的地(destination),因此它必须指定一个destination头字段,另外在所有头字段之后,新起一个空行,之后就是需要发送的消息(譬如这里的 hello stomp!)
[img]http://dl.iteye.com/upload/attachment/0076/5504/8c34b256-ae33-337c-9963-ea5c99c723a4.jpg[/img]
此时"hello stomp!" 这条消息就被发送到了队列 my_queue中去
现在我们再起一个客户端clinet_a,来接受这个队列(myqueue)中的消息
[img]http://dl.iteye.com/upload/attachment/0076/5506/ac407f1a-8681-363b-95bb-e4e1306277e4.jpg[/img]
SUBSCRIBE Frame 表示客户端希望订阅某一个目的地(destination)的消息(这里是/queue/my_queue),其中头字段id表示在一个会话连接里,唯一标示一个订阅者(subscription),头字段destination标示该订阅者(subscription)需要订阅的目的地(这里是一个队列,/queue/my_queue)
紧接着,我们就接受到服务器端发送来的消息(MESSAGE Frame),其中message_id:唯一标示了这条消息(后面我们会使用这个消息id进行ack,uack操作),content-length:标示消息体的长度,在所有这些头后面,新起一个空行就是消息内容(如这里的hello stomp!)
如此时我们希望订阅另一个destination,该如何办呢?,是不是再发送一个SUBSCRIBE Frame就好了?
[img]http://dl.iteye.com/upload/attachment/0076/5508/8ba4858c-f9db-3436-bfa6-cac411c57243.jpg[/img]
结果发现服务器端返回了ERROR Frame 告诉我们SUBSCRIBE失败,原来同一个subscription id只能订阅一个destination,要想订阅令一个destination,必须先发送UNSUBSCRIBE Frame,然后再SUBSCRIBE 到新的目的地
[img]http://dl.iteye.com/upload/attachment/0076/5510/28060320-383b-30d0-abdc-d43b871799d5.jpg[/img]
UNSCUBSCRIBE Frame中的id标示需要取消订阅的subscription,然后我们在订阅到新的destination(这里是/queue/other_queue),可以发现订阅成功,服务器没有再发送ERROR Frame
至此,我们就模拟出了一个PTP(point-to-point)消息模型,下面我们也模拟下另外一个pub/sub消息模型:
准备工作,新起两个连接订阅到/topic/my_topic上,如下图:
clinet_a
[img]http://dl.iteye.com/upload/attachment/0076/5512/a327f707-a6c4-3d4d-8fef-0c92865695e4.jpg[/img]
client b
[img]http://dl.iteye.com/upload/attachment/0076/5515/79dee18d-068d-36b2-8114-fda77216bab5.jpg[/img]
Send端发送广播消息:
[img]http://dl.iteye.com/upload/attachment/0076/5518/31084fbf-e343-3727-ab9b-a40240b12a1c.jpg[/img]
我们去检查下两个消息接收客户端,果然发现收到了这条广播消息^_^
client a
[img]http://dl.iteye.com/upload/attachment/0076/5521/12a6bb8c-a4b9-3201-9d96-aa3bdf7006ff.jpg[/img]
client b
[img]http://dl.iteye.com/upload/attachment/0076/5523/295f8b9a-b9fa-383c-b9fd-85f36562a7d4.jpg[/img]
默认情况下,只要服务器端发送消息,就认为客户端接收成功(即ack模式为auto),若我们需要更严格的消息保证,则必须采用client模式,即由客户端确认消息的接受
服务器发送了一个消息到/queue/ackqueue
[img]http://dl.iteye.com/upload/attachment/0076/5528/c45d3ae0-2211-3fc4-b3f4-685f5a9e2ddf.jpg[/img]
此时我们的客户端client_a确实接受到了该消息
[img]http://dl.iteye.com/upload/attachment/0076/5530/3e792cb6-fbbb-3f5a-a380-7e0164e4860e.jpg[/img]
虽然该消息已经发送到客户端,但是由于该消息没有确认(ack),则该消息还保存在队列/queue/ackqueue中(直到客户端确认才删除),我们可以通过rabbitmq提供的命令来查看(sudo rabbitmqctl list_queues name [color=blue]messages_ready[/color] [color=red]messages_unacknowledged[/color]):
[img]http://dl.iteye.com/upload/attachment/0076/5532/4fdcdc0b-ad63-3d8f-86f4-7051dbce52c4.jpg[/img]
若客户端在确认消息前与客户端断开连接,那么服务器可能(根据不同server的设计而不同)会选择将该消息发送给另一个subscription(这里是client_b)
[img]http://dl.iteye.com/upload/attachment/0076/5534/8dcb5910-0bd8-31cc-b47f-125b9f75c8ce.jpg[/img]
可以看出rabbit是选择将它发送给另一个subscription, 我们在使用命令查ack_queue队列中是否还有未确认的消息
[img]http://dl.iteye.com/upload/attachment/0076/5536/ccf73230-1861-34e3-bdcd-bd49b5a1623a.jpg[/img]
结果发现没有了 ^_^
若是客户端希望确认一个消息,该如何做呢?,只要发送一个ACK Frame即可!
在ACK Frame中,subscription标示是谁确认消息,message-id标示是确认哪条消息(即MESSAGE Frame中携带的message-id)
[img]http://dl.iteye.com/upload/attachment/0076/5538/e2307ae3-6940-3574-9d99-43354707114d.jpg[/img]
另外我在rabbitmq的测试结果发现,ack具有累积效应,譬如接收了10条消息,如果你ack了第8条消息,那么1-7条消息都会被ack,只有9-10两条消息还保持未ack状态
除了有ACK Frame,还有NACK Frame,它表示客户端未成功接收某条消息,这时候服务器可以选择重发或者丢弃(对于rabbitmq,我的测试结果是选择重发)
[b][size=medium]3.断开连接[/size][/b]
说完了连接的建立,消息的发送与接收,现在我们来看看客户端如何与服务器断开连接的,更重要的是如何安全的断开连接
[img]http://dl.iteye.com/upload/attachment/0076/5541/8294e46f-56e7-325a-8842-063928af90f4.jpg[/img]
通过发送DISCONNNECT Frame表示向服务器发送一个断开连接请求,其中receipt表示服务器收到请求后请告知客户端,并返回一个相同的receipt-id
至此关于Stmop绝大部分概念,我们已经实践完毕,如需更详细的还请翻阅官方文档 ^_^