使用Reactive Extensions进行异步网络编程


25

socket多年前(以基于事件的异步模式(EAP)方式)进行了一些(或多或少)“低级”异步编程之后,最近又将其“升级”到了TcpListener(异步编程模型(APM)),然后尝试转到async/await(基于任务的异步模式(TAP))时,我不得不忍受所有这些“低级管道”的困扰。所以我在想;为什么不试一试RXReactive Extensions),因为它可能更贴近我的问题领域。

我编写的代码很多,许多客户端通过Tcp连接到我的应用程序,然后启动双向(异步)通信。客户端服务器可以随时决定是否需要发送消息,所以这不是您的经典request/response设置,而是更多的实时,双向,“线路”开放给双方以发送他们想要的任何内容,只要他们想要。(如果有人用一个得体的名字来形容这一点,我将很高兴听到它!)。

每个应用程序的“协议”有所不同(并且与我的问题并不相关)。我确实有,但是有一个最初的问题:

  1. 假设只有一个“服务器”正在运行,但是它必须跟踪许多(通常是数千个)连接(例如客户端),每个连接都有(由于缺乏更好的描述)自己的“状态机”来跟踪其内部状态等,您更喜欢哪种方法?EAP / TAP / APM?RX甚至可以考虑吗?如果没有,为什么?

因此,我需要使用Async,因为a)它不是请求/响应协议,所以我不能在“等待消息”阻止调用或“发送消息”阻止调用中使用线程/客户端(但是,如果发送是仅阻止该客户端,我可以忍受它)b)我需要处理许多并发连接。我看不到使用阻塞调用(可靠地)执行此操作的方法。

我的大多数应用程序都与VoiP相关;无论是来自SIP 科学家的 SIP消息,还是来自FreeSwitch / OpenSIPS等应用程序的PBX(相关)消息,但是您可以以最简单的形式尝试想象一个“聊天”服务器尝试处理许多“聊天”客户端。大多数协议都是基于文本的(ASCII)。

因此,在实现了上述技术的许多不同排列之后,我想通过创建一个对象来简化我的工作,该对象可以简单地实例化,告诉它在哪IPEndpoint听,并在发生任何感兴趣的事情时告诉我(通常我通常会这样做)使用事件,因此通常将某些EAP与其他两种技术混合使用)。该类不应费力尝试“理解”协议。它应该只处理传入/传出的字符串。因此,我着眼于RX希望(最终)可以简化工作,我从头开始创建了一个新的“小提琴”:

using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Reactive.Linq;
using System.Text;

class Program
{
    static void Main(string[] args)
    {
        var f = new FiddleServer(new IPEndPoint(IPAddress.Any, 8084));
        f.Start();
        Console.ReadKey();
        f.Stop();
        Console.ReadKey();
    }
}

public class FiddleServer
{
    private TcpListener _listener;
    private ConcurrentDictionary<ulong, FiddleClient> _clients;
    private ulong _currentid = 0;

    public IPEndPoint LocalEP { get; private set; }

    public FiddleServer(IPEndPoint localEP)
    {
        this.LocalEP = localEP;
        _clients = new ConcurrentDictionary<ulong, FiddleClient>();
    }

    public void Start()
    {
        _listener = new TcpListener(this.LocalEP);
        _listener.Start();
        Observable.While(() => true, Observable.FromAsync(_listener.AcceptTcpClientAsync)).Subscribe(
            //OnNext
            tcpclient =>
            {
                //Create new FSClient with unique ID
                var fsclient = new FiddleClient(_currentid++, tcpclient);
                //Keep track of clients
                _clients.TryAdd(fsclient.ClientId, fsclient);
                //Initialize connection
                fsclient.Send("connect\n\n");

                Console.WriteLine("Client {0} accepted", fsclient.ClientId);
            },
            //OnError
            ex =>
            {

            },
            //OnComplete
            () =>
            {
                Console.WriteLine("Client connection initialized");
                //Accept new connections
                _listener.AcceptTcpClientAsync();
            }
        );
        Console.WriteLine("Started");
    }

    public void Stop()
    {
        _listener.Stop();
        Console.WriteLine("Stopped");
    }

    public void Send(ulong clientid, string rawmessage)
    {
        FiddleClient fsclient;
        if (_clients.TryGetValue(clientid, out fsclient))
        {
            fsclient.Send(rawmessage);
        }
    }
}

