2009-10-08 4 views
7

SocketAsyncEventArgs 이벤트를 사용하여 비동기 소켓 서버를 만들고 싶습니다.SocketAsyncEventArgs를 사용하는 서버 디자인

서버는 약 1000 개의 연결을 동시에 관리해야합니다. 각 패킷의 논리를 처리하는 가장 좋은 방법은 무엇입니까?

서버 디자인은 this MSDN example을 기반으로하므로 모든 소켓에는 데이터 수신을위한 자체 SocketAsyncEventArgs가 있습니다. 기능를받을 내부

  1. 는 논리 물건 마십시오. 오버 헤드가 생성되지 않지만 논리가 완료되기 전에 다음 ReceiveAsync() 호출이 완료되지 않으므로 새 데이터를 소켓에서 읽을 수 없습니다. 두 가지 주요 질문은 다음과 같습니다. 클라이언트가 많은 양의 데이터를 보내고 논리 처리가 무거울 경우 시스템이 버퍼를 가득 채워서 손실 된 패킷을 어떻게 처리할까요? 또한 모든 클라이언트가 동시에 데이터를 보내면 1000 개의 스레드가 있습니까? 아니면 내부 제한이 있으며 다른 스레드가 실행을 완료하기 전에 새 스레드를 시작할 수 없습니까?

  2. 대기열을 사용하십시오. 수신 기능은 매우 짧고 빠르게 실행되지만 대기열 때문에 정상적인 오버 헤드가 발생합니다. 문제는 worker 쓰레드가 과중한 서버 부하로 충분히 빠르지 않으면 대기열이 가득 차게되어 패킷을 강요해야한다는 것입니다. 또한 생산자/소비자 문제가 발생하여 많은 대기열로 전체 대기열을 느리게 할 수 있습니다.

그렇다면 더 나은 디자인, 수신 함수의 논리, 작업자 스레드의 논리 또는 지금까지 놓친 모든 것이 달라질 수 있습니다.

데이터 전송과 관련한 또 다른 퀘스트.

소켓 (수신 이벤트와 유사)에 묶여있는 SocketAsyncEventArgs를 가지고 몇 개의 작은 패킷에 대해 하나의 송신 호출을 만들기 위해 버퍼 시스템을 사용하는 것이 더 낫습니다 (패킷이 다른 경우가 있다고 가정 해 봅시다. 다른) 또는 모든 패킷에 대해 다른 SocketAsyncEventArgs를 사용하고 다시 사용할 수 있도록 풀에 저장합니까?

답변

10

비동기 소켓을 효과적으로 구현하려면 각 소켓에 1 개 이상의 SocketAsyncEventArgs가 필요합니다. 각 SocketAsyncEventArgs의 byte [] 버퍼에도 문제가 있습니다. 요컨대, 관리되는 네이티브 전환 (전송/수신)이 발생할 때마다 바이트 버퍼가 고정됩니다. 필요에 따라 SocketAsyncEventArgs 및 바이트 버퍼를 할당하면 조각화 및 GC가 고정 된 메모리를 압축 할 수 없기 때문에 많은 클라이언트에서 OutOfMemoryExceptions를 실행할 수 있습니다.

가장 좋은 방법은 응용 프로그램이 처음 시작될 때 많은 바이트와 SocketAsyncEventArgs를 할당하는 SocketBufferPool 클래스를 만드는 것입니다. 이렇게하면 고정 된 메모리가 인접하게됩니다. 그런 다음 필요에 따라 풀에서 버퍼를 단순히 다시 사용하십시오.

실제로 리소스 배포를 관리하기 위해 SocketAsyncEventArgs 및 SocketBufferPool 클래스 주위에 래퍼 클래스를 만드는 것이 가장 좋습니다.일례로서

은 여기 BeginReceive 메소드에 대한 코드는 다음

private void BeginReceive(Socket socket) 
    { 
     Contract.Requires(socket != null, "socket"); 

     SocketEventArgs e = SocketBufferPool.Instance.Alloc(); 
     e.Socket = socket; 
     e.Completed += new EventHandler<SocketEventArgs>(this.HandleIOCompleted); 

     if (!socket.ReceiveAsync(e.AsyncEventArgs)) { 
      this.HandleIOCompleted(null, e); 
     } 
    } 

