创建一个TCP套接字服务器,每秒能处理数千个请求

时间:2017-06-14 06:04:33

标签: c# sockets load-testing tcpclient tcpserver

第一次尝试时,我创建了一个基本的TCP服务器:

public class Tcp
{
    private  TcpListener listener { get; set; }
    private  bool accept { get; set; } = false;

    public  void StartServer(string ip, int port)
    {
        IPAddress address = IPAddress.Parse(ip);
        listener = new TcpListener(address, port);

        listener.Start();
        accept = true;
        StartListener();
        Console.WriteLine($"Server started. Listening to TCP clients at {ip}:{port}");
    }
    public async void StartListener() //non blocking listener
    {

        listener.Start();
        while (true)
        {
            try
            {
                TcpClient client = await listener.AcceptTcpClientAsync().ConfigureAwait(false);
                HandleClient(client);
            }
            finally { }
        }
    }
    private void HandleClient(TcpClient client)
    {
        try
        {

            NetworkStream networkStream = client.GetStream();
            byte[] bytesFrom = new byte[20];
            networkStream.Read(bytesFrom, 0, 20);
            string dataFromClient = System.Text.Encoding.ASCII.GetString(bytesFrom);
            string serverResponse = "Received!";
            Byte[] sendBytes = Encoding.ASCII.GetBytes(serverResponse);
            networkStream.Write(sendBytes, 0, sendBytes.Length);
            networkStream.Flush();
        }
        catch(Exception ex)
        {
        }
    }
}

我写了一个客户端测试代码,每秒发送和记录请求数

public class Program
{
    private volatile static Dictionary<int, int> connections = new Dictionary<int, int>();
    private volatile static int fail = 0;
    private static string message = "";
    public static void Main(string[] args)
    {
        ServicePointManager.DefaultConnectionLimit = 1000000;
        ServicePointManager.Expect100Continue = false;
        for (int i = 0; i < 512; i++)
        {
            message += "T";
        }

        int taskCount = 10;
        int requestsCount = 1000;
        var taskList = new List<Task>();
        int seconds = 0;
        Console.WriteLine($"start : {DateTime.Now.ToString("mm:ss")} ");

        for (int i = 0; i < taskCount; i++)
        {

            taskList.Add(Task.Factory.StartNew(() =>
            {
                for (int j = 0; j < requestsCount; j++)
                {
                    Send();
                }
            }));
        }
        Console.WriteLine($"threads stablished : {DateTime.Now.ToString("mm: ss")}");
        while (taskList.Any(t => !t.IsCompleted)) { Thread.Sleep(5000); }
        Console.WriteLine($"Compelete : {DateTime.Now.ToString("mm: ss")}");
        int total = 0;
        foreach (KeyValuePair<int, int> keyValuePair in connections)
        {
            Console.WriteLine($"{keyValuePair.Key}:{keyValuePair.Value}");
            total += keyValuePair.Value;
            seconds++;
        }
        Console.WriteLine($"succeded:{total}\tfail:{fail}\tseconds:{seconds}");
        Console.WriteLine($"End : {DateTime.Now.ToString("mm: ss")}");
        Console.ReadKey();
    }

    private static void Send()
    {
        try
        {
            TcpClient tcpclnt = new TcpClient();
            tcpclnt.ConnectAsync("192.168.1.21", 5678).Wait();
            String str = message;
            Stream stm = tcpclnt.GetStream();

            ASCIIEncoding asen = new ASCIIEncoding();
            byte[] ba = asen.GetBytes(str);

            stm.Write(ba, 0, ba.Length);

            byte[] bb = new byte[100];
            int k = stm.Read(bb, 0, 100);
            tcpclnt.Close();
            lock (connections)
            {
                int key = int.Parse(DateTime.Now.ToString("hhmmss"));
                if (!connections.ContainsKey(key))
                {
                    connections.Add(key, 0);
                }
                connections[key] = connections[key] + 1;
            }
        }
        catch (Exception e)
        {
            lock (connections)
            {
                fail += 1;
            }
        }
    }
}

当我在本地计算机上测试它时,我得到每秒最多4000个请求,当我将其上传到本地Lan时,它减少到每秒200个请求。

问题是: 如何提高服务器性能? 负载测试套接字服务器的正确方法是什么?

1 个答案:

答案 0 :(得分:2)

您可能有一个&#34;非阻塞侦听器&#34;,但是当任何特定客户端连接时,它会专注于该客户端,直到该客户端发送了一条消息并且已将响应发送回该客户端。这不会很好地扩展。

我通常不是async void的粉丝,但它与您当前的代码保持一致:

public async void StartListener() //non blocking listener
{

    listener.Start();
    while (true)
    {
        TcpClient client = await listener.AcceptTcpClientAsync().ConfigureAwait(false);
        HandleClient(client);
    }
}
private async void HandleClient(TcpClient client)
{
    NetworkStream networkStream = client.GetStream();
    byte[] bytesFrom = new byte[20];
    int totalRead = 0;
    while(totalRead<20)
    {
        totalRead += await networkStream.ReadAsync(bytesFrom, totalRead, 20-totalRead).ConfigureAwait(false);
    }
    string dataFromClient = System.Text.Encoding.ASCII.GetString(bytesFrom);
    string serverResponse = "Received!";
    Byte[] sendBytes = Encoding.ASCII.GetBytes(serverResponse);
    await networkStream.WriteAsync(sendBytes, 0, sendBytes.Length).ConfigureAwait(false);
    networkStream.Flush(); /* Not sure necessary */
}

我还修复了我在评论中提到的关于忽略来自Read的返回值的错误,并删除了隐藏错误,让我无法在野外发现错误&#34;错误处理。

如果您不能保证您的客户端总是会向此代码发送一个20字节的消息,那么您需要执行其他操作,以便服务器知道要读取多少数据。这通常通过为消息添加其长度前缀或使用某种形式的标记值来指示结束来完成。请注意,即使使用长度前缀,您也无法保证一次读取整个长度,因此您还需要使用读取循环(如上所述)来发现首先是长度。

如果将所有内容切换为async并不能为您提供所需的比例,那么您需要放弃使用NetworkStream并开始在Socket级别工作,特别是设计用于SocketAsyncEventArgs的异步方法:

  

SocketAsyncEventArgs类是System.Net.Sockets.Socket类的一组增强功能的一部分,它提供了可供专用高性能套接字应用程序使用的备用异步模式...应用程序可以使用增强型异步专门或仅在目标热区域中模式化(例如,在接收大量数据时)。

     

这些增强功能的主要特点是避免在高容量异步套接字I / O期间重复分配和同步对象......

     

在新的System.Net.Sockets.Socket类增强中,异步套接字操作由应用程序分配和维护的可重用SocketAsyncEventArgs对象描述...