NetMQ 解读

什么是NetMQ

NetMQ is a 100% native C# port of the lightweight messaging library ZeroMQ.

NetMQ extends the standard socket interfaces with features traditionally provided by specialised messaging middleware products. NetMQ sockets provide an abstraction of asynchronous message queues, multiple messaging patterns, message filtering (subscriptions), seamless access to multiple transport protocols, and more.

简单说就是ZeroMQ的.net开源版本 吊炸天的速度你懂的

常用模型

请求回应模型 Request-Reply

由请求端发起请求,然后等待回应端应答。一个请求必须对应一个回应,从请求端的角度来看是发-收配对,从回应端的角度是收-发对。跟一对一结对模型的区别在于请求端可以是1~N个。
请求端和回应端都可以是1:N的模型。通常把1认为是server,N认为是Client。ZeroMQ可以很好的支持路由功能(实现路由功能的组件叫作Device),把1:N扩展为N:M(只需要加入若干路由节点)。从这个模型看,更底层的端点地址是对上层隐藏的。每个请求都隐含有回应地址,而应用则不关心它。通常把该模型主要用于远程调用及任务分配等。
(NetMQ请求响应C#调用案例)

发布订阅模型 Publisher-Subscriber

发布端单向分发数据,且不关心是否把全部信息发送给订阅端。如果发布端开始发布信息时,订阅端尚未连接上来,则这些信息会被直接丢弃。订阅端未连接导致信息丢失的问题,可以通过与请求回应模型组合来解决。订阅端只负责接收,而不能反馈,且在订阅端消费速度慢于发布端的情况下,会在订阅端堆积数据。该模型主要用于数据分发。天气预报、微博明星粉丝可以应用这种经典模型。 (NetMQ发布订阅模式C#调用案例)

核心类

为什么使用NetMQPoller管理多个Socket
1.高效性

using (var rep1 = new ResponseSocket("@tcp://*:5001"))
using (var rep2 = new ResponseSocket("@tcp://*:5002"))
{
    while (true)
    {
        // Hmmm....
    }
}
How would we fairly service both of these response sockets? Can't we just process them each in turn?

// blocks until a message is received
var msg1 = rep1.ReceiveString();

// might never reach this code!
var msg2 = rep2.ReceiveString();

A receive call blocks until a message arrives. If we make a blocking receive call on rep1, then we will ignore any messages for rep2 until rep1 actually receives something—which may never happen. This is clearly not a suitable solution.

Instead we could use non-blocking receive calls on rep1 and rep2. However this would keep the CPU maxed out even when no messages are available. Again, this is not a suitable approach.
效率低下
2.正确性
For ZeroMQ/NetMQ to give great performance, some restrictions exist on how we can use its sockets. In particular, NetMQSocket is not threadsafe. It is invalid to use a socket from multiple threads simultaneously.
非线程安全的
Poller应运而生
Let’s use a Poller to easily service two sockets from a single thread:

using (var rep1 = new ResponseSocket("@tcp://*:5001"))
using (var rep2 = new ResponseSocket("@tcp://*:5002"))
using (var poller = new NetMQPoller { rep1, rep2 })
{
    // these event will be raised by the Poller
    rep1.ReceiveReady += (s, a) =>
    {
        // receive won't block as a message is ready
        string msg = a.Socket.ReceiveString();
        // send a response
        a.Socket.Send("Response");
    };
    rep2.ReceiveReady += (s, a) =>
    {
        // receive won't block as a message is ready
        string msg = a.Socket.ReceiveString();
        // send a response
        a.Socket.Send("Response");
    };

    // start polling (on this thread)
    poller.Run();
}

This code sets up two sockets and bind them to different addresses. It then adds those sockets to a NetMQPoller using the collection initialiser (you could also call Add(NetMQSocket)). Event handlers are attached to each socket’s ReceiveReady event. Finally the poller is started via Run(), which blocks until Stop is called on the poller.

Timers 间隔触发

Pollers have an additional feature: timers.

If you wish to perform some operation periodically, and need that operation to be performed on a thread which is allowed to use one or more sockets, you can add a NetMQTimer to the NetMQPoller along with the sockets you wish to use.

This code sample will publish a message every second to all connected peers.

var timer = new NetMQTimer(TimeSpan.FromSeconds(1));

using (var pub = new PublisherSocket("@tcp://*:5001"))
using (var poller = new NetMQPoller { pub, timer })
{
    pub.ReceiveReady += (s, a) => { /* ... */ };

    timer.Elapsed += (s, a) =>
    {
        pub.Send("Beep!");
    };

    poller.Run();
}

Adding/removing sockets/timers 添加移除

Sockets and timers may be safely added and removed from the poller while it is running.

Note that implementations of ISocketPollable include NetMQSocket, NetMQActor and NetMQBeacon. Therefore a NetMQPoller can observe any of these types.

  • Add(ISocketPollable)
  • Add(NetMQTimer)
  • Remove(NetMQTimer)
  • Add(System.Net.Sockets.Socket, Action)
  • Remove(System.Net.Sockets.Socket)

Poller的线程控制
Controlling polling

So far we’ve seen Run. This devotes the calling thread to polling activity until the poller is cancelled, either from a socket/timer event handler, or from another thread.

If you wish to continue using the calling thread for other actions, you may call RunAsync instead, which calls Run in a new thread.

To stop a poller, use either Stop or StopAsync. The latter waits until the poller’s loop has completely exited before returning, and may be necessary during graceful teardown of an application.

2.Beacon

Beacon

NetMQBeacon implements a peer-to-peer discovery service for local networks.

A beacon can broadcast and/or capture service announcements using UDP messages on the local area network. You can define the format of your outgoing beacons, and set a filter that validates incoming beacons. Beacons are sent and received asynchronously in the background.

We can use the NetMQBeacon to discover and connect to other NetMQ/CZMQ services in the network automatically without central configuration. Please note that to use NetMQBeacon your infrastructure must support broadcast. Most cloud providers doesn't support broadcast.

This implementation uses IPv4 UDP broadcasts, and is a port of zbeacon from czmq with some extensions, though it maintains network compatibility.

简单说Beacon就是端到端的UDP广播。

NetMQBeacon can be used to create a simple bus that allows a set of nodes to discover one another, configured only via a shared port number.

NetMQBeacon就是我们常用的类 他可以创建一条总线使得节点间可以互相访问 仅仅通过配置一个端口号。

Each bus node binds a subscriber socket and connects to other nodes with a publisher socket.
Each node will use NetMQBeacon to announce its existence and to discover other nodes. We will also use  NetMQActor to implement our node.

每个总线节点都需要绑定一个订阅端和一个发布端来来连接其他节点。
每个节点使用NetMQBeacon来声明自己,节点用NetMQActor来实现。

下面详细讲解一下官方的Demo
实际上要启动两个app才能看出效果。
https://github.com/NetMQ/Samples/blob/master/src/Beacon/BeaconDemo/Bus.cs
我总结一下使用NetMQBeacon的重点:
PublisherSocket 发送、SubscriberSocket 订阅、NetMQBeacon 主人公、NetMQPoller 管理类 、NetMQActor 节点类、PairSocket 监听NetMQActor的消息

接收NetMQBeacon 广播就得Configure port 别的NetMQBeacon Publish 你的NetMQBeacon的OnBeaconReady就会被触发、间歇触发等。
当然还必须用PublisherSocket 对其他节点发送数据才能收到
SubscriberSocket 接收其他节点publish的数据
PairSocket 来处理Actor发送的消息(消息队列轮询处理,多线程下的Actor Model同步机制 将多线程的读取以队列消息的形式进行轮询处理)也可以发送给Actor消息 返回处理

上代码分析:
创建端节点(NetMQActor类) 配置共享节点端口(999)

 // Create a bus using broadcast port 9999
            // All communication with the bus is through the returned actor
            var actor = Bus.Create(9999);

        /// <summary>
        /// Creates a new message bus actor. All communication with the bus is
        /// through the returned <see cref="NetMQActor"/>.
        /// </summary>
        public static NetMQActor Create(int broadcastPort)
        {
            Bus node = new Bus(broadcastPort);
            return node.m_actor;
        }
        //创建 全局字典存放 所有的其他节点 初始化Actor
         private Bus(int broadcastPort)
        {
            m_nodes = new Dictionary<NodeKey, DateTime>();
            m_broadcastPort = broadcastPort;
            m_actor = NetMQActor.Create(RunActor);
        }

PairSocket (m_shim) 用于与Actor传递消息 通过SendFrame
只有通过PublishSocket(m_publisher)发送的消息才能被其他节点的Subscriber接收 并触发OnSubscriberReady
NetMQBeacon(m_beacon).publish(obj,interverl)会间歇触发其他节点的 OnBeaconReady

 private PairSocket m_shim;
 private void RunActor(PairSocket shim)
        {
            // save the shim to the class to use later

            m_shim = shim;

            // create all subscriber, publisher and beacon
            using (m_subscriber = new SubscriberSocket())
            using (m_publisher = new PublisherSocket())
            using (m_beacon = new NetMQBeacon())
            {
                // listen to actor commands
                 //actor SendFrame 发送的东西都会在这里判断并执行
                m_shim.ReceiveReady += OnShimReady;

                // subscribe to all messages
                m_subscriber.Subscribe("");

                // we bind to a random port, we will later publish this port
                // using the beacon
                m_randomPort = m_subscriber.BindRandomPort("tcp://*");
                Console.WriteLine("Bus subscriber is bound to {0}", m_subscriber.Options.LastEndpoint);

                // listen to incoming messages from other publishers, forward them to the shim
                m_subscriber.ReceiveReady += OnSubscriberReady;

                // configure the beacon to listen on the broadcast port
                Console.WriteLine("Beacon is being configured to UDP port {0}", m_broadcastPort);
                m_beacon.Configure(m_broadcastPort);

                // publishing the random port to all other nodes
                Console.WriteLine("Beacon is publishing the Bus subscriber port {0}", m_randomPort);
                m_beacon.Publish(m_randomPort.ToString(), TimeSpan.FromSeconds(1));

                // Subscribe to all beacon on the port
                Console.WriteLine("Beacon is subscribing to all beacons on UDP port {0}", m_broadcastPort);
                m_beacon.Subscribe("");

                // listen to incoming beacons
                m_beacon.ReceiveReady += OnBeaconReady;

                // Create a timer to clear dead nodes
                NetMQTimer timer = new NetMQTimer(TimeSpan.FromSeconds(1));
                timer.Elapsed += ClearDeadNodes;

                // Create and configure the poller with all sockets and the timer
                m_poller = new NetMQPoller { m_shim, m_subscriber, m_beacon, timer };

                // signal the actor that we finished with configuration and
                // ready to work
                m_shim.SignalOK();

                // polling until cancelled
                m_poller.Run();
            }
        }

Main:获取当前设备ip并改到窗口标题上

  Console.Title = "NetMQ Beacon Demo";

            // Create a bus using broadcast port 9999
            // All communication with the bus is through the returned actor
            var actor = Bus.Create(9999);

            actor.SendFrame(Bus.GetHostAddressCommand);
            var hostAddress = actor.ReceiveFrameString();

            Console.Title = $"NetMQ Beacon Demo at {hostAddress}";

OnShimReady:获取ip后发送给actor

   else if (command == GetHostAddressCommand)
            {
                var address = m_beacon.BoundTo + ":" + m_randomPort;
                m_shim.SendFrame(address);
            }

节点间数据传递
Main:

 actor.SendMoreFrame(Bus.PublishCommand).SendMoreFrame("Hello?").SendFrame(hostAddress);
            actor.SendMoreFrame(Bus.PublishCommand).SendMoreFrame("God like").SendFrame(hostAddress);
            //actor.SendMoreFrame("HHHHHHHHHHHHHH").SendFrame(hostAddress);
            //actor.SendMoreFrame(Bus.PublishCommand).SendMoreFrame("Fuckkkkkkk?").SendFrame(hostAddress);
            // receive messages from other nodes on the bus
            while (true)
            {
                // actor is receiving messages forwarded by the Bus subscriber
                string message = actor.ReceiveFrameString();
                switch (message)
                {
                    case "Hello?":
                        // another node is saying hello
                        var fromHostAddress = actor.ReceiveFrameString();
                        var msg = fromHostAddress + " says Hello?";
                        Console.WriteLine(msg);

                        // send back a welcome message via the Bus publisher
                        msg = hostAddress + " says Welcome!";
                        actor.SendMoreFrame(Bus.PublishCommand).SendFrame(msg);
                        break;
                    case Bus.AddedNodeCommand:
                        var addedAddress = actor.ReceiveFrameString();
                        Console.WriteLine("Added node {0} to the Bus", addedAddress);
                        break;
                    case Bus.RemovedNodeCommand:
                        var removedAddress = actor.ReceiveFrameString();
                        Console.WriteLine("Removed node {0} from the Bus", removedAddress);
                        break;
                    default:
                        // it's probably a welcome message
                        Console.WriteLine(message);
                        break;
                }
            }

OnShimReady:
必须要使用m_publish 发送其他节点才能接收

    else if (command == PublishCommand)
            {
                // it is a publish command
                // we just forward everything to the publisher until end of message
                // 只有使用PublisherSocket 其他的节点才能收到并触发 OnSubscriberReady
                NetMQMessage message = m_shim.ReceiveMultipartMessage();
                m_publisher.SendMultipartMessage(message);
            }

其他节点触发OnSubscriberReady 本例收到后转发至actor打印

        private void OnSubscriberReady(object sender, NetMQSocketEventArgs e)
        {
            // we got a new message from the bus 从总线(其他节点publish的一条信息)接收到一条信息 
            // let's forward everything to the shim 转发到actor接收  actor.ReceiveFrameString()可以直接获取到该信息

            NetMQMessage message = m_subscriber.ReceiveMultipartMessage();
            Console.WriteLine("OnSubscriberReady");
            m_shim.SendMultipartMessage(message);
        }
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值