Після декількох (більш-менш) асинхронних socket
програмувань "низького рівня" років тому (за способом " Асинхронний візерунок" (EAP) на основі подій ) і нещодавно перейшов "до" TcpListener
(Асинхронна модель програмування (APM) ), а потім намагаючись перейти до async/await
(Асинхронний зразок на основі завдань ) (TAP). У мене це було досить, тому що я маю турбуватися зі всією цією "сантехнікою низького рівня". Тож я рахував; чому б не RX
піти ( Реактивне розширення ), оскільки це може більш щільно вписатися в мою проблемну область.
Я пишу багато кодів для багатьох клієнтів, що підключаються через Tcp до моєї програми, які потім починають двостороннє (асинхронне) спілкування. Клієнт або сервер можуть у будь-який момент вирішити, що повідомлення потрібно надіслати і зробити це, тому це не ваша класична request/response
настройка, а більше, двостороння "лінія", відкрита для обох сторін, щоб надсилати все, що вони хочуть , коли вони хочуть. (Якщо хтось має гідне ім'я, щоб описати це, я би радий почути це!).
"Протокол" відрізняється від програми (і насправді не стосується мого питання). Однак у мене є початкове запитання:
- Враховуючи, що працює лише один "сервер", але він повинен відслідковувати багато (зазвичай тисячі) з'єднань (наприклад, клієнтів), кожен з яких (за відсутності кращого опису) має власну "державну машину", щоб відслідковувати свій внутрішній держави тощо, який підхід ви б віддали перевагу 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"
- Я на правильному шляху? Або мої "дизайнерські" рішення несправедливі?
- Цілком зрозуміло: чи може це впоратися з тисячами клієнтів (розбір / обробка фактичних повідомлень убік)
- За винятками: я не впевнений, як отримати винятки, підняті в межах клієнтів "до" сервера ("RX-розумно"); який би був хороший спосіб?
- Тепер я можу отримати будь-якого підключеного клієнта зі свого серверного класу (використовуючи його
ClientId
), якщо припустити, що я піддаю клієнтів тим чи іншим способом і безпосередньо викликаю методи на них. Я також можу викликати методи через клас сервера (наприклад,Send(clientId, rawmessage)
метод (тоді як останній підхід був би "зручним" методом для швидкого отримання повідомлення в іншу сторону). - Я не зовсім впевнений, куди (і як) поїхати звідси:
- а) мені потрібно обробляти вхідні повідомлення; як би я це встановив? Я можу отримати потік курсу, але де я б обробляв отримання отриманих байтів? Я думаю, що мені потрібна якась "ObservableStream" - щось, на що я можу підписатися? Я б поставив це в
FiddleClient
абоFiddleServer
? - б) Припускаючи , що я хочу , щоб уникнути використання події , поки ці
FiddleClient
/FiddleServer
класи не реалізується більш конкретно до кравця їх конкретне застосування протоколу обробки і т.д. , використовуючи більш конкретнийFooClient
/FooServer
класи: як би я йти від отримання даних в нижележащем «Fiddle'-класах для їх більш конкретні аналоги?
- а) мені потрібно обробляти вхідні повідомлення; як би я це встановив? Я можу отримати потік курсу, але де я б обробляв отримання отриманих байтів? Я думаю, що мені потрібна якась "ObservableStream" - щось, на що я можу підписатися? Я б поставив це в
Статті / посилання, які я вже читав / знімав / використовував для довідок:
- Споживайте сокет за допомогою реактивного розширення
- Створення та підписання простих спостережуваних послідовностей
- Як додати або пов’язати контекст до кожного з'єднання / сесії ObservableTcpListener
- Асинхронне програмування з Реактивними рамками та бібліотекою паралельних завдань - Частини 1 , 2 та 3 .
- Використовувати реактивне розширення (Rx) для програмування сокет?
- .NET - Rx Driven веб - сервер і .NET Rx Driven веб - сервер, Take 2
- Деякі навчальні посібники Rx