47

私は経験豊富な C# 開発者ですが、これまで TCP サーバー アプリケーションを開発したことはありません。現在、少なくとも 5 ~ 10,000 の同時接続を処理できる高度にスケーラブルで高性能なサーバーを開発する必要があります: GPS デバイスから GPRS を介して生のバイト データを取得します。

一般的な通信プロセスは次のようになります。

  • GPS デバイスがサーバーへの接続を開始する
  • データを取得したい場合、サーバーが応答します
  • デバイスが GPS データを送信する
  • 私のサーバーは、それを取得することについてデバイスにレポートを送信します(チェックサムのようなsg)
  • GPS、レポートから新しいデータを取得すると、これが何度も発生します
  • 後で GPS DEVICE が接続を閉じます

だから、私のサーバーでは必要です

  • 接続/アクティブなクライアントをトレースする
  • サーバー側からクライアントを閉じる
  • デバイスが接続を閉じたときにイベントをキャッチします
  • バイトデータを取得する
  • クライアントにデータを送信する

このトピックについてインターネットで読み始めましたが、それは私にとって悪夢のようです. 方法はたくさんありますが、どれが一番いいのかわかりませんでした。

私には非同期ソケット メソッドが最適のように思えますが、この非同期スタイルでコードを記述するのはひどく、デバッグが容易ではありません。

私の質問は、C# で高性能 TCP サーバーを実装する最良の方法はどれだと思いますか? これを行うための適切なオープン ソース コンポーネントを知っていますか? (いろいろ試しましたが、いいのが見つかりませんでした。)

4

5 に答える 5

42

これは非同期でなければなりません。これを回避する方法はありません。高いパフォーマンスとスケーラビリティは、ソケットあたり 1 スレッドとは相容れません。StackExchange 自体が何を行っているかを見ることができます。async Redis await BookSleeveを参照してください。これは、次の C# リリースからの CTP 機能を活用します (これはエッジにあり、変更される可能性がありますが、クールです)。さらに最先端を行くために、ソリューションはSocketAsyncEventArgs クラスを活用することを中心に進化しています。これは、「従来の」C# 非同期処理に関連する非同期ハンドラーの頻繁な割り当てを排除することで、さらに一歩進んでいます。

SocketAsyncEventArgs クラスは、System.Net.Sockets.Socket クラスに対する一連の拡張機能の一部であり、専用の高性能ソケット アプリケーションで使用できる代替の非同期パターンを提供します。このクラスは、高いパフォーマンスを必要とするネットワーク サーバー アプリケーション用に特別に設計されました。アプリケーションは、強化された非同期パターンを排他的に使用することも、特定のホット エリア (大量のデータを受信する場合など) でのみ使用することもできます。

簡単に言えば、非同期を学ぶか、試して死ぬか...

ところで、非同期の理由を知りたい場合は、この投稿からリンクされている 3 つの記事をお読みください: High Performance Windows programs。究極の答えは、基盤となる OS の設計で必要になるということです。

于 2011-05-16T21:02:00.140 に答える
13

Remus が上で述べているように、パフォーマンスを高く保つには async を使用する必要があります。それが .NET の Begin.../End... メソッドです。

ソケットの内部では、これらのメソッドは IO Completion Ports を利用します。これは、Windows オペレーティング システムで多くのソケットを処理する最もパフォーマンスの高い方法と思われます。

Jim が言うように、ここでは TcpClient クラスが役立ち、非常に使いやすいです。これは、TcpListener を使用して着信接続をリッスンし、TcpClient を使用してそれらを処理する例です。最初の BeginAccept 呼び出しと BeginRead 呼び出しは非同期です。

この例では、ソケット上でメッセージ ベースのプロトコルが使用されていることを前提としており、各転送の最初の 4 バイトが長さであることを除いて省略されていますが、ストリームで同期読み取りを使用して残りのデータを取得できます。それはすでにバッファリングされています。

コードは次のとおりです。

class ClientContext
{
    public TcpClient Client;
    public Stream Stream;
    public byte[] Buffer = new byte[4];
    public MemoryStream Message = new MemoryStream();
}

class Program
{
    static void OnMessageReceived(ClientContext context)
    {
        // process the message here
    }

    static void OnClientRead(IAsyncResult ar)
    {
        ClientContext context = ar.AsyncState as ClientContext;
        if (context == null)
            return;

        try
        {
            int read = context.Stream.EndRead(ar);
            context.Message.Write(context.Buffer, 0, read);

            int length = BitConverter.ToInt32(context.Buffer, 0);
            byte[] buffer = new byte[1024];
            while (length > 0)
            {
                read = context.Stream.Read(buffer, 0, Math.Min(buffer.Length, length));
                context.Message.Write(buffer, 0, read);
                length -= read;
            }

            OnMessageReceived(context);
        }
        catch (System.Exception)
        {
            context.Client.Close();
            context.Stream.Dispose();
            context.Message.Dispose();
            context = null;
        }
        finally
        {
            if (context != null)
                context.Stream.BeginRead(context.Buffer, 0, context.Buffer.Length, OnClientRead, context);
        }
    }

