nanomsg是一个消息通信组件 - zeromq的作者之一用C语言重写的通信框架,
使用宽松的MIT许可开源,小、轻、快,非常方便,介绍我就不多写了,下面我具体的讲用法,用了你就知道是怎么回事。
pull/push 单向管道推送模式
push/pull套接字结合使用可实现消息队列的扇出模式, 这是一个1对多的模式,服务端不能有多个,但客户端可以多个。 官网上给的这个图是1对1的,不要误解(他实际上是用于1对多的):

服务端或客户端不管谁先启动,都会等待对方连接, 注意pub/sub模式的服务端启动后是不会等待客户端来连接的,而push/pull就完全相反必须双方连接好了再推送,可以想象为一个管道,两头都接上才能传输数据。
pub/sub模式中,服务端一旦跟客户端连接,中间就绝不会丢弃消息, 就类似于一个单向的数据流,服务端只发不收,客户端只收不发,一个消息不会重复发给多个客户端 一般用于任务分派、负载均衡。
push服务端示例:
import console;
import nnmsg; var sock = nnmsg.socket.push(); sock.bind ("ws://*:7715")
console.log("push服务端等待连接中,没有人连接是不会发送的")
for(i=1;100;1){ sock.send("分发任务ID:" + i ); console.log("分发任务ID:" + i ) sleep(1000) }
console.pause(true);
pull客户端示例:
import console;
import nnmsg; var sock = nnmsg.socket.pull();
console.log("客户端等待连接") if( sock.connect("ws://127.0.0.1:7715") ){ var s = sock.recv(); while(s){ console.log("服务器返回:",s ); s = sock.recv(); } } console.pause(true);
千万不要以为这有什么呀,自己写个套接字也可以推送点东西, nanomsg可没有这么简单,表面上看起来他好像是一个传输流,实际上他并不是。 nanomsg不像传统套接字那样,一定要服务端先启动,客户端后面跑过来连接, 他也可以客户端先启动,服务端后启动。 而且在连接传输的过程中,服务端,或者是客户端都可以中途断线, 下次再启动,可以继续工作,很神奇是吧?! |
sub/pub 消息广播模式
sub/pub套接字结合使用可实现消息广播模式(Topics && Broadcast) 服务端只管发布,不管客户端是否连接,也不管是不是丢消息,但客户端连接上来以后就不会丢消息。
 这个模式与push/pull的区别是很明显的, 不会像push/pull那样一定要等待双方连接才传输数据。 pub服务端只管自己顾自己的发布消息,而不管别人有没有收到。 一个典型的例子就是报时服务器,下面看代码。
pub服务端:
import console;
import nnmsg; var sock = nnmsg.socket.pub(); sock.bind ("ws://*:7713")
console.log("pub服务端已启动,不管客户端是不是连接上来,也不考虑有没有人接收消息")
for(i=1;100;1){ sock.send("报时:" + tostring( time() ) ); console.log("报时:" + tostring( time() ) ) sleep(1000) }
console.pause(true);
sub客户端:
import console;
import nnmsg; var sock = nnmsg.socket.sub();
console.log("客户端等待连接")
//一定要指定订阅的主题前缀,指定为空订阅所有主题 sock.subscribe("报时:");
if( sock.connect("ws://127.0.0.1:7713") ){ var s = sock.recv(); while(s){ console.log("服务器返回:",s ); s = sock.recv(); } } console.pause(true);
这个sub客户端有一点特别,一定要用 sock.subscribe("报时:"); 指定你感兴趣的消息, 这个函数的参数指定要订阅消息的前几个字符,这几个字符没有特别格式的要求,只要消息是由这几个字符开始就可以。 pus/sub模式里,服务端的同一个消息可能被多个客户端接收,也可能根本没有客户端接收, push/pull则完全不同 - 服务端消息一定会被一个客户端,并且也只会被一个客户端取走。 |
surveyor/respondent调查者模式
surveyor/respondent套接字结合使用可实现调查模式, surveyort负责发出调查问题,而respondent客户端则负责回应。
 surveyor 服务端示例:
