2016-12-28 18 views
2

私はバックエンドサーバに接続されたシンプルなTCPフォワーダを実装しました。このサーバーはhttp://localhost:5000に設定されており、このTCPフォワーダはhttp://localhost:5001を受信して​​います。 wrkwhich is a HTTP benchmarking tool to generator loadを使用して別のマシンを使用して負荷を生成すると、2つの異なる結果が生じます。 kestrelasp.net core web apiに基づくサービスに負荷を直接送信すると、230Kリクエスト/秒以上が処理されますが、このTCPフォワーダに負荷を送信すると、83Krequest /秒を処理できます。コードは次のとおりです。Cでソケットを使用してTCPフォワーダを改善する#

using System; 
using System.Net; 
using System.Net.Sockets; 

namespace BrunoGarcia.Net 
{ 
    static void Main(string[] args) 
    { 
     new TcpForwarderSlim().Start(
      new IPEndPoint(IPAddress.Parse(args[0]), int.Parse(args[1])), 
      new IPEndPoint(IPAddress.Parse(args[2]), int.Parse(args[3]))); 
    } 
    public class TcpForwarderSlim 
    { 
     private readonly Socket _mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 

     public void Start(IPEndPoint local, IPEndPoint remote) 
     { 
      _mainSocket.Bind(local); 
      _mainSocket.Listen(10); 

      while (true) 
      { 
       var source = _mainSocket.Accept(); 
       var destination = new TcpForwarderSlim(); 
       var state = new State(source, destination._mainSocket); 
       destination.Connect(remote, source); 
       source.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state); 
      } 
     } 

     private void Connect(EndPoint remoteEndpoint, Socket destination) 
     { 
      var state = new State(_mainSocket, destination); 
      _mainSocket.Connect(remoteEndpoint); 
      _mainSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, OnDataReceive, state); 
     } 

     private static void OnDataReceive(IAsyncResult result) 
     { 
      var state = (State)result.AsyncState; 
      try 
      { 
       var bytesRead = state.SourceSocket.EndReceive(result); 
       if (bytesRead > 0) 
       { 
        state.DestinationSocket.Send(state.Buffer, bytesRead, SocketFlags.None); 
        state.SourceSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state); 
       } 
      } 
      catch 
      { 
       state.DestinationSocket.Close(); 
       state.SourceSocket.Close(); 
      } 
     } 

     private class State 
     { 
      public Socket SourceSocket { get; private set; } 
      public Socket DestinationSocket { get; private set; } 
      public byte[] Buffer { get; private set; } 

      public State(Socket source, Socket destination) 
      { 
       SourceSocket = source; 
       DestinationSocket = destination; 
       Buffer = new byte[8192]; 
      } 
     } 
    } 
} 

問題は何ですか? TCPフォワーダを使用すると、結果をどのように改善できますか?または、ポートをリッスンして2つ以上のバックエンドサービスの1つにTCPリクエストを送信するためのトンネルまたはフォワーダを作成するより良い方法はありますか。

答えて

0

state.DestinationSocket.Sendが完了するまで、さらにデータを受信し始めません。送信を処理し始めるとすぐに、より多くのデータの受信を開始することができます。the order of multiple BeginSend calls is preservedこれに切り替えた場合は、前のリクエストが処理される前に次のリクエストの処理を開始できます。

重要なメモ!新しいBeginReceiveリクエストごとに、新しいバッファを作成する(またはバッファプールを使用する)必要があります。以下はテストされていないコードですが、うまくいけば、あなたを正しい道に導くことができます。

public class TcpForwarderSlim 
{ 
    private readonly Socket _mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 

    public void Start(IPEndPoint local, IPEndPoint remote) 
    { 
     _mainSocket.Bind(local); 
     _mainSocket.Listen(10); 

     while (true) 
     { 
      var source = _mainSocket.Accept(); 
      var destination = new TcpForwarderSlim(); 
      var state = new State(source, destination._mainSocket); 
      destination.Connect(remote, source); 
      source.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state); 
     } 
    } 

    private void Connect(EndPoint remoteEndpoint, Socket destination) 
    { 
     var state = new State(_mainSocket, destination); 
     _mainSocket.Connect(remoteEndpoint); 
     _mainSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, OnDataReceive, state); 
    } 

    private static void OnDataReceive(IAsyncResult result) 
    { 
     var state = (State)result.AsyncState; 
     try 
     { 
      var bytesRead = state.SourceSocket.EndReceive(result); 
      if (bytesRead > 0) 
      { 
       //Start an asyncronous send. 
       var sendAr = state.DestinationSocket.BeginSend(state.Buffer, 0, bytesRead, SocketFlags.None,null,null); 

       //Get or create a new buffer for the state object. 
       var oldBuffer = state.ReplaceBuffer(); 

       state.SourceSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state); 

       //Wait for the send to finish. 
       state.DestinationSocket.EndSend(sendAr); 

       //Return byte[] to the pool. 
       state.AddBufferToPool(oldBuffer); 
      } 
     } 
     catch 
     { 
      state.DestinationSocket.Close(); 
      state.SourceSocket.Close(); 
     } 
    } 

    private class State 
    { 
     private readonly ConcurrentBag<byte[]> _bufferPool = new ConcurrentBag<byte[]>(); 
     private readonly int _bufferSize; 
     public Socket SourceSocket { get; private set; } 
     public Socket DestinationSocket { get; private set; } 
     public byte[] Buffer { get; private set; } 

     public State(Socket source, Socket destination) 
     { 
      SourceSocket = source; 
      DestinationSocket = destination; 
      _bufferSize = Math.Min(SourceSocket.ReceiveBufferSize, DestinationSocket.SendBufferSize); 
      Buffer = new byte[_bufferSize]; 
     } 

     /// <summary> 
     /// Replaces the buffer in the state object. 
     /// </summary> 
     /// <returns>The previous buffer.</returns> 
     public byte[] ReplaceBuffer() 
     { 
      byte[] newBuffer; 
      if (!_bufferPool.TryTake(out newBuffer)) 
      { 
       newBuffer = new byte[_bufferSize]; 
      } 
      var oldBuffer = Buffer; 
      Buffer = newBuffer; 
      return oldBuffer; 
     } 

     public void AddBufferToPool(byte[] buffer) 
     { 
      _bufferPool.Add(buffer); 
     } 
    } 
} 
+0

ありがとうございますが、このソリューションはrpsを10Kに完全にダウングレードしました。 :| – Alex

+0

それは価値があった。この種のソリューションを最適化するより良い方法があるかもしれません。私は2つのバージョンでプロファイラを使用し、両方のホットスポットがどこにあるかを見ていきます。また、このソリューションをより大きなバッファー・サイズまたはより小さなバッファー・サイズで試してください。私は自分の 'Math.Min(SourceSocket.ReceiveBufferSize、DestinationSocket.SendBufferSize); 'が助けになると思ったが、おそらくパフォーマンスを傷つけるだろう。 –

関連する問題