    static void OnClientAccepted(IAsyncResult ar)
    {
        TcpListener listener = ar.AsyncState as TcpListener;
        if (listener == null)
            return;

        try
        {
            ClientContext context = new ClientContext();
            context.Client = listener.EndAcceptTcpClient(ar);
            context.Stream = context.Client.GetStream();
            context.Stream.BeginRead(context.Buffer, 0, context.Buffer.Length, OnClientRead, context);
        }
        finally
        {
            listener.BeginAcceptTcpClient(OnClientAccepted, listener);
        }
    }

    static void Main(string[] args)
    {
        TcpListener listener = new TcpListener(new IPEndPoint(IPAddress.Any, 20000));
        listener.Start();

        listener.BeginAcceptTcpClient(OnClientAccepted, listener);

        Console.Write("Press enter to exit...");
        Console.ReadLine();
        listener.Stop();
    }
}

非同期呼び出しを処理する方法を示しますが、TcpListener が常に新しい接続を受け入れるようにするためにエラー処理を追加する必要があり、クライアントが予期せず切断した場合のエラー処理を追加する必要があります。また、すべてのデータが一度に到着するとは限らず、処理が必要な場合もいくつかあるようです。

于 2011-06-09T14:11:57.120 に答える
2

UDPテクニックも探していると思います。10,000 クライアントの場合は高速ですが、メッセージを受信したメッセージごとに確認応答を実装する必要があるという問題があります。UDP では、クライアントごとにソケットを開く必要はありませんが、x 秒後にハートビート/ping メカニズムを実装して、どのクライアントが接続されているかどうかを確認する必要があります。

于 2012-09-11T11:36:20.570 に答える
2

これはTcpClientクラスで行うことができますが、実を言うと、10,000 個のソケットをオープンできるかどうかはわかりません。それはかなり多いです。しかし、私は定期的にTcpClient数十の同時ソケットを処理するために使用しています。実際、非同期モデルは非常に使いやすいものです。

あなたの最大の問題はTcpClient仕事をすることではありません。10,000 の同時接続では、帯域幅とスケーラビリティが問題になると思います。1 台のマシンでそのすべてのトラフィックを処理できるかどうかさえわかりません。パケットの大きさと受信頻度にもよると思いますが、1 台のコンピューターにすべてを実装することを約束する前に、大まかな見積もりを行ったほうがよいでしょう。

于 2011-05-16T21:02:47.437 に答える
1

私が作成した TCP CSharpServer を使用できます。実装は非常に簡単で、クラスの 1 つで IClientRequest を実装するだけです。

using System;
using System.Collections.Generic;
using System.Linq;

namespace cSharpServer
{
    public interface IClientRequest
    {        
        /// <summary>
        /// this needs to be set, otherwise the server will not beable to handle the request.
        /// </summary>
        byte IdType { get; set; } // This is used for Execution.
        /// <summary>
        /// handle the process by the client.
        /// </summary>
        /// <param name="data"></param>
        /// <param name="client"></param>
        /// <returns></returns>
        byte[] Process(BinaryBuffer data, Client client);
    }
}

