Lập trình mạng không đồng bộ bằng phần mở rộng Reactive


25

Sau khi thực hiện một số socketchươ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/responsethiế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:

  1. 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:

  1. Xem câu hỏi trên "1"
  2. 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?
  3. Đồ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)
  4. 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ì?
  5. 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).
  6. 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 FiddleClienthay FiddleServer?
    • b) Giả sử tôi muốn tránh sử dụng sự kiện cho đến khi các lớp FiddleClient/ FiddleServerlớ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ớp FooClient/ FooServerlớ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?

Các bài viết / liên kết tôi đã đọc / đọc lướt / sử dụng để tham khảo:


Hãy xem thư viện ReactiveSockets hiện có
Flagbird

2
Tôi không tìm kiếm các thư viện hoặc liên kết (mặc dù để tham khảo, chúng được đánh giá cao) nhưng để nhập / tư vấn / trợ giúp cho các câu hỏi và thiết lập chung của tôi. Tôi muốn học cách cải thiện mã của riêng mình và có thể quyết định tốt hơn về hướng nào để thực hiện điều này, cân nhắc các ưu điểm và khuyết điểm, v.v. Không tham khảo một số thư viện, thả nó vào và tiếp tục. Tôi muốn học hỏi kinh nghiệm này và có thêm kinh nghiệm với lập trình Rx / mạng.
RobIII

Chắc chắn, nhưng vì thư viện là nguồn mở, bạn có thể thấy cách nó được triển khai ở đó
Flagbird

1
Chắc chắn, nhưng nhìn vào mã nguồn không giải thích tại sao một số quyết định thiết kế đã / được đưa ra. Và bởi vì tôi còn khá mới với Rx kết hợp với lập trình Mạng, tôi không có đủ kinh nghiệm để biết liệu thư viện này có tốt không, nếu thiết kế hợp lý, nếu quyết định đúng được đưa ra và ngay cả khi nó phù hợp với tôi.
RobIII

Tôi nghĩ sẽ tốt khi suy nghĩ lại về máy chủ như một thành viên tích cực của một cái bắt tay, vì vậy, máy chủ đó khởi tạo kết nối thay vì lắng nghe các kết nối. Ví dụ: tại đây: codeproject.com/Articles/20250/Reverse-Connection-Shell

Câu trả lời:


1

... Tôi muốn đơn giản hóa công việc của mình bằng cách tạo một đối tượng mà tôi có thể khởi tạo đơn giản, nói cho nó biết IPEndpoint nào sẽ lắng nghe và bảo nó cho tôi biết bất cứ khi nào điều gì đó thú vị đang diễn ra ...

Sau khi đọc câu nói đó tôi nghĩ ngay đến "diễn viên". Các diễn viên rất giống với các đối tượng ngoại trừ họ chỉ có một đầu vào duy nhất trong đó bạn truyền thông điệp đó (thay vì gọi trực tiếp các phương thức của đối tượng) và chúng hoạt động không đồng bộ. Trong một ví dụ rất đơn giản ... bạn sẽ tạo diễn viên và gửi tin nhắn với IPEndpoint và địa chỉ của diễn viên để gửi kết quả tới. Nó tắt và nó hoạt động trong nền. Bạn chỉ nghe lại từ đó khi "điều gì đó quan tâm" xảy ra. Bạn có thể khởi tạo nhiều diễn viên như bạn cần để xử lý tải.

Tôi không quen thuộc với bất kỳ thư viện diễn viên nào trong .Net mặc dù tôi biết có một số. Tôi quen thuộc với thư viện TPL Dataflow (sẽ có một phần bao gồm nó trong cuốn sách của tôi http://DataflowBook.com ) và thật dễ dàng để thực hiện một mô hình diễn viên đơn giản với thư viện đó.


Điều đó có vẻ thú vị. Tôi sẽ thiết lập một dự án đồ chơi để xem nó có phù hợp với tôi không. Cám ơn vì sự gợi ý.
RobIII
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.