Асинхронізуйте мережеве програмування за допомогою реактивних розширень


25

Після декількох (більш-менш) асинхронних socketпрограмувань "низького рівня" років тому (за способом " Асинхронний візерунок" (EAP) на основі подій ) і нещодавно перейшов "до" TcpListener(Асинхронна модель програмування (APM) ), а потім намагаючись перейти до async/await(Асинхронний зразок на основі завдань ) (TAP). У мене це було досить, тому що я маю турбуватися зі всією цією "сантехнікою низького рівня". Тож я рахував; чому б не RXпіти ( Реактивне розширення ), оскільки це може більш щільно вписатися в мою проблемну область.

Я пишу багато кодів для багатьох клієнтів, що підключаються через Tcp до моєї програми, які потім починають двостороннє (асинхронне) спілкування. Клієнт або сервер можуть у будь-який момент вирішити, що повідомлення потрібно надіслати і зробити це, тому це не ваша класична request/responseнастройка, а більше, двостороння "лінія", відкрита для обох сторін, щоб надсилати все, що вони хочуть , коли вони хочуть. (Якщо хтось має гідне ім'я, щоб описати це, я би радий почути це!).

"Протокол" відрізняється від програми (і насправді не стосується мого питання). Однак у мене є початкове запитання:

  1. Враховуючи, що працює лише один "сервер", але він повинен відслідковувати багато (зазвичай тисячі) з'єднань (наприклад, клієнтів), кожен з яких (за відсутності кращого опису) має власну "державну машину", щоб відслідковувати свій внутрішній держави тощо, який підхід ви б віддали перевагу EAP / TAP / APM? Чи вважатиметься RX навіть варіантом? Якщо ні, то чому?

Отже, мені потрібно працювати з Async з а) це не протокол запиту / відповіді, тому я не можу мати потік / клієнта в "очікуванні повідомлення" -блокуючому дзвінку або "відправлення повідомлення" -блокуванні виклику (однак, якщо відправлення блокування лише для цього клієнта, я можу з ним жити) і b) мені потрібно обробляти багато одночасних з'єднань. Я не бачу способу зробити це (надійно) за допомогою блокування дзвінків.

Більшість моїх додатків пов'язані з VoiP; будь то SIP-повідомлення від SIP-коефіцієнтів або АТС (пов'язані) з повідомленнями з таких додатків, як FreeSwitch / OpenSIPS тощо, але ви можете, у найпростішій формі, спробувати уявити собі "чат" сервер, який намагається обробити багатьох клієнтів "чату". Більшість протоколів є текстовими (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), якщо припустити, що я піддаю клієнтів тим чи іншим способом і безпосередньо викликаю методи на них. Я також можу викликати методи через клас сервера (наприклад, Send(clientId, rawmessage)метод (тоді як останній підхід був би "зручним" методом для швидкого отримання повідомлення в іншу сторону).
  6. Я не зовсім впевнений, куди (і як) поїхати звідси:
    • а) мені потрібно обробляти вхідні повідомлення; як би я це встановив? Я можу отримати потік курсу, але де я б обробляв отримання отриманих байтів? Я думаю, що мені потрібна якась "ObservableStream" - щось, на що я можу підписатися? Я б поставив це в FiddleClientабо FiddleServer?
    • б) Припускаючи , що я хочу , щоб уникнути використання події , поки ці FiddleClient/ FiddleServerкласи не реалізується більш конкретно до кравця їх конкретне застосування протоколу обробки і т.д. , використовуючи більш конкретний FooClient/ FooServerкласи: як би я йти від отримання даних в нижележащем «Fiddle'-класах для їх більш конкретні аналоги?

Статті / посилання, які я вже читав / знімав / використовував для довідок:


Погляньте на існуючу бібліотеку ReactiveSockets
Flagbug

2
Я не шукаю бібліотеки чи посилання (хоча для довідок вони оцінені), але для введення / поради / допомоги щодо моїх питань та загальних налаштувань. Я хочу навчитися вдосконалювати свій власний код і мати можливість краще вирішити, в якому напрямку потрібно взяти це, зважити плюси та мінуси тощо. Не посилаючись на якусь бібліотеку, киньте її та продовжуйте рухатися далі. Я хочу вчитися на цьому досвіді та отримувати більше досвіду з програмуванням Rx / мережі.
RobIII

Звичайно, але оскільки бібліотека є відкритим кодом, ви можете побачити, як її там впроваджено
Flagbug

1
Звичайно, але перегляд вихідного коду не пояснює, чому деякі / дизайнерські рішення приймалися / приймаються. І тому, що я відносно новачок у Rx у поєднанні з мережевим програмуванням, я не маю достатнього досвіду, щоб сказати, чи корисна ця бібліотека, якщо дизайн має сенс, чи були прийняті правильні рішення і навіть якщо це правильно для мене.
РобІІІІ

Я думаю, що буде добре переосмислити сервер як активний учасник рукостискання, так що цей сервер ініціює з'єднання, а не слухає з'єднання. Наприклад, тут: codeproject.com/Articles/20250/Reverse-Connection-Shell

Відповіді:


1

... Я хотів би спростити свою роботу, створивши об'єкт, який я можу просто інстанціювати, сказати йому, на якому IPEndpoint слухати, і щоб він сказав мені, коли відбувається щось цікаве ...

Прочитавши цю заяву, я одразу подумав "актори". Актори дуже схожі на об'єкти, за винятком того, що вони мають лише один вхід, куди ви передаєте це повідомлення (замість того, щоб безпосередньо викликати методи об’єкта), і вони працюють асинхронно. На дуже спрощеному прикладі ... ви б створили актора і надіслали йому повідомлення з IPEndpoint та адресою актора, на який слід надіслати результат. Це вимикається і чи працює це на задньому плані. Ви чуєте про це лише тоді, коли трапляється "щось цікаве". Ви можете створити стільки акторів, скільки вам потрібно, щоб впоратися з навантаженням.

Я не знайомий з жодними бібліотеками акторів у .Net, хоча я знаю, що їх є. Мені знайома бібліотека потоків даних TPL (в моїй книзі http://DataflowBook.com буде розділ, що висвітлює її ), і реалізувати просту модель актора з цією бібліотекою слід легко.


Це виглядає цікаво. Я буду створювати іграшковий проект, щоб перевірити, чи підходить мені це. Дякую за пропозицію.
РобІІІ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.