public class FiddleClient
{
    private TcpClient _tcpclient;

    public ulong ClientId { get; private set; }

    public FiddleClient(ulong id, TcpClient tcpclient)
    {
        this.ClientId = id;
        _tcpclient = tcpclient;
    }

    public void Send(string rawmessage)
    {
        Console.WriteLine("Sending {0}", rawmessage);
        var data = Encoding.ASCII.GetBytes(rawmessage);
        _tcpclient.GetStream().WriteAsync(data, 0, data.Length);    //Write vs WriteAsync?
    }
}

我知道,在这个“小提琴”中,有一些具体的实现细节;在这种情况下,我正在使用FreeSwitch ESL,因此"connect\n\n"在重构为更通用的方法时,应该删除小提琴。

我还知道,我需要将匿名方法重构为Server类上的私有实例方法。我只是不确定OnSomething为其方法名称使用哪种约定(例如“ ”)?

这是我的基础/起点/基础(需要一些“调整”)。我对此有一些疑问:

  1. 参见上面的问题“ 1”
  2. 我在正确的轨道上吗?还是我的“设计”决定不公正?
  3. 并发方面:这将应付成千上万的客户端(解析/处理实际消息)
  4. 关于异常:我不确定如何将客户端中引发的异常“上”到服务器(“ RX方式”);有什么好办法吗?
  5. 现在ClientId,假设我以一种或另一种方式公开客户端,并直接在其上调用方法,则可以从服务器类中获取任何连接的客户端(使用)。我还可以通过Server类来调用方法(例如,该Send(clientId, rawmessage)方法(而后一种方法将是一种“便捷”方法,用于快速将消息发送到另一侧)。
  6. 我不太确定从这里到哪里(以及如何去):
    • a)我需要处理传入的消息;我该如何设置?我可以得到课程流,但是在哪里可以检索接收到的字节?我想我需要某种“ ObservableStream”-我可以订阅吗?我会把这在FiddleClient还是FiddleServer
    • b)假设我想避免使用事件,直到更具体地实现这些FiddleClient/ FiddleServer类以定制其特定于应用程序的协议处理等为止。使用更具体的FooClient/ FooServer类:从获取底层“小提琴”类中的数据到他们的方法如何?更具体的对应?

我已经阅读/略读/用于参考的文章/链接:


看看现有的ReactiveSockets
Flagbug,2014年

2
我不是在寻找库或链接(尽管可以作为参考),但是我想在我的问题和常规设置上提供输入/建议/帮助。我想学习改进自己的代码,并能够更好地下定决心要朝哪个方向发展,权衡利弊等。不要引用某些库,而是直接入库。我想从这种经验中学习,并获得有关Rx /网络编程的更多经验。
RobIII 2014年

当然可以,但是由于该库是开源的,因此您可以在
此处

1
当然可以,但是查看源代码并不能解释为什么要做出一些设计决策。而且由于我对Rx结合网络编程还比较陌生,因此我没有足够的经验来判断该库是否不错,设计是否合理,是否做出了正确的决定,甚至对我来说都是正确的。
RobIII 2014年

我认为最好将服务器重新视为握手的活动成员,因此,服务器可以启动连接而不是监听连接。例如,在这里:codeproject.com/Articles/20250/Reverse-Connection-Shell

Answers:


1

...我想通过创建一个对象来简化我的工作,该对象可以简单地实例化,告诉它在哪个IPEndpoint上监听,并在发生任何感兴趣的事情时告诉我...

阅读该声明后,我立即想到了“演员”。Actor与对象非常相似,不同之处在于Actor仅具有一个输入,您可以在该输入中向其传递消息(而不是直接调用对象的方法),并且它们异步操作。在一个非常简化的示例中……您将创建角色,并使用IPEndpoint和角色的地址向其发送一条消息,以将结果发送到该消息。它熄灭并在后台运行。只有在发生“感兴趣的事情”时,您才收到回音。您可以实例化处理负载所需的角色。

我不知道.Net中的任何actor库,尽管我知道有一些。我对TPL Dataflow库很熟悉(在我的书http://DataflowBook.com中将有一节介绍它),并且使用该库实现简单的actor模型应该很容易。


看起来很有趣。我将建立一个玩具项目,看它是否适合我。谢谢你的建议。
RobIII 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.