2011-08-18 23 views
5

Tôi có cơ sở hạ tầng máy khách/máy chủ. Hiện tại họ sử dụng TcpClient và TcpListener để gửi dữ liệu nhận giữa tất cả các máy khách và máy chủ.Cách tốt nhất để chấp nhận nhiều khách hàng tcp?

Điều tôi hiện đang làm là khi nhận dữ liệu (trên chỉ riêng của nó), nó được xếp vào hàng đợi cho một luồng khác để xử lý để giải phóng ổ cắm để sẵn sàng và mở để nhận dữ liệu mới.

   // Enter the listening loop. 
       while (true) 
       { 
        Debug.WriteLine("Waiting for a connection... "); 

        // Perform a blocking call to accept requests. 
        using (client = server.AcceptTcpClient()) 
        { 
         data = new List<byte>(); 

         // Get a stream object for reading and writing 
         using (NetworkStream stream = client.GetStream()) 
         { 
          // Loop to receive all the data sent by the client. 
          int length; 

          while ((length = stream.Read(bytes, 0, bytes.Length)) != 0) 
          { 
           var copy = new byte[length]; 
           Array.Copy(bytes, 0, copy, 0, length); 
           data.AddRange(copy); 
          } 
         } 
        } 

        receivedQueue.Add(data); 
       } 

Tuy nhiên tôi muốn tìm hiểu xem có cách nào tốt hơn để thực hiện việc này không. Ví dụ nếu có 10 khách hàng và tất cả họ đều muốn gửi dữ liệu đến máy chủ cùng một lúc, người dùng sẽ nhận được thông tin trong khi tất cả những người khác sẽ không thành công. Hoặc nếu một khách hàng có kết nối chậm và sẽ chặn tất cả các liên lạc khác sẽ dừng .

Không có cách nào để có thể nhận dữ liệu từ tất cả khách hàng cùng một lúc và thêm dữ liệu nhận được vào hàng đợi để xử lý khi tải xuống xong?

+0

Phích cắm không biết xấu hổ: http://jonathan.dickinsons.co.za/blog/2011/02/net-sockets-and-you/ - nó chạm nhanh vào vòng lặp async; và có triển khai thực sự (bạn không nên sử dụng 'ThreadPool' như @Jalal được đề xuất). –

Trả lời

15

Vì vậy, đây là câu trả lời sẽ giúp bạn bắt đầu - cấp độ mới bắt đầu hơn số blog post của tôi.

.Net có mẫu không đồng bộ xoay quanh cuộc gọi Bắt đầu * và Kết thúc *. Ví dụ: BeginReceiveEndReceive. Họ gần như luôn luôn có đối tác không đồng bộ của họ (trong trường hợp này là Receive); và đạt được mục tiêu tương tự. Điều quan trọng nhất cần nhớ là những ổ cắm làm nhiều hơn là chỉ làm cho cuộc gọi không đồng bộ - chúng lộ ra một cái gì đó gọi là IOCP (Cổng IO hoàn thành, Linux/Mono có hai nhưng tôi quên tên) đó là cực kỳ quan trọng để sử dụng trên máy chủ; Điểm mấu chốt của những gì IOCP làm là ứng dụng của bạn không tiêu thụ một luồng trong khi nó chờ dữ liệu.

Làm thế nào để sử dụng Các Begin/End Pattern

Mỗi Begin * phương pháp sẽ có chính xác 2 nhiều tranh cãi trong comparisson để nó không async đối tác.Đầu tiên là một AsyncCallback, thứ hai là một đối tượng. Ý nghĩa của hai điều này là "đây là một phương thức để gọi khi bạn hoàn thành" và "đây là một số dữ liệu tôi cần bên trong phương thức đó". Phương thức được gọi luôn có cùng chữ ký, bên trong phương thức này bạn gọi là đối tác End * để có được kết quả nếu bạn đã thực hiện nó một cách đồng bộ. Vì vậy, ví dụ:

private void BeginReceiveBuffer() 
{ 
    _socket.BeginReceive(buffer, 0, buffer.Length, BufferEndReceive, buffer); 
} 

private void EndReceiveBuffer(IAsyncResult state) 
{ 
    var buffer = (byte[])state.AsyncState; // This is the last parameter. 
    var length = _socket.EndReceive(state); // This is the return value of the method call. 
    DataReceived(buffer, 0, length); // Do something with the data. 
} 

gì xảy ra ở đây là Net bắt đầu chờ đợi dữ liệu từ các ổ cắm, ngay khi nó nhận dữ liệu mà họ gọi EndReceiveBuffer và đi qua 'dữ liệu tùy chỉnh' (trong trường hợp này buffer) để nó qua số state.AsyncResult. Khi bạn gọi EndReceive, nó sẽ cung cấp cho bạn độ dài của dữ liệu đã nhận được (hoặc ném một ngoại lệ nếu có điều gì đó không thành công). Pattern

tốt hơn cho Sockets

Hình thức này sẽ cung cấp cho bạn xử lý lỗi trung tâm - nó có thể được sử dụng bất cứ nơi nào mô hình async kết thúc tốt đẹp một 'điều' dòng giống (ví dụ TCP đến theo thứ tự nó đã được gửi , vì vậy nó có thể được xem như một đối tượng Stream).

private Socket _socket; 
private ArraySegment<byte> _buffer; 
public void StartReceive() 
{ 
    ReceiveAsyncLoop(null); 
} 

// Note that this method is not guaranteed (in fact 
// unlikely) to remain on a single thread across 
// async invocations. 
private void ReceiveAsyncLoop(IAsyncResult result) 
{ 
    try 
    { 
     // This only gets called once - via StartReceive() 
     if (result != null) 
     { 
      int numberOfBytesRead = _socket.EndReceive(result); 
      if(numberOfBytesRead == 0) 
      { 
       OnDisconnected(null); // 'null' being the exception. The client disconnected normally in this case. 
       return; 
      } 

      var newSegment = new ArraySegment<byte>(_buffer.Array, _buffer.Offset, numberOfBytesRead); 
      // This method needs its own error handling. Don't let it throw exceptions unless you 
      // want to disconnect the client. 
      OnDataReceived(newSegment); 
     } 

     // Because of this method call, it's as though we are creating a 'while' loop. 
     // However this is called an async loop, but you can see it the same way. 
     _socket.BeginReceive(_buffer.Array, _buffer.Offset, _buffer.Count, SocketFlags.None, ReceiveAsyncLoop, null); 
    } 
    catch (Exception ex) 
    { 
     // Socket error handling here. 
    } 
} 

Chấp nhận Nhiều Connections

gì bạn thường làm là viết một lớp có chứa ổ cắm vv (cũng như vòng lặp async của bạn) của bạn và tạo một cho mỗi khách hàng. Vì vậy, ví dụ:

public class InboundConnection 
{ 
    private Socket _socket; 
    private ArraySegment<byte> _buffer; 

    public InboundConnection(Socket clientSocket) 
    { 
     _socket = clientSocket; 
     _buffer = new ArraySegment<byte>(new byte[4096], 0, 4096); 
     StartReceive(); // Start the read async loop. 
    } 

    private void StartReceive() ... 
    private void ReceiveAsyncLoop() ... 
    private void OnDataReceived() ... 
} 

Mỗi kết nối khách hàng nên được theo dõi bởi lớp máy chủ của bạn (để bạn có thể ngắt kết nối chúng sạch khi máy chủ tắt, cũng như tìm kiếm/tìm họ).

+0

+1 Cảm ơn tất cả các tài liệu tuyệt vời. – Dylan

+0

Tôi quên đề cập rằng bạn cũng có thể chấp nhận khách hàng theo cùng một cách, ví dụ: BeginAcceptTcpClient. Bạn có thể thiết lập vòng lặp async giống hệt nhau. –

+1

