C#等待TCP响应的良好实践

时间:2016-10-07 13:05:31

标签: c# .net networking network-programming

在使用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;

获得完整响应的好方法是什么? 感谢您的帮助或指挥。

更多信息

网络团队总是充满了某些东西,大多数时候它充满了cisco IOS登录页面。最大的问题是确定路由器何时完成填充响应。 我大部分时间都得到了回应:

  

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

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

  • mrmathijs95 -

3 个答案:

答案 0 :(得分: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);
    }
}    

答案 1 :(得分:0)

不要使用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();
}

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

最后,因为你说流上总是有数据,你在那里进行读取,你可能必须对上次读取的字节数做一些逻辑,以便在流中自行抵消数据不清楚。您将知道您获得的回复是否始终相同。

答案 2 :(得分:0)

这是我的工作代码。 它使用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