TcpListener + TcpClient - дождаться, пока клиент прочитает данные перед закрытием - PullRequest
1 голос
/ 18 марта 2020

Я создаю простой HTTP-сервер для файлов PDF с помощью TcpClient. Это работает хорошо, однако TcpClient закрывается до завершения загрузки PDF-файла браузером. Как я могу заставить TcpClient ждать, пока удаленный клиент не получит все написанное до закрытия?

//pdf is byte[]

TcpListener server = new TcpListener(address, port);

server.Start();

TcpClient client = server.AcceptTcpClient();  //Wait for connection

var ns = client.GetStream();

string headers;

using (var writer = new StringWriter())
{
    writer.WriteLine("HTTP/1.1 200 OK");
    //writer.WriteLine("Accept:  text/html");
    writer.WriteLine("Content-type:  application/pdf");
    writer.WriteLine("Content-length: " + pdf.Length);
    writer.WriteLine();
    headers = writer.ToString();
}

var bytes = Encoding.UTF8.GetBytes(headers);
ns.Write(bytes, 0, bytes.Length);

ns.Write(pdf, 0, pdf.Length);

Thread.Sleep(TimeSpan.FromSeconds(10)); //Adding this line fixes the problem....

client.Close();

server.Stop();

Могу ли я заменить этот уродливый хак Thread.Sleep?

РЕДАКТИРОВАТЬ: Код ниже работает, основываясь на ответах:

TcpListener Server = null;

public void StartServer()
{
    Server = new TcpListener(IPAddress.Any, Port);

    Server.Start();

    Server.BeginAcceptTcpClient(AcceptClientCallback, null);
}

void AcceptClientCallback(IAsyncResult result)
{
    var client = Server.EndAcceptTcpClient(result);

    var ns = client.GetStream();

    string headers;

    byte[] pdf = //get pdf

    using (var writer = new StringWriter())
    {
        writer.WriteLine("HTTP/1.1 200 OK");
        //writer.WriteLine("Accept:  text/html");
        writer.WriteLine("Content-type:  application/pdf");
        writer.WriteLine("Content-length: " + pdf.Length);
        writer.WriteLine();
        headers = writer.ToString();
    }

    var bytes = Encoding.UTF8.GetBytes(headers);
    ns.Write(bytes, 0, bytes.Length);

    ns.Write(pdf, 0, pdf.Length);

    client.Client.Shutdown(SocketShutdown.Send);

    byte[] buffer = new byte[1024];
    int byteCount;
    while ((byteCount = ns.Read(buffer, 0, buffer.Length)) > 0)
    {

    }

    client.Close();

    Server.Stop();
}

1 Ответ

1 голос
/ 18 марта 2020

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

Код мог бы быть в порядке лучше, но, как минимум, вы могли бы заставить его работать, добавив что-то вроде этого непосредственно перед вашим client.Close(); утверждением:

// Indicate the end of the bytes being sent
ns.Socket.Shutdown(SocketShutdown.Send);

// arbitrarily-sized buffer...most likely nothing will  ever be written to it
byte[] buffer = new byte[4096];
int byteCount;

while ((byteCount = ns.Read(buffer, 0, buffer.Length)) > 0)
{
    // ignore any data read here
}

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

Любая конечная точка может инициировать постепенное закрытие с причиной отключения «send». Другая конечная точка может подтвердить ее, как только она закончила отправку того, что она хочет отправить, используя причину отключения «обоих», когда обе конечные точки могут закрыть свои сокеты (или потоки или прослушиватели или любой другой более высокий уровень) абстракция, они могут использовать оболочку сокета).

Конечно, в правильно реализованном протоколе вы заранее знаете, будут ли какие-либо данные когда-либо отправляться удаленной конечной точкой. Если вы знаете, что никогда не будет, вы могли бы обойтись без буфера нулевой длины, и если вы знаете, что некоторые данные могут быть отправлены обратно от клиента, то вы на самом деле делаете что-то с этими данными ( в отличие от пустого тела l oop, приведенного выше).

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

Несмотря на это, код, который вы опубликовали, далек от того, чтобы быть настолько хорошим. Вы не просто реализуете базовое TCP-соединение c, но, по-видимому, пытаетесь переопределить протокол HTTP. В этом нет никакого смысла, поскольку в NET уже встроены функции HTTP-сервера (см., Например, System. Net .HttpListener ). Если вы намереваетесь заново изобрести HTTP-сервер, вам потребуется гораздо больше кода, чем вы опубликовали. Отсутствие одной только обработки ошибок является серьезным недостатком и вызовет всевозможные головные боли.

Если вы собираетесь писать низкоуровневый сетевой код, вам следует провести лот дополнительных исследований и эксперименты. Одним из очень хороших ресурсов является FAQ программиста Winsock . Конечно, основное внимание уделяется программистам, ориентирующимся на Winsock API. Но там также имеется масса информации общего назначения, и в любом случае все различные API-интерфейсы сокетов очень похожи, поскольку все они основаны на одних и тех же низкоуровневых концепциях.

Вы также можете хочу просмотреть различные существующие вопросы и ответы по переполнению стека. Вот несколько примеров, связанных с вашей конкретной проблемой c: Как правильно использовать TPL с TcpClient? Отправить большой файл по TCP-соединению

Будьте осторожны, хотя. Там почти столько же плохих советов, сколько и хороших. Там нет недостатка в людях, которые ведут себя так, как будто они являются экспертами в сетевом программировании, когда они этого не делают, поэтому принимайте все, что вы прочитали, с небольшим количеством соли (включая мой совет выше!).

...