2011-08-18 4 views
5

클라이언트/서버 인프라가 있습니다. 현재 TcpClient와 TcpListener를 사용하여 모든 클라이언트와 서버간에 수신 데이터를 전송합니다.여러 개의 TCP 클라이언트를 허용하는 가장 좋은 방법은 무엇입니까?

현재 내가하는 일은 데이터를 수신 할 때 (자체 스레드에서) 데이터를받을 때 소켓을 해제하여 새 데이터를 수신 할 수 있도록 다른 스레드가 처리 할 큐에 넣습니다.

   // 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); 
       } 

그러나 이것을 수행하는 더 좋은 방법이 있는지 알고 싶었습니다. 예를 들어, 10 개의 클라이언트가 있고 동시에 모두 서버에 데이터를 보내려고하면 다른 서버는 모두 실패합니다. 그렇지 않으면 하나의 클라이언트가 느린 연결을 가지고 있고 소켓을 점유하면 다른 모든 통신이 중단됩니다 .

동시에 모든 클라이언트로부터 데이터를 수신하고 다운로드 완료시 처리를 위해 큐에 수신 된 데이터를 추가 할 수있는 방법이 있습니까?

+0

뻔뻔한 플러그 : http://jonathan.dickinsons.co.za/blog/2011/02/net-sockets-and-you/ - 비동기 루프를 짧게 터치합니다. 실제의 구현을 포함하고 있습니다 (@Jalal가 제안한 것처럼'ThreadPool'을 사용해서는 안됩니다). –

답변

15

그래서 여기에 대한 답변이 나와 있습니다. 이는 내 blog post보다 초급 수준입니다.

.Net에는 Begin * 및 End * 호출을 중심으로 비동기 패턴이 있습니다. 예 : BeginReceiveEndReceive. 그들은 거의 항상 비동기 대응 (이 경우 Receive)입니다. 동일한 목표를 달성하십시오.

기억해야 할 가장 중요한 점은 소켓이 async를 호출하는 것 이상을한다는 것입니다. IOCP (IO Completion Ports, Linux/Mono는이 두 가지를 가지고 있지만 이름은 잊어 버렸습니다) 서버에서 사용하기; IOCP가하는 ​​핵심은 애플리케이션이 데이터를 기다리는 동안 쓰레드를 소비하지 않는다는 것입니다. 시작/종료 패턴

를 사용하는 방법

모든 시작 *가 아닌 비동기 대응의에 방법은 comparisson 정확히 2 이상의 인수를해야합니다.첫 번째는 AsyncCallback이고 두 번째는 객체입니다. 이 두 가지 의미는 "완료되면 전화를 걸 수있는 방법입니다." "여기에 필요한 데이터가 있습니다." 호출되는 메서드는 항상 동일한 서명을 가지므로이 메서드 내부에서 End *를 호출하여 동 기적으로 수행 한 결과가 무엇인지 얻을 수 있습니다. 그래서 예를 들어 : 여기 어떻게됩니까

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. 
} 

닷넷이 바로 그것 EndReceiveBuffer를 호출하고 여기에 (이 경우 buffer 단위) '사용자 정의 데이터'를 통과 데이터를수록, 소켓에서 데이터를 기다리는 시작합니다 state.AsyncResult을 통해 EndReceive으로 전화하면 수신 된 데이터의 길이가 되돌려집니다 (또는 실패한 경우 예외가 발생합니다). 소켓에 대한

더 나은 패턴

이 양식을 중앙 오류 처리를 줄 것이다 - 그것은 어디서나 사용할 수있는 비동기 패턴이 전송되었습니다 (예 : TCP가 순서대로 도착 스트림과 같은 '일을'랩 곳 따라서 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. 
    } 
} 

수락 여러 연결 당신이 일반적으로 할 당신의 소켓 등 (뿐만 아니라 비동기 루프)를 포함하는 클래스를 작성하고 각 클라이언트에 대해 하나를 만들 무엇

. 예를 들어 그래서 (서버가 종료 될 때/검색뿐만 아니라, 깨끗하게 분리 한을 검색 할 수 있도록 (듯이))

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() ... 
} 

각 클라이언트 연결이 서버 클래스에 의해 추적해야합니다.

+0

+1 모든 멋진 자료를 보내 주셔서 감사합니다. – Dylan

+0

나는 당신도 클라이언트를 똑같이 받아 들일 수 있다고 언급하는 것을 잊었다. BeginAcceptTcpClient. 비동기 루프도 동일하게 설정할 수 있습니다. –

+1

블로그 게시물 링크가 죽었습니다. 하지만 여기 archive.org에 있습니다 : https://web.archive.org/web/20121127003207/http://jonathan.dickinsons.co.za/blog/2011/02/net-sockets-and-you –

0

일반적으로 여러 스레드가있는 스레드 풀을 사용하고 있습니다. 각각의 새로운 연결마다 풀의 스레드 중 하나에서 연결 처리 (사용자의 경우 - using 절에서 수행하는 모든 작업)를 실행하고 있습니다.

두 개의 성능을 동시에 달성 할 수 있습니다. 동시에 여러 개의 연결을 허용하고 들어오는 연결을 처리하기 위해 할당하는 리소스 (스레드 등)의 수를 제한하기 때문입니다.

당신은 좋은 예에게이 here

행운

+0

다시 Thread-Per-Client를 사용합니다. 이것은 정말로 나쁜 습관입니다. –

1

당신은 데이터를 읽는 비동기 방법을 사용한다, 예입니다

또한
// 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); 
} 

당신은에 AutoResetEvent 또는 ManualResetEvent을 사용하는 것이 좋습니다 새 데이터가 컬렉션에 추가 될 때 알림을받습니다. 따라서 데이터를 처리하는 스레드는 데이터가 수신되는 시점을 알게되고 4.0을 사용하는 경우 0을 사용하는 것이 좋습니다. Queue 대신입니다.

+0

이미 BlockingCollection을 사용 중입니다. 그리고 파일을받을 전용 스레드가 있다는 사실 때문에 동기 메서드를 사용하고 있습니다. 위의 예에서 두 클라이언트가 동시에 연결되면 server.AcceptTcpClient가 TcpListener가 다음에 사용 가능할 때까지 (HandleTcp 이후) 둘 다 허용합니까, 아니면 하나가 대기 할 것입니까? 참고로 .Net 4를 사용하는 경우 ThreadPool 대신 Task 라이브러리를 사용해야합니다. – Dylan

+0

두 스레드 모두 받아들이므로 여기에'Thread'를 사용하고 있습니다. 다른 스레드의 첫 번째 데이터를 읽으므로 현재 스레드를 차단하지 않으므로 ThreadPoo.Queu..는 핸들을 반환합니다. 호출자 스레드는 즉시 동시에 동시에 클라이언트를 처리 할 새 스레드를 작성합니다. –

+0

-1 ThreadPool을 사용합니다. 닷넷 3.5에서는'Begin/End-Receive'를 사용해야합니다. .Net 4.0에서는 새로운 이벤트 모델 또는 TPL을 사용할 수 있습니다. 귀하의 코드는 IOCP를 전혀 사용하지 않습니다. 그것은 그것을 가난한 제안으로 만든다. –

1

이렇게하려면 비동기 소켓 프로그래밍을 사용해야합니다. MSDN에서 제공하는 example을 살펴보십시오.

+1

+1 MSDN 인용 - 진실의 근원 :). –

관련 문제