C#中的高性能TCP服务器

Tom*_*Tom 44 c# tcp scalable

我是一名经验丰富的C#开发人员,但到目前为止我还没有开发过TCP服务器应用程序.现在我必须开发一个高度可扩展的高性能服务器,它可以处理至少5-10万个并发连接:通过GPRS从GPS设备获取字节数据.

常见的通信过程应如下所示:

  • GPS设备启动与我的服务器的连接
  • 如果我想获取数据,我的服务器会回答
  • 设备发送GPS数据
  • 我的服务器向设备发送有关获取它的报告(例如校验和)
  • 从GPS获取新数据,报告并且这种情况一次又一次地发生
  • 以后GPS DEVICE关闭连接

所以,在我的服务器中我需要

  • 跟踪连接/活动客户端
  • 从服务器端关闭任何客户端
  • 当设备关闭连接时捕获事件
  • 获取字节数据
  • 将数据发送给客户

我开始通过互联网阅读这个主题,但对我来说这似乎是一场噩梦.有很多方法,但我找不出哪个是最好的.

异步套接字方法对我来说似乎是最好的,但是以这种异步方式编写代码非常糟糕且不易调试.

所以我的问题是:您认为在C#中实现高性能TCP服务器的最佳方式是哪种?你知道任何好的开源组件吗?(我尝试了几个,但我找不到一个好的.)

Rem*_*anu 40

它必须是异步的,没有办法解决这个问题.高性能和可扩展性不会与每个插槽的单线程混合使用.您可以查看StackExchange自己正在做什么,请参阅异步Redis等待BookSleeve,它利用了下一个C#版本中的CTP功能(因此处于边缘并且可能会发生变化,但很酷).为了获得更多的优势,解决方案围绕着利用SocketAsyncEventArgs类进行了演变,通过消除与"经典"C#异步处理相关联的异步处理程序的频繁分配,使得事情更进一步:

SocketAsyncEventArgs类是System.Net.Sockets.Socket类的一组增强的一部分,它们提供了可供专用高性能套接字应用程序使用的备用异步模式.此类专为需要高性能的网络服务器应用程序而设计.应用程序可以仅使用增强型异步模式,也可以仅在目标热区域中使用(例如,在接收大量数据时).

长话短说:学习异步或死于尝试......

顺便说一下,如果你问为什么是异步,那么请阅读这篇文章中链接的三篇文章:高性能Windows程序.最终的答案是:底层操作系统设计需要它.

  • @RemusRusanu我没有问题,我觉得我对正在发生的事情有很好的理解.我提出了一个有效的论点,你只是说我错了?这个想法是人们来到这里,并阅读答案.如果你解释为什么我错了,而不是仅仅说我错了,那对观众会更有帮助.所以,如果我错了,请纠正我.这将有助于其他人看到你的答案. (3认同)
  • @RemusRusanu如果我的理解是完全错误的,那么为什么当我运行异步任务时,它们会创建线程?如何在不打开线程的情况下异步运行任务?我认为除了"你的理解是完全错误的"之外的评论在这种情况下会受到警告,因为这不是一个反驳论点. (2认同)
  • 您必须了解异步 io 在最低级别(内核/驱动程序)的处理方式,才能理解为什么它不需要为您发布的每个 IO 提供一个线程。在这个小空间里,我能让你走上正确轨道的最简单方法是指出内核深处只有 1 个“线程”控制一切(泵送所有线程),如果你从那时开始考虑它当然,您可以看到异步 IO 如何因操作系统本身而优化。也许阅读“线程上下文切换”。 (2认同)

小智 11

正如Remus上面所说,你必须使用异步来保持高性能.那是.NET中的Begin .../End ...方法.

在套接字的引擎盖下,这些方法使用IO完成端口,这似乎是在Windows操作系统上处理许多套接字的最高性能方式.

正如Jim所说,TcpClient类可以在这里提供帮助并且非常易于使用.下面是使用TcpListener监听传入连接和TcpClient处理它们的示例,初始BeginAccept和BeginRead调用是异步的.

这个例子假设在套接字上使用了基于消息的协议,除了每个传输的前4个字节是长度之外,它被省略,但是这允许你在流上使用同步Read来获取其余的数据已经缓冲了.

这是代码:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

它演示了如何处理异步调用,但它需要添加错误处理,以确保TcpListener始终接受新连接,并在客户端意外断开连接时进行更多错误处理.此外,确实存在一些情况,其中并非所有数据都一次性到达,也需要处理.