如何构建一个健壮/可扩展的异步等待回显服务器?

时间:2013-11-03 07:04:20

标签: c# async-await tcpclient tcplistener

我正在建立一个简单的TCP客户端和服务器作为我的网络项目的基础。我计划将async await技术用于未来的证明和可扩展的服务器。

如果我输入错误的IP地址,客户端无法连接到我的服务器并抛出异常。我可以使用try / catch捕获异常,但这是推荐的方法吗?

你们对实施有什么看法?有什么评论让我改进吗?

我的服务器

    private void startServer_Click(object sender, RoutedEventArgs e)
    {
        if (anyIP.IsChecked == true)
        {
            listener = new TcpListener(IPAddress.Any, Int32.Parse(serverPort.Text));
            Logger.Info("Ip Address : " + IPAddress.Any + " Port : " + serverPort.Text);
        }
        else
        {
            listener = new TcpListener(IPAddress.Parse(serverIP.Text), Int32.Parse(serverPort.Text));
            Logger.Info("Ip Address : " + serverIP.Text + " Port : " + serverPort.Text);
        }
        try
        {
            listener.Start();
            Logger.Info("Listening");
            HandleConnectionAsync(listener, cts.Token);
        }
        //finally
        //{
            //cts.Cancel();
            //listener.Stop();
            //Logger.Info("Stop listening");
        //}

        //cts.Cancel();
    }

    async Task HandleConnectionAsync(TcpListener listener, CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            Logger.Info("Accepting client");
            //TcpClient client = await listener.AcceptTcpClientAsync();
            TcpClient client = await listener.AcceptTcpClientAsync();
            Logger.Info("Client accepted");
            EchoAsync(client, ct);
        }

    }

    async Task EchoAsync(TcpClient client, CancellationToken ct)
    {
        var buf = new byte[4096];
        var stream = client.GetStream();
        while (!ct.IsCancellationRequested)
        {
            var amountRead = await stream.ReadAsync(buf, 0, buf.Length, ct);
            Logger.Info("Receive " + stream.ToString());
            if (amountRead == 0) break; //end of stream.
            await stream.WriteAsync(buf, 0, amountRead, ct);
            Logger.Info("Echo to client");
        }
    }

    private void stopServer_Click(object sender, RoutedEventArgs e)
    {
        cts.Cancel();
        listener.Stop();
        Logger.Info("Stop listening");
    }

我的客户

    private void connect_Click(object sender, System.Windows.RoutedEventArgs e)
    {
        IPAddress ipAddress;
        int port;

        //TODO Check if ip address is valid
        ipAddress = IPAddress.Parse(serverIP.Text);
        //TODO port range is 0-65000
        port = int.Parse(serverPort.Text);

        StartClient(ipAddress, port);
    }

    private static async void StartClient(IPAddress serverIpAddress, int port)
    {
        var client = new TcpClient();
        //can i try/catch to catch await exception?
        try
        {
            await client.ConnectAsync(serverIpAddress, port);
        }
        catch (Exception e)
        {
            Logger.Info(e);                
        }
        Logger.Info("Connected to server");
        using (var networkStream = client.GetStream())
        using (var writer = new StreamWriter(networkStream))
        using (var reader = new StreamReader(networkStream))
        {
            writer.AutoFlush = true;
            for (int i = 0; i < 10; i++)
            {
                Logger.Info("Writing to server");
                await writer.WriteLineAsync(DateTime.Now.ToLongDateString());
                Logger.Info("Reading from server");
                var dataFromServer = await reader.ReadLineAsync();
                if (!string.IsNullOrEmpty(dataFromServer))
                {
                    Logger.Info(dataFromServer);
                }

            }
        }
        if (client != null)
        {
            client.Close();
            Logger.Info("Connection closed");
        }

    }

1 个答案:

答案 0 :(得分:3)

我有一个.NET TCP/IP FAQ,我建议你去了解一些基础知识。

在简要介绍一下你的代码后,这些要点对我来说很突出:

  1. 您的客户端和服务器都有时间只读(不写)。这意味着您受half-open scenario(我在常见问题解答中描述)的约束。一个强大的服务器应该定期写,即使它没有什么可说的。
  2. 您的客户和服务器都有时间只写(不读)。这意味着你是subject to a deadlock(正如我在常见问题解答中描述的那样)另一端表现不佳(例如,发送大量数据)。但是,你不能无限期地阅读,或者你会打开自己的DoS;所以你应该决定你的限制在哪里,并建立对你的应用程序有意义的读缓冲区大小(和写入超时)。
  3. 使用ReadLineAsync会让您受到轻微的DoS攻击,因为您无法指定该行的最大允许大小。
  4. Your code must be prepared for an exception at any time(正如我在常见问题解答中所述)。显然,ReadAsyncWriteAsync可能会抛出。不太明显的是任何套接字方法都可能抛出,包括AcceptTcpClientAsync
  5. 您的代码使用了异常处理类型的混合。永远不会等待async Task方法,因此异常只会默默地结束该方法。 StartClient方法更有问题,因为它是async void。您需要考虑应用程序对错误检测和重试策略的需求,并在每个级别应用适当的处理。
  6. 总之,我重申了我的评论:我强烈建议只是自托管SignalR。如果您别无选择,套接字应仅