Liên kết bài đăng trên blog đã chết. Nhưng tại đây, trên archive.org: https://web.archive.org/web/20121127003207/http://jonathan.dickinsons.co.za/blog/2011/02/net-sockets-and-you –

0

Điều tôi thường làm là sử dụng một nhóm luồng có nhiều luồng. Khi mỗi kết nối mới tôi đang chạy xử lý kết nối (trong trường hợp của bạn - mọi thứ bạn làm trong mệnh đề đang sử dụng) trong một trong các chủ đề từ nhóm.

Bằng cách đó bạn đạt được cả hiệu suất vì bạn cho phép một số kết nối đồng thời chấp nhận và bạn cũng giới hạn số lượng tài nguyên (chủ đề, v.v.) mà bạn phân bổ để xử lý các kết nối đến.

Bạn có một ví dụ tốt đẹp here

Good Luck

+0

Một lần nữa với điều Thread-Per-Client. Đây thực sự là thực hành tồi. –

1

Bạn nên sử dụng phương pháp không đồng bộ của việc đọc dữ liệu, một ví dụ là:

// Enter the listening loop. 
while (true) 
{ 
    Debug.WriteLine("Waiting for a connection... "); 

    client = server.AcceptTcpClient(); 

    ThreadPool.QueueUserWorkItem(new WaitCallback(HandleTcp), client); 
} 

private void HandleTcp(object tcpClientObject) 
{ 
    TcpClient client = (TcpClient)tcpClientObject; 
    // Perform a blocking call to accept requests. 

    data = new List<byte>(); 

    // Get a stream object for reading and writing 
    using (NetworkStream stream = client.GetStream()) 
    { 
     // Loop to receive all the data sent by the client. 
     int length; 

     while ((length = stream.Read(bytes, 0, bytes.Length)) != 0) 
     { 
      var copy = new byte[length]; 
      Array.Copy(bytes, 0, copy, 0, length); 
      data.AddRange(copy); 
     } 
    } 

    receivedQueue.Add(data); 
} 

Ngoài ra bạn nên xem xét sử dụng AutoResetEvent hoặc ManualResetEvent để được thông báo khi dữ liệu mới được thêm vào bộ sưu tập sao cho chuỗi xử lý dữ liệu sẽ biết khi nào dữ liệu được nhận và nếu bạn đang sử dụng 4.0, bạn nên tắt để sử dụng BlockingCollection thay vì Queue.

+0

Đã sử dụng BlockingCollection. Và tôi đang sử dụng các phương thức đồng bộ do thực tế là tôi có một chuỗi chuyên dụng để nhận các tệp. Trong ví dụ trên, nếu hai máy khách kết nối cùng một lúc, server.AcceptTcpClient sẽ chấp nhận cả hai hoặc sẽ được xếp hàng đợi cho đến khi TcpListener có sẵn tiếp theo (sau HandleTcp)? Cũng như một lưu ý, nếu bạn đang sử dụng .Net 4 bạn nên sử dụng thư viện Task thay vì ThreadPool. – Dylan

+0

Nó sẽ chấp nhận cả hai, đó là lý do tại sao chúng ta đang sử dụng 'Thread' ở đây, bởi vì nó sẽ đọc dữ liệu đầu tiên tại một luồng khác để nó không chặn luồng hiện tại, vì vậy' ThreadPoo.Queu..' sẽ trả về tay cầm chuỗi người gọi ngay lập tức và đồng thời tạo một chuỗi mới để xử lý ứng dụng khách. –

+0

-1 để sử dụng ThreadPool. Trong Net 3.5 bạn nên sử dụng 'Bắt đầu/Kết thúc-Nhận'. Trên .Net 4.0 bạn có thể sử dụng mô hình sự kiện mới, hoặc TPL. Mã của bạn không sử dụng IOCP chút nào; mà làm cho nó một gợi ý nghèo nàn. –

1

Bạn nên sử dụng lập trình socket không đồng bộ để đạt được điều này. Hãy xem example do MSDN cung cấp.

+1

+1 để trích dẫn MSDN - nguồn gốc của sự thật :). –