【问题标题】:C# good practice waiting for TCP responseC# 等待 TCP 响应的好习惯
【发布时间】:2016-10-07 13:05:30
【问题描述】:

在使用 TCP 制作用于远程控制 cisco 路由器的 c# 应用程序时,我遇到了等待路由器响应的问题。

对于应用程序,我必须使用 TCP 连接连接到 Cisco 路由器。建立连接后,网络流会将我的命令推送到 Cisco 路由器。为了让路由器处理命令,我使用Thread.Sleep。这不是最好的解决方案。

这是了解我的程序在做什么的完整代码。

        string IPAddress = "192.168.1.1";
        string message = "show running-config";  // command to run
        int bytes;
        string response = "";

        byte[] responseInBytes = new byte[4096];
        var client = new TcpClient();
        client.ConnectAsync(IPAddress, 23).Wait(TimeSpan.FromSeconds(2));
        if (client.Connected == true)
        {
            client.ReceiveTimeout = 3;
            client.SendTimeout = 3;
            byte[] messageInBytes = Encoding.ASCII.GetBytes(message);
            NetworkStream stream = client.GetStream();
            Console.WriteLine();
            stream.Write(messageInBytes, 0, messageInBytes.Count());    //send data to router
            Thread.Sleep(50);                                           // temporary way to let the router fill his tcp response
            bytes = stream.Read(responseInBytes, 0, responseInBytes.Length);
            response = Encoding.ASCII.GetString(responseInBytes, 0, bytes);
            return response;                                            //whole command output
        }
        return null;

什么是获得完整响应的良好且可靠的方法。 感谢您的任何帮助或命令。

更多信息

networksteam 总是充满了一些东西,大部分时间都充满了 cisco IOS 登录页面。最大的问题是确定路由器何时完成填充响应。 我大部分时间得到的回应:

"??\u0001??\u0003??\u0018??\u001f\r\n\r\n用户访问验证\r\n\r\n用户名:"

每次返回的数据都会不同,因为它是 cisco 命令的结果。这可以从短字符串到非常长的字符串。

  • mrmathijs95 -

【问题讨论】:

标签: c# .net networking network-programming


【解决方案1】:

NetworkStreamStream.Read 读取时,不能100% 确定您将读取所有预期数据。 Stream.Read 可以在只有少数数据包到达且不等待其他数据包时返回。

为确保您获得所有数据,请使用BinaryReader 进行读取。
BinaryReader.Read 方法将阻塞当前线程,直到所有预期数据到达

private string GetResponse(string message)
{
    const int RESPONSE_LENGTH = 4096;
    byte[] messageInBytes = Encoding.ASCII.GetBytes(message);

    bool leaveStreamOpen = true;
    using(var writer = new BinaryWriter(client.GetStream()))
    {
        writer.Write(messageInBytes);
    }

    using(var reader = New BinaryReader(client.GetStream()))
    {
        byte[] bytes = reader.Read(RESPONSE_LENGTH );
        return Encoding.ASCII.GetString(bytes);
    }
}    

【讨论】:

  • 我将您的解决方案与 thread.sleep 结合使用,让路由器处理请求。我还使用了一个 while 循环,如果响应发生变化(使用 byte[] 比较),则每 X 毫秒检查一次,完整的代码将在答案中 -
【解决方案2】:

不要使用 Thread.Sleep。我会async/await 整件事,因为您并不总是知道基于您最近的编辑的数据是什么。我会这样做(未经测试):

public class Program
{
    // call Foo write with "show running-config"
}

public class Foo
{
    private TcpClient _client;
    private ConcurrentQueue<string> _responses;
    private Task _continualRead;
    private CancellationTokenSource _readCancellation;

    public Foo()
    {
        this._responses = new ConcurrentQueue<string>();
        this._readCancellation = new CancellationTokenSource();
        this._continualRead = Task.Factory.StartNew(this.ContinualReadOperation, this._readCancellation.Token, this._readCancellation.Token);

    }

    public async Task<bool> Connect(string ip)
    {
        this._client = new TcpClient
        {
            ReceiveTimeout = 3, // probably shouldn't be 3ms.
            SendTimeout = 3     // ^
        };

        int timeout = 1000;

        return await this.AwaitTimeoutTask(this._client.ConnectAsync(ip, 23), timeout);


    }

    public async void StreamWrite(string message)
    {
        var messageBytes = Encoding.ASCII.GetBytes(message);
        var stream = this._client.GetStream();
        if (await this.AwaitTimeoutTask(stream.WriteAsync(messageBytes, 0, messageBytes.Length), 1000))
        {
            //write success
        }
        else
        {
            //write failure.
        }

    }

    public async void ContinualReadOperation(object state)
    {
        var token = (CancellationToken)state;
        var stream = this._client.GetStream();
        var byteBuffer = new byte[4096];
        while (!token.IsCancellationRequested)
        {
            int bytesLastRead = 0;
            if (stream.DataAvailable)
            {
                bytesLastRead = await stream.ReadAsync(byteBuffer, 0, byteBuffer.Length, token);
            }

            if (bytesLastRead > 0)
            {
                var response = Encoding.ASCII.GetString(byteBuffer, 0, bytesLastRead);
                this._responses.Enqueue(response);
            }
        }
    }

    private async Task<bool> AwaitTimeoutTask(Task task, int timeout)
    {
        return await Task.WhenAny(task, Task.Delay(timeout)) == task;
    }


    public void GetResponses()
    {
        //Do a TryDequeue etc... on this._responses.
    }
}

我没有公开读取取消,但是你可以添加这个方法来取消读取操作:

public void Cancel()
{
    this._readCancellation.Cancel();
}

然后处理掉你的客户和所有有趣的东西。

最后,因为您说流上总是有可用的数据,在您进行读取的地方,如果数据不清除,您可能需要对上次读取的字节数执行一些逻辑,以在流中抵消您自己.您会知道收到的回复是否始终相同。

【讨论】:

    【解决方案3】:

    这是我的工作代码。 它使用Fabio的解决方案, 结合 while 循环,每 X 毫秒检查一次响应是否已更改。


                client.ReceiveTimeout = 3;
                client.SendTimeout = 3;
                byte[] messageInBytes = Encoding.ASCII.GetBytes(message);
                NetworkStream stream = client.GetStream();
                Console.WriteLine();
                using (var writer = new BinaryWriter(client.GetStream(),Encoding.ASCII,true))
                {
                    writer.Write(messageInBytes);
                }
                using (var reader = new BinaryReader(client.GetStream(),Encoding.ASCII, true))
                {
                    while (itIsTheEnd == false)
                    {
                        bytes = reader.Read(responseInBytes, 0, responseInBytes.Count());
                        if (lastBytesArray == responseInBytes)
                        {
                            itIsTheEnd = true;
                        }
                        lastBytesArray = responseInBytes;
                        Thread.Sleep(15);
                    }
                }
                response = Encoding.ASCII.GetString(responseInBytes);
    

    感谢所有提出解决方案的人。 感谢Fabio 提供的解决方案。

    【讨论】:

    • 你为什么要睡觉?
    • 第一次睡眠(写睡眠)是可选的,可以去掉。第二次睡眠是必要的。某些 cisco 命令可能会在屏幕上处理一整秒的文本。确保输出已完全由路由器给出。我使用了一个while循环来检查输出是否有变化。如果我不使用 thread.sleep,它将在不到一毫秒的时间内检查,并且不会检测到输出已更改。这主要发生在输出较大的命令中,例如;显示运行配置。 - 我删除了答案中的 thread.sleep
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-06
    • 2012-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-17
    相关资源
    最近更新 更多