및 여기 HandleIOCompleted 방법이다 : 위의 코드를 올릴 것이다 TcpSocket 클래스에 포함

private void HandleIOCompleted(object sender, SocketEventArgs e) 
    { 
     e.Completed -= this.HandleIOCompleted; 
     bool closed = false; 

     lock (this.sequenceLock) { 
      e.SequenceNumber = this.sequenceNumber++; 
     } 

     switch (e.LastOperation) { 
      case SocketAsyncOperation.Send: 
      case SocketAsyncOperation.SendPackets: 
      case SocketAsyncOperation.SendTo: 
       if (e.SocketError == SocketError.Success) { 
        this.OnDataSent(e); 
       } 
       break; 
      case SocketAsyncOperation.Receive: 
      case SocketAsyncOperation.ReceiveFrom: 
      case SocketAsyncOperation.ReceiveMessageFrom: 
       if ((e.BytesTransferred > 0) && (e.SocketError == SocketError.Success)) { 
        this.BeginReceive(e.Socket); 
        if (this.ReceiveTimeout > 0) { 
         this.SetReceiveTimeout(e.Socket); 
        } 
       } else { 
        closed = true; 
       } 

       if (e.SocketError == SocketError.Success) { 
        this.OnDataReceived(e); 
       } 
       break; 
      case SocketAsyncOperation.Disconnect: 
       closed = true; 
       break; 
      case SocketAsyncOperation.Accept: 
      case SocketAsyncOperation.Connect: 
      case SocketAsyncOperation.None: 
       break; 
     } 

     if (closed) { 
      this.HandleSocketClosed(e.Socket); 
     } 

     SocketBufferPool.Instance.Free(e); 
    } 

DataReceived & DataSent 이벤트입니다. 주목해야 할 한 가지는 SocketAsyncOperation.ReceiveMessageFrom : block; 소켓에 오류가 없으면 다른 BeginReceive()가 즉시 시작되어 풀에서 다른 SocketEventArgs를 할당합니다.

또 다른 중요한 메모는 HandleIOComplete 메서드에 설정된 SocketEventArgs SequenceNumber 속성입니다. 비동기 요청은 대기열에있는 순서대로 완료되지만 다른 스레드 경쟁 조건의 영향을받을 수 있습니다. DataReceived 이벤트를 발생시키기 전에 코드가 BeginReceive를 호출하기 때문에 BeginReceive를 호출 한 후 이벤트를 재개하기 전에 원본 IOCP를 처리하는 스레드가 차단되고 두 번째 비동기 수신이 새 스레드에서 완료되면 DataReceived 이벤트가 먼저 발생합니다. 이것은 매우 드문 경우이지만 발생할 수 있으며 SequenceNumber 속성은 소비하는 응용 프로그램에 데이터가 올바른 순서로 처리되도록하는 기능을 제공합니다.

다른주의해야 할 영역 중 하나는 비동기 전송입니다. 종종 비동기 전송 요청은 동기식으로 완료되며 호출이 동기식으로 완료되면 SendAsync는 false를 반환하고 성능을 심각하게 저하시킬 수 있습니다. IOCP에서 다시 발생하는 비동기 호출의 추가 오버 헤드는 실제로 동기 호출을 사용하는 것보다 성능이 떨어질 수 있습니다. 비동기 호출은 스택에서 동기 호출이 발생하는 동안 두 개의 커널 호출과 힙 할당을 필요로합니다.

if (!socket.ReceiveAsync(e.AsyncEventArgs)) { 
    this.HandleIOCompleted(null, e); 
} 

을하지만 그렇게하면 오류가 발생합니다 :이 도움이

희망, 빌 코드에서

-1

, 당신은이 작업을 수행. 동 기적으로 완료 될 때 콜백이 호출되지 않는 이유가 있습니다. 그러한 액션은 스택을 채울 수 있습니다.

각 ReceiveAsync가 항상 동기식으로 반환된다고 상상해보십시오. HandleIOCompleted가 잠시 동안 있었던 경우 동일한 스택 레벨에서 동 기적으로 반환 된 결과를 처리 할 수 ​​있습니다. 동 기적으로 반환되지 않으면 잠시 중단됩니다. 하지만, 당신이해야 할 일은 결국 스택에 새 항목을 만드는 것입니다. 그래서 불운이 발생하면 스택 오버 플로우 예외가 발생합니다.

관련 문제