import console;
import nnmsg; var sock = nnmsg.socket.surveyor(); sock.bind ("ws://*:7717")
console.log("已启动 Survey(WebSocket协议) 服务端") while (1){ var msg = sock.send("客户端你好,现在几点了?"); while(1){ var tm,err = sock.recvTable(); if(!tm){ if(nnmsg.lasterrno() == 0x9523DFE/*_NN_EFSM*/ ) break ; else continue ; } console.log( tm ) } console.log("调查完了") }
respondent客户端:
import console; import nnmsg;
console.log("Survey(WebSocket协议)客户端已启动") var sock = nnmsg.socket.respondent();
if( sock.connect("ws://127.0.0.1:7717") ){ while(1){ var msg = sock.recv(); console.dump( "收到:",msg ); sock.sendTable( time() ) } }
console.pause(true);
|
pair 端对端双向通信模式
pair套接字,服务端和客户端可以1对1的收发消息,

pair 服务端:
import console;
import nnmsg; var sock = nnmsg.socket.pair(); sock.bind ("ws://*:7710")
console.log("已启动 WebSocket 服务端") while (1){ var msg = sock.recv(); console.dump( "客户端:",msg ); sock.send("客户端你还好吗?!") }
console.pause(true);
pair 客户端:
import console;
import nnmsg; var sock = nnmsg.socket.pair(); if( sock.connect("ws://127.0.0.1:7710") ){ sock.send("hello") console.log("服务端:",sock.recv() ) } console.pause(true);
|
bus 消息总线模式
一个消息总线上可以有多个套接字, 每个套接字即是服务端可以启动监听,也是客户端可以同时连接多个其他的套接字。
连接到消息总线的任何一个套接字发送消息,消息总线上的其他套接字都能收到,一个套接字发出的消息, 其他套接字有可能重复的接收到多次(这个就好比街头听到的小道消息,可能由不同的人告诉你)。 但是套接字永远不会收到自己发的消息。

