Почему этот C# код TcpClient не всегда видит ответ? - PullRequest
0 голосов
/ 20 января 2020

Я использую следующий код для чтения ответа на запрос psuedo-HTTP. Иногда это работает, но не всегда, и я не понимаю.

Справочная информация. У меня есть устройство, которое принимает HTTP-запросы GET и отправляет фрагментированный HTTP-ответ. В одном случае ответ не является правильным ответом HTTP. Это пропускает нулевой кусок, который указывает на конец данных. Я исправил эту проблему в устройстве, но я пытаюсь выяснить, как читать несоответствующий HTTP-ответ. Я нашел код из Создание http-запроса с использованием TcpClient , который иногда работает, а иногда нет, и я не понимаю, почему.

Если я использую код без изменений, он работает нормально. Если я использую его, заменив «www.bing.com» на IP-адрес моего устройства, например, «192.1.168.89», в обоих местах появится строка и изменим командную строку GET на «GET /index.htm HTTP / 1.1», это работает отлично. Эта версия команды возвращает веб-страницу, созданную устройством и отправляющую несколько буферов TCP (около 1400 байт в моем устройстве) порций данных.

Однако, если я переключаюсь на другую команду, которую мое устройство понимает , "GET /request.htm?T HTTP / 1.1", но возвращает менее 500 байтов фрагментированных данных, тогда я никогда не увижу ответ. На самом деле это никогда не проходит мимо вызова «CopyToAsyn c (память)», и я не понимаю, почему. Устройство видит запрос, анализирует его и отправляет правильный HTTP-ответ. (Я знаю, что это правильный ответ, потому что у меня есть код, который использует HTTPClient для чтения ответа, и он видит ответ нормально. И я вижу, что данные ответа со стороны устройства в обоих случаях совпадают. Я вижу данные устройства, потому что я пишу прошивку устройства и могу изменить его на printf () данные, отправляемые в подпрограммы TCP.)

У любого есть объяснение, почему код ниже не всегда видит ответ ?

private static async Task<string> HttpRequestAsync() {
    string result = string.Empty;

    using (var tcp = new TcpClient("www.bing.com", 80))
    using (var stream = tcp.GetStream())
    {
        tcp.SendTimeout = 500;
        tcp.ReceiveTimeout = 1000;
        // Send request headers
        var builder = new StringBuilder();
        builder.AppendLine("GET /?scope=images&nr=1 HTTP/1.1");
        builder.AppendLine("Host: www.bing.com");
        //builder.AppendLine("Content-Length: " + data.Length);   // only for POST request
        builder.AppendLine("Connection: close");
        builder.AppendLine();
        var header = Encoding.ASCII.GetBytes(builder.ToString());
        await stream.WriteAsync(header, 0, header.Length);

        // Send payload data if you are POST request
        //await stream.WriteAsync(data, 0, data.Length);

        // receive data
        using (var memory = new MemoryStream())
        {
            await stream.CopyToAsync(memory);
            memory.Position = 0;
            var data = memory.ToArray();

            var index = BinaryMatch(data, Encoding.ASCII.GetBytes("\r\n\r\n")) + 4;
            var headers = Encoding.ASCII.GetString(data, 0, index);
            memory.Position = index;

            if (headers.IndexOf("Content-Encoding: gzip") > 0)
            {
                using (GZipStream decompressionStream = new GZipStream(memory, CompressionMode.Decompress))
                using (var decompressedMemory = new MemoryStream())
                {
                    decompressionStream.CopyTo(decompressedMemory);
                    decompressedMemory.Position = 0;
                    result = Encoding.UTF8.GetString(decompressedMemory.ToArray());
                }
            }
            else
            {
                result = Encoding.UTF8.GetString(data, index, data.Length - index);
                //result = Encoding.GetEncoding("gbk").GetString(data, index, data.Length - index);
            }
        }

        //Debug.WriteLine(result);
        return result;
    }
}

private static int BinaryMatch(byte[] input, byte[] pattern)
{
    int sLen = input.Length - pattern.Length + 1;
    for (int i = 0; i < sLen; ++i)
    {
        bool match = true;
        for (int j = 0; j < pattern.Length; ++j)
        {
            if (input[i + j] != pattern[j])
            {
                match = false;
                break;
            }
        }
        if (match)
        {
            return i;
        }
    }
    return -1;
}

=====================

Позвольте мне отредактировать приведенную выше функцию, чтобы показать, что это сейчас и, возможно, прояснить ситуацию.

static async Task<byte[]> getTcpClientHttpDataRequestAsync(string ipAddress, string request)
        {
            string result = string.Empty;
            List<byte> arrayList = new List<byte>();

            using (var tcp = new TcpClient("192.168.1.89", 80))
            using (var stream = tcp.GetStream())
            using (var memory = new MemoryStream())
            {
                tcp.SendTimeout = 500;
                tcp.ReceiveTimeout = 10000;
                tcp.NoDelay = true;
                // Send request headers
                var builder = new StringBuilder();
                builder.AppendLine("GET /request.htm?x01011920000000000001 HTTP/1.1");
                builder.AppendLine("Host: 192.168.1.89");
                builder.AppendLine("Connection: Close");
                builder.AppendLine();
                var header = Encoding.ASCII.GetBytes(builder.ToString());

                Console.WriteLine("======");
                Console.WriteLine(builder.ToString());
                Console.WriteLine("======");

                await stream.WriteAsync(header, 0, header.Length);

                do { } while (stream.DataAvailable == 0);

                Console.WriteLine("Data available");

                bool done = false;
                do
                {
                    int next = stream.ReadByte();

                    if (next < 0)
                    {
                        done = true;
                    }
                    else
                    {
                        arrayList.Add(Convert.ToByte(next));
                    }

                } while (stream.DataAvailable && !done);

                byte[] data = arrayList.ToArray();

                return data;
            }
        }

Команда GET - это то, на что реагирует мое устройство. Если команда начинается с 'x', как показано, то она отвечает правильным HTTP-ответом, а функция выше считывает данные. Если он начинается с 'd', то в конце отсутствует фрагмент длины 0, а функция выше никогда не видит никаких данных с устройства.

С Wireshark я вижу следующие ответы для 'x' и Команды 'd'.

Команда 'x' возвращает 2 кадра TCP со следующими данными:

0000   1c 6f 65 d3 f0 e2 4c 60 de 41 3f 67 08 00 45 00   .oe...L`.A?g..E.
0010   00 9c 00 47 00 00 64 06 d2 49 c0 a8 01 59 c0 a8   ...G..d..I...Y..
0020   01 22 00 50 05 5d fc f5 9e 72 ad 75 e3 2c 50 18   .".P.]...r.u.,P.
0030   00 01 a9 cd 00 00 48 54 54 50 2f 31 2e 31 20 32   ......HTTP/1.1 2
0040   30 30 20 4f 4b 0d 0a 43 6f 6e 6e 65 63 74 69 6f   00 OK..Connectio
0050   6e 3a 20 63 6c 6f 73 65 0d 0a 43 6f 6e 74 65 6e   n: close..Conten
0060   74 2d 54 79 70 65 3a 20 74 65 78 74 2f 68 74 6d   t-Type: text/htm
0070   6c 0d 0a 43 61 63 68 65 2d 43 6f 6e 74 72 6f 6c   l..Cache-Control
0080   3a 20 6e 6f 2d 63 61 63 68 65 0d 0a 54 72 61 6e   : no-cache..Tran
0090   73 66 65 72 2d 45 6e 63 6f 64 69 6e 67 3a 20 63   sfer-Encoding: c
00a0   68 75 6e 6b 65 64 0d 0a 0d 0a                     hunked....

0000   1c 6f 65 d3 f0 e2 4c 60 de 41 3f 67 08 00 45 00   .oe...L`.A?g..E.
0010   00 45 00 48 00 00 64 06 d2 9f c0 a8 01 59 c0 a8   .E.H..d......Y..
0020   01 22 00 50 05 5d fc f5 9e e6 ad 75 e3 2c 50 18   .".P.].....u.,P.
0030   00 01 fc 20 00 00 30 30 31 0d 0a 2b 0d 0a 30 30   ... ..001..+..00
0040   37 0d 0a 01 85 86 00 00 0d 0a 0d 0a 30 30 30 0d   7...........000.
0050   0a 0d 0a                                          ...

Для сравнения, команда 'd' возвращает данные в 2 кадрах TCP как:

0000   1c 6f 65 d3 f0 e2 4c 60 de 41 3f 67 08 00 45 00   .oe...L`.A?g..E.
0010   00 9c 00 4e 00 00 64 06 d2 42 c0 a8 01 59 c0 a8   ...N..d..B...Y..
0020   01 22 00 50 05 5e d3 c3 f9 f5 69 cc 6d a3 50 18   .".P.^....i.m.P.
0030   00 01 30 ae 00 00 48 54 54 50 2f 31 2e 31 20 32   ..0...HTTP/1.1 2
0040   30 30 20 4f 4b 0d 0a 43 6f 6e 6e 65 63 74 69 6f   00 OK..Connectio
0050   6e 3a 20 63 6c 6f 73 65 0d 0a 43 6f 6e 74 65 6e   n: close..Conten
0060   74 2d 54 79 70 65 3a 20 74 65 78 74 2f 68 74 6d   t-Type: text/htm
0070   6c 0d 0a 43 61 63 68 65 2d 43 6f 6e 74 72 6f 6c   l..Cache-Control
0080   3a 20 6e 6f 2d 63 61 63 68 65 0d 0a 54 72 61 6e   : no-cache..Tran
0090   73 66 65 72 2d 45 6e 63 6f 64 69 6e 67 3a 20 63   sfer-Encoding: c
00a0   68 75 6e 6b 65 64 0d 0a 0d 0a                     hunked....

0000   1c 6f 65 d3 f0 e2 4c 60 de 41 3f 67 08 00 45 00   .oe...L`.A?g..E.
0010   00 36 00 4f 00 00 64 06 d2 a7 c0 a8 01 59 c0 a8   .6.O..d......Y..
0020   01 22 00 50 05 5e d3 c3 fa 69 69 cc 6d a3 50 18   .".P.^...ii.m.P.
0030   00 01 64 c2 00 00 30 30 37 0d 0a 01 90 91 00 00   ..d...007.......
0040   0d 0a 0d 0a                                       ....

Единственное заметное отличие, которое я вижу, состоит в том, что во втором кадре команды 'd' отсутствует 1-байтовый фрагмент, который является частью нашего протокола (и не должен влиять на Функция TCP / HTTP) и последние 7 байтов данных, которые предоставляет команда 'x', то есть фрагмент длины 0, ожидаемый для HTTP.

Возвращаясь к коду в HttpRequestAsyn c (), если команда 'd' отправляется, тогда код никогда не видит, что stream.DataAvailable становится истинным, даже если данные были отправлены. Почему?

1 Ответ

0 голосов
/ 20 января 2020
await stream.CopyToAsync()

не будет завершено до тех пор, пока

stream.DataAvailable == false

В заголовках вы указали серверу, что после завершения соединения TCP закроете соединение, но не сделали этого. Сервер в конце концов закроет соединение, когда решит, что вы ушли. Сервер не обязан выполнять ваш запрос «Соединение: закрыть», и это должно быть указано в заголовках, которые возвращает сервер.

Перед тем, как позвонить stream.CopyToAsync(), вы должны проверить заголовки, чтобы определить, что Content-Length было предоставлено и передало длину буфера в stream.CopyToAsync(), а затем вызывало TcpClient.Close()

...