BinaryBuffer を使用すると、サーバーに送信されたデータを非常に簡単に読み取ることができます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace cSharpServer
{
    public class BinaryBuffer
    {
        private const string Str0001 = "You are at the End of File!";
        private const string Str0002 = "You are Not Reading from the Buffer!";
        private const string Str0003 = "You are Currenlty Writing to the Buffer!";
        private const string Str0004 = "You are Currenlty Reading from the Buffer!";
        private const string Str0005 = "You are Not Writing to the Buffer!";
        private const string Str0006 = "You are trying to Reverse Seek, Unable to add a Negative value!";
        private bool _inRead;
        private bool _inWrite;
        private List<byte> _newBytes;
        private int _pointer;
        public byte[] ByteBuffer;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public override string ToString()
        {
            return Helper.DefaultEncoding.GetString(ByteBuffer, 0, ByteBuffer.Length);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public BinaryBuffer(string data)
            : this(Helper.DefaultEncoding.GetBytes(data))
        {
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public BinaryBuffer()
        {
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public BinaryBuffer(byte[] data)
            : this(ref data)
        {
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public BinaryBuffer(ref byte[] data)
        {
            ByteBuffer = data;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void IncrementPointer(int add)
        {
            if (add < 0)
            {
                throw new Exception(Str0006);
            }
            _pointer += add;
            if (EofBuffer())
            {
                throw new Exception(Str0001);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int GetPointer()
        {
            return _pointer;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static string GetString(ref byte[] buffer)
        {
            return Helper.DefaultEncoding.GetString(buffer, 0, buffer.Length);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static string GetString(byte[] buffer)
        {
            return GetString(ref buffer);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void BeginWrite()
        {
            if (_inRead)
            {
                throw new Exception(Str0004);
            }
            _inWrite = true;

            _newBytes = new List<byte>();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Write(float value)
        {
            if (!_inWrite)
            {
                throw new Exception(Str0005);
            }
            _newBytes.AddRange(BitConverter.GetBytes(value));
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Write(byte value)
        {
            if (!_inWrite)
            {
                throw new Exception(Str0005);
            }
            _newBytes.Add(value);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Write(int value)
        {
            if (!_inWrite)
            {
                throw new Exception(Str0005);
            }

            _newBytes.AddRange(BitConverter.GetBytes(value));
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Write(long value)
        {
            if (!_inWrite)
            {
                throw new Exception(Str0005);
            }
            byte[] byteArray = new byte[8];

            unsafe
            {
                fixed (byte* bytePointer = byteArray)
                {
                    *((long*)bytePointer) = value;
                }
            }

            _newBytes.AddRange(byteArray);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int UncommitedLength()
        {
            return _newBytes == null ? 0 : _newBytes.Count;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteField(string value)
        {
            Write(value.Length);
            Write(value);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Write(string value)
        {
            if (!_inWrite)
            {
                throw new Exception(Str0005);
            }
            byte[] byteArray = Helper.DefaultEncoding.GetBytes(value);
            _newBytes.AddRange(byteArray);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Write(decimal value)
        {
            if (!_inWrite)
            {
                throw new Exception(Str0005);
            }
            int[] intArray = decimal.GetBits(value);

            Write(intArray[0]);
            Write(intArray[1]);
            Write(intArray[2]);
            Write(intArray[3]);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void SetInt(int value, int pos)
        {
            byte[] byteInt = BitConverter.GetBytes(value);
            for (int i = 0; i < byteInt.Length; i++)
            {
                _newBytes[pos + i] = byteInt[i];
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void SetLong(long value, int pos)
        {
            byte[] byteInt = BitConverter.GetBytes(value);
            for (int i = 0; i < byteInt.Length; i++)
            {
                _newBytes[pos + i] = byteInt[i];
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Write(byte[] value)
        {
            Write(ref value);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Write(ref byte[] value)
        {
            if (!_inWrite)
            {
                throw new Exception(Str0005);
            }
            _newBytes.AddRange(value);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void EndWrite()
        {
            if (ByteBuffer != null)
            {
                _newBytes.InsertRange(0, ByteBuffer);
            }
            ByteBuffer = _newBytes.ToArray();
            _newBytes = null;
            _inWrite = false;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void EndRead()
        {
            _inRead = false;
            _pointer = 0;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void BeginRead()
        {
            if (_inWrite)
            {
                throw new Exception(Str0003);
            }
            _inRead = true;
            _pointer = 0;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte ReadByte()
        {
            if (!_inRead)
            {
                throw new Exception(Str0002);
            }
            if (EofBuffer())
            {
                throw new Exception(Str0001);
            }
            return ByteBuffer[_pointer++];
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int ReadInt()
        {
            if (!_inRead)
            {
                throw new Exception(Str0002);
            }
            if (EofBuffer(4))
            {
                throw new Exception(Str0001);
            }
            int startPointer = _pointer;
            _pointer += 4;

            return BitConverter.ToInt32(ByteBuffer, startPointer);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public float[] ReadFloatArray()
        {
            float[] dataFloats = new float[ReadInt()];
            for (int i = 0; i < dataFloats.Length; i++)
            {
                dataFloats[i] = ReadFloat();
            }
            return dataFloats;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public float ReadFloat()
        {
            if (!_inRead)
            {
                throw new Exception(Str0002);
            }
            if (EofBuffer(sizeof(float)))
            {
                throw new Exception(Str0001);
            }
            int startPointer = _pointer;
            _pointer += sizeof(float);

            return BitConverter.ToSingle(ByteBuffer, startPointer);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public decimal ReadDecimal()
        {
            if (!_inRead)
            {
                throw new Exception(Str0002);
            }
            if (EofBuffer(16))
            {
                throw new Exception(Str0001);
            }
            return new decimal(new[] { ReadInt(),
                ReadInt(),
                ReadInt(),
                ReadInt()
            });
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public long ReadLong()
        {
            if (!_inRead)
            {
                throw new Exception(Str0002);
            }
            if (EofBuffer(8))
            {
                throw new Exception(Str0001);
            }
            int startPointer = _pointer;
            _pointer += 8;

            return BitConverter.ToInt64(ByteBuffer, startPointer);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public string ReadString(int size)
        {
            return Helper.DefaultEncoding.GetString(ReadByteArray(size), 0, size);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public byte[] ReadByteArray(int size)
        {
            if (!_inRead)
            {
                throw new Exception(Str0002);
            }
            if (EofBuffer(size))
            {
                throw new Exception(Str0001);
            }
            byte[] newBuffer = new byte[size];

            Array.Copy(ByteBuffer, _pointer, newBuffer, 0, size);

            _pointer += size;

            return newBuffer;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool EofBuffer(int over = 1)
        {
            return ByteBuffer == null || ((_pointer + over) > ByteBuffer.Length);
        }
    }
}

完全なプロジェクトは GitHub CSharpServerにあります

于 2015-09-09T04:53:58.293 に答える