下面请看演示,消息总线里不分服务端或者客户端:
import win.ui; /*DSG{{*/ var winform = win.form(text="nanomsg消息总线模式 演示";right=759;bottom=469) winform.add( edit={cls="edit";left=17;top=18;right=739;bottom=445;db=1;dl=1;dr=1;dt=1;edge=1;multiline=1;vscroll=1;z=1} ) /*}}*/
winform.show()
//用于启动一个节点线程 var joinbus = function(port,winform){ import win; import nnmsg; var sock = nnmsg.socket.bus(); sock.bind ("tcp://*:" + port) var nodes = thread.get("nnmsg-bus") : {}; for(i,addr in nodes){ sock.connect(addr)//连接到消息总线上所有其他的节点 } //把自己的地址写到消息总线上去 table.push(nodes,"tcp://127.0.0.1:" + port); thread.set("nnmsg-bus",nodes ) sleep(100);//等待其他节点加入 sock.send(port + "发送:我是新人,我今天才刚知道这个消息总线" ) while( win.isWindow(winform.hwnd) ){ var msg = sock.recv(); winform.edit.print(port + "接收:" + msg) } }
//创建一大堆节点模拟消息总线 for(port=7591;7599){ thread.invoke(joinbus,port,winform) sleep(100); }
win.loopMessage();
运行示例可以看到,这个模式发送的消息发送的还是有些激烈的,所以这种模式只适合在本地局域网内使用。 |
使用WebSocket客户端与nanomsg交互
nanomsg实现各种模式的代码惊人的简洁,但简洁不等于简单。 并且各种编程语言里基本都有nanomsg的实现,使用nanomsg有良好的可扩展性。
nanomsg不但可以实现多种不同的通信模式,也可以选择多种不同的通信协议。 例如上面的我写的消息总线模式 - 使用的是tcp协议,而其他的模式使用的是WebSocket协议。 尤其是其中的WebSocket协议非常有趣,我们可以自己写一个WebSocket去连接nanomsg的服务端,并与nanomsg交互。
最初尝试的时候,我失败了! 在aardio中使用 web.socket.client 对象去连接 nanomsg,nanomsg很不高兴的拒绝了连接。
于是,我想起了 wsock.tcp.simpleHttpServer 这个好东西, 首先我用 wsock.tcp.simpleHttpServer启动了一个HTTP服务端 - 用来冒充nanomsg服务端。 然后用 nanamsg创建了一个WebSocket客户端连接上去,在HTTP服务器上输出发过来的数据包, 发现了nanomsg的小秘密,多了一个Sec-WebSocket-Protocol的HTTP头。
好吧,下面我们看演示代码。
nanomsg 服务端:
import console;
import nnmsg; var sock = nnmsg.socket.pub(); sock.bind ("ws://*:7725")
console.log("pub服务端已启动,可以打开aardio websocket客户端试一下了")
for(i=1;100;1){ sock.send("报时:" + tostring( time() ) ); console.log("报时:" + tostring( time() ) ) sleep(1000) }
console.pause(true);
下面是aardio实现的WebSocket客户端:
import win.ui; /*DSG{{*/ var winform = win.form(text="WebSocket客户端演示";right=770;bottom=467) winform.add( btnClose={cls="button";text="断开";left=681;top=295;right=757;bottom=333;db=1;dr=1;z=6}; btnOpen={cls="button";text="连接WebSocket服务端";left=501;top=295;right=655;bottom=333;db=1;dr=1;z=2}; btnSend={cls="button";text="发送数据";left=567;top=380;right=764;bottom=456;db=1;dr=1;z=4}; cbSecWebSocketProtocol={cls="combobox";left=301;top=298;right=481;bottom=324;edge=1;items={};mode="dropdown";z=7}; txtMessage={cls="edit";left=29;top=22;right=741;bottom=285;db=1;dl=1;dr=1;dt=1;edge=1;multiline=1;z=1}; txtSend={cls="edit";text="WebSocket测试";left=25;top=382;right=554;bottom=457;db=1;dl=1;dr=1;edge=1;multiline=1;z=3}; txtUrl={cls="edit";text="ws://127.0.0.1:7725";left=29;top=295;right=269;bottom=331;db=1;dl=1;dr=1;edge=1;z=5} ) /*}}*/
winform.cbSecWebSocketProtocol.items ={ "pub.sp.nanomsg.org"; "sub.sp.nanomsg.org"; "pair.sp.nanomsg.org"; "req.sp.nanomsg.org"; "rep.sp.nanomsg.org"; "surveyor.sp.nanomsg.org"; "respondent.sp.nanomsg.org"; "push.sp.nanomsg.org"; "pull.sp.nanomsg.org"; "bus.sp.nanomsg.org"; }
import web.socket.client; var ws = web.socket.client(); ws.heartbeatInterval = - 1;//sub端不能发任何数据
ws.onOpen = function(){ winform.cbSecWebSocketProtocol.disabled = true; }
ws.onClose = function(){ winform.txtMessage.print("onClose"); winform.cbSecWebSocketProtocol.disabled = false; winform.btnSend.disabledText = null; }
ws.onError = function(err){ winform.txtMessage.print("onError",err); }
ws.onMessage = function(msg){ winform.txtMessage.print(msg.data); }
winform.btnSend.oncommand = function(id,event){ ws.send(winform.txtSend.text) }
winform.btnOpen.oncommand = function(id,event){ //加上这句,假装自己是nanomsg客户端 ws.headers ={ ["Sec-WebSocket-Protocol"] = winform.cbSecWebSocketProtocol.selText; } if( winform.cbSecWebSocketProtocol.selText == "pub.sp.nanomsg.org" ){ winform.btnSend.disabledText = "此模式不支持发送" } ws.connect(winform.txtUrl.text); }
winform.btnClose.oncommand = function(id,event){ ws.close(); }
winform.show() win.loopMessage();
运行示例,奇迹发生了:  |