Sau khi thực hiện một số socket
chương trình không đồng bộ "mức độ thấp" (ít nhiều) từ nhiều năm trước (theo kiểu Không đồng bộ dựa trên sự kiện (EAP) ) và gần đây chuyển "lên" sang TcpListener
(Mô hình lập trình không đồng bộ (APM) ) và sau đó cố gắng chuyển sang async/await
(Mẫu không đồng bộ dựa trên tác vụ (TAP) ) Tôi đã có khá nhiều thứ với việc phải tiếp tục bận tâm với tất cả 'hệ thống ống nước cấp thấp' này. Vì vậy, tôi đã tìm ra; tại sao không thử RX
( Phần mở rộng phản ứng ) vì nó có thể phù hợp hơn với miền vấn đề của tôi.
Rất nhiều mã tôi viết phải gửi cho nhiều khách hàng kết nối qua Tcp với ứng dụng của tôi, sau đó bắt đầu giao tiếp hai chiều (không đồng bộ). Bất cứ lúc nào, máy khách hoặc máy chủ có thể quyết định một tin nhắn cần được gửi và làm như vậy, vì vậy đây không phải là request/response
thiết lập cổ điển của bạn mà là "đường dây" hai chiều, thời gian thực mở cho cả hai bên để gửi bất cứ điều gì họ muốn , bất cứ khi nào họ muốn. (Nếu bất cứ ai có một cái tên đàng hoàng để mô tả điều này, tôi rất vui khi nghe nó!).
"Giao thức" khác nhau cho mỗi ứng dụng (và không thực sự phù hợp với câu hỏi của tôi). Tôi có, tuy nhiên có một câu hỏi ban đầu:
- Cho rằng chỉ có một "máy chủ" đang chạy, nhưng nó phải theo dõi nhiều (thường là hàng ngàn) kết nối (ví dụ: máy khách) mà mỗi máy chủ (vì không có mô tả tốt hơn) "máy trạng thái" của riêng chúng để theo dõi nội bộ của chúng vv, bạn thích cách tiếp cận nào? EAP / TAP / APM? RX thậm chí sẽ được coi là một lựa chọn? Nếu không, tại sao?
Vì vậy, tôi cần phải làm việc Async vì a) nó không phải là giao thức yêu cầu / phản hồi vì vậy tôi không thể có một luồng / ứng dụng khách trong một cuộc gọi chặn tin nhắn "chờ tin nhắn" hoặc "gửi tin nhắn" (tuy nhiên, nếu gửi là chặn cho khách hàng đó chỉ tôi mới có thể sống với nó) và b) Tôi cần xử lý nhiều kết nối đồng thời. Tôi thấy không có cách nào để làm điều này (đáng tin cậy) bằng cách sử dụng các cuộc gọi chặn.
Hầu hết các ứng dụng của tôi đều liên quan đến VoiP; có thể là tin nhắn SIP từ khách hàng SIP hoặc tin nhắn tổng đài (có liên quan) từ các ứng dụng như FreeSwitch / OpenSIPS, v.v. nhưng bạn có thể, ở dạng đơn giản nhất, hãy thử tưởng tượng một máy chủ "trò chuyện" đang cố xử lý nhiều máy khách "trò chuyện". Hầu hết các giao thức đều dựa trên văn bản (ASCII).
Vì vậy, sau khi thực hiện nhiều hoán vị khác nhau của các kỹ thuật đã nói ở trên, tôi muốn đơn giản hóa công việc của mình bằng cách tạo ra một đối tượng mà tôi có thể đơn giản khởi tạo, nói với nó IPEndpoint
để lắng nghe và nói với tôi bất cứ khi nào có chuyện gì xảy ra (mà tôi thường làm sử dụng các sự kiện cho, vì vậy một số EAP thường được trộn lẫn với hai kỹ thuật còn lại). Lớp học không nên cố gắng 'hiểu' giao thức; nó chỉ nên xử lý các chuỗi đến / đi. Và do đó, để mắt đến RX với hy vọng rằng (cuối cùng) sẽ đơn giản hóa công việc, tôi đã tạo ra một "câu đố" mới từ đầu:
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?
}
}
Tôi biết rằng, trong "fiddle" này, có một chi tiết cụ thể thực hiện; trong trường hợp này, tôi đang làm việc với FreeSwitch ESL, vì vậy, "connect\n\n"
trong câu đố, khi tái cấu trúc theo cách tiếp cận chung chung hơn, sẽ bị xóa.
Tôi cũng nhận thức được rằng tôi cần cấu trúc lại các phương thức ẩn danh thành các phương thức cá thể riêng tư trên lớp Máy chủ; Tôi chỉ không chắc chắn nên sử dụng quy ước nào (ví dụ " OnSomething
") cho tên phương thức của họ?
Đây là cơ sở / điểm bắt đầu / nền tảng của tôi (cần một số "điều chỉnh"). Tôi có một số câu hỏi về điều này:
- Xem câu hỏi trên "1"
- Có phải tôi đang trên đường ray bên phải không? Hay là những quyết định "thiết kế" của tôi không công bằng?
- Đồng thời khôn ngoan: điều này sẽ đối phó với hàng ngàn khách hàng (phân tích / xử lý các thông điệp thực tế sang một bên)
- Về các trường hợp ngoại lệ: Tôi không chắc chắn làm thế nào để đưa các ngoại lệ được nêu ra trong các máy khách "lên" đến máy chủ ("RX-khôn ngoan"); một cách tốt sẽ là gì?
- Bây giờ tôi có thể nhận bất kỳ máy khách nào được kết nối từ lớp máy chủ của mình (sử dụng nó
ClientId
), giả sử tôi tiếp xúc với máy khách bằng cách này hay cách khác và gọi phương thức trực tiếp trên chúng. Tôi cũng có thể gọi các phương thức thông qua lớp Máy chủ (ví dụ:Send(clientId, rawmessage)
phương thức (trong khi cách tiếp cận sau sẽ là phương thức "tiện lợi" để nhanh chóng nhận được tin nhắn sang phía bên kia). - Tôi không chắc chắn nơi (và làm thế nào) để đi từ đây:
- a) Tôi cần xử lý các tin nhắn đến; Làm thế nào tôi có thể thiết lập này? Tôi có thể lấy ofcourse suối, nhưng nơi tôi sẽ xử lý lấy các byte nhận được? Tôi nghĩ rằng tôi cần một số loại "ObservableStream" -một cái gì đó tôi có thể đăng ký? Tôi sẽ đặt cái này trong
FiddleClient
hayFiddleServer
? - b) Giả sử tôi muốn tránh sử dụng sự kiện cho đến khi các lớp
FiddleClient
/FiddleServer
lớp này được triển khai cụ thể hơn để điều chỉnh xử lý giao thức cụ thể cho ứng dụng của họ, v.v. bằng cách sử dụng các lớpFooClient
/FooServer
lớp cụ thể hơn: làm thế nào tôi có thể lấy dữ liệu trong các lớp 'Fiddle' bên dưới đối tác cụ thể hơn?
- a) Tôi cần xử lý các tin nhắn đến; Làm thế nào tôi có thể thiết lập này? Tôi có thể lấy ofcourse suối, nhưng nơi tôi sẽ xử lý lấy các byte nhận được? Tôi nghĩ rằng tôi cần một số loại "ObservableStream" -một cái gì đó tôi có thể đăng ký? Tôi sẽ đặt cái này trong
Các bài viết / liên kết tôi đã đọc / đọc lướt / sử dụng để tham khảo:
- Sử dụng một socket sử dụng phần mở rộng Reactive
- Tạo và đăng ký các chuỗi có thể quan sát đơn giản
- Cách thêm hoặc liên kết ngữ cảnh với mỗi Kết nối / Phiên kết nối ObservableTcpListener
- Lập trình không đồng bộ với Khung phản ứng và Thư viện song song nhiệm vụ - Phần 1 , 2 và 3 .
- Sử dụng phần mở rộng Reactive (Rx) để lập trình socket thực tế?
- Máy chủ web .NET Rx Driven và Máy chủ web .NET Rx Driven, mất 2
- Một số hướng dẫn Rx