Долгая задержка [~ 1 с] между браузером, пытающимся подключиться, и Socket.Accept () - PullRequest
3 голосов
/ 13 августа 2010

Обзор проблемы: я уже некоторое время занимаюсь написанием пользовательских приложений на http-сервере. Я обнаружил, что при подключении любого веб-браузера к моему серверному приложению задержка 0,5-1 секунды (в соответствии с Google Chrome) перед обработкой запроса [который будет занимать миллисекунды]

В конце концов я попытался создать фиктивную программу, чтобы точно определить проблему:

using System.Text;
using System.Net;
using System.Net.Sockets;

namespace SlowHTTPServer 
{ 
    class FailServer 
    {
        static void Main() 
        {
            //Create socket object, bind it, listen to 80
            Socket listenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            listenerSocket.Bind(new IPEndPoint(IPAddress.Any, 80));
            listenerSocket.Listen(64);

            while (true)
            {
                Socket clientConn = listenerSocket.Accept();            //Accept
                System.DateTime startTime = System.DateTime.Now;

                byte[] buffer = new byte[1024];                         //Request header buffer
                clientConn.Receive(buffer);                             //Recieve request header

                string reqHeader = Encoding.ASCII.GetString(buffer);    //Get the string version of it
                                                                        //We completely ignore most of the request header lol

                //Normally i'd send a response header but...
                if (reqHeader.IndexOf("script1") != -1)                 //script1.js - document.title='hai dere'
                    clientConn.Send(Encoding.ASCII.GetBytes("document.title='hai dere';"));
                else if (reqHeader.IndexOf("script2") != -1)            //script2.js - Get a pretty background color onload
                    clientConn.Send(Encoding.ASCII.GetBytes("window.onload=function(){document.body.style.backgroundColor='#FF99FF';};"));
                else if (reqHeader.IndexOf("iframe") != -1)             //Noob iframe that just has text.
                    clientConn.Send(Encoding.ASCII.GetBytes("blargh zargh nargh dargh pikachu tangerine zombie destroy annihilate"));
                else                                                    //hai dere is the body.innerHTML, load script1.js and script2.js
                    clientConn.Send(Encoding.ASCII.GetBytes("<html><head><script src='script1.js'></script><script src='script2.js'></script></head><body>mainPage<iframe src='iframe.html'>u no haz iframe</iframe></body></html>"));

                clientConn.Close();                                     //Close the connection to client.  We've done such a good job!

                System.Console.WriteLine((System.DateTime.Now - startTime).TotalMilliseconds);
            }
        }
    }
}

... И теперь я полностью сбит с толку, потому что вышеприведенная программа будет обслуживать страницу + скрипт + iframe в веб-браузере в течение 2 секунд [подключение от localhost к localhost, брандмауэр Windows + антивирус отключены]. При использовании такого сервера, как Apache, запросы [с использованием предварительно созданных файлов в файловой системе, разумеется] будут обрабатываться менее чем за 100 миллисекунд.

Из того, что я вижу, код, который я разместил, является чрезвычайно урезанным http-сервером. Производительность не меняется, если я отправляю заголовки ответа или нет.

Проблема становится чрезвычайно раздражающей, когда у меня есть проекты с 30+ файлами сценариев JavaScript, и для полной загрузки страницы требуется более 20 секунд [что недопустимо, когда все сценарии <10 КБ] </p>

Если вы посмотрите на скрипт, вы заметите, что я отслеживаю, когда сокет принимает и когда он закрывается, и обычно время обработки составляет всего 1-10 миллисекунд, что означает, что задержка между сервером http и веб браузер, [до того как соединение будет принято?]

Я в тупике и нуждаюсь в помощи. Спасибо.

Другие примечания: - «Настоящий» сервер, который я написал, принимает клиентские соединения и передает их другому потоку для выполнения работы, поэтому задача отправки байтов и закрытия соединения не является причиной такой большой задержки. - Связывается с портом 80, чтобы проверить код, запустите программу и перейдите к http://127.0.0.1/ или http://localhost/

Изображения:
Инструменты разработчика Chrome, показывающие время загрузки


Дополнительная программа:
Я попытался смоделировать проблему, создав очень простую программу сервер / клиент ... Проблема НЕ была воспроизведена, и время соединения составляло 1 миллисекунду. Я еще тупее

using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace SlowSocketAccept
{
    class Program
    {
        static DateTime connectionBeginTime;
        static bool listening = false;
        static void Main(string[] args)
        {

            new Thread(ClientThread).Start();
            Socket listenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            listenerSocket.Bind(new IPEndPoint(IPAddress.Any, 80));
            listenerSocket.Listen(80);

            listening = true;
            Socket newConn = listenerSocket.Accept();
            byte[] reqHeader = new byte[1024];
            newConn.Receive(reqHeader);
            newConn.Send(Encoding.ASCII.GetBytes("Response Header\r\n\r\nContent"));
            newConn.Close();

            Console.WriteLine("Elapsed time: {0} ms", (DateTime.Now - connectionBeginTime).TotalMilliseconds);

        }
        static void ClientThread()
        {
            while (listening == false) ; //Busy wait, whatever it's an example
            System.Threading.Thread.Sleep(10); //Wait for accept to be called =/

            Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            connectionBeginTime = DateTime.Now;
            s.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 80));
            s.Send(Encoding.ASCII.GetBytes("Request Header"));
            byte[] response = new byte[1024];
            s.Receive(response);
        }
    }
}

[a и b - темы]
а) Запустить тему 'b'
б) занят ожиданием, пока А не установит флаг, говоря, что все в порядке, чтобы подключиться
а) создать сокет, начать слушать 80, установить флаг, говорящий B, что все в порядке, чтобы подключиться
б) Создать сокет, сохранить текущее время и подключиться к 127.0.0.1:80 [localhost: 80]
а) поток принимает соединение и начинает слушать
б) поток отправляет фиктивную строку [наш заголовок запроса]
а) принимает фиктивную строку, затем отправляет фиктивную строку обратно [заголовок ответа + содержимое]
а) закрыть соединение

Истекшее время составляет около 1 миллисекунды = /

1 Ответ

2 голосов
/ 13 августа 2010

Если вы не включите заголовок Content-Length - и не укажете, используете ли вы HTTP 1.0 или что соединение должно закрываться - браузер будет продолжать читать контент, пока не заметит, что соединение закрыто (что может занять в то время).

Убедитесь, что ваше приложение отправляет заголовок Content-Length и "Connection: close".

Кроме того, вы должны выключить клиентский сокет, прежде чем закрыть его. Это говорит .net (и, возможно, другой стороне, я забыл), что вы закончили отправку данных, и что они должны быть сброшены в сеть и сбросить соединение. Close - вещь со стороны кода; Shutdown - это то, что фактически закрывает соединение с точки зрения клиента.

...