Библиотека HawkNL, nlClose не закрывает сокет - PullRequest
1 голос
/ 29 июня 2011

Может ли кто-нибудь помочь мне со следующим.

У меня надежное соединение tcp клиента и сервера.

Клиент подключается к серверу, сервер получает пакеты.Затем клиент закрывает сокет (nlclose) и на сервере nlRead по-прежнему возвращает 0 (что означает, что соединение все еще активно). Он не сообщает мне, что клиент закрыл сокет, почему?сторона заставляет сервер прекратить прослушивание сокета. Почему это так?

Кто-нибудь из вас имеет опыт работы с hawkNL?

Спасибо!

Код запускается на Windows XP Sp3 box.

РЕДАКТ.:

Код клиента:

procedure Client;
const
  TCP_PORT = 9800;
  READ_SIZE_CHUNK = 1048576;//1mb
Type
  TChunk = record
    data: pointer;
    datasize: NLint;
  end;
var
  LRslt: NLboolean;
  LClientSocket: NLsocket;
  LChunkData: TChunk;
  LNumRead: Integer;
  LReadError: NLenum;
  LAddress: NLaddress;
  LWrittenBytes: Integer;
  LBuffer: array [0..10] of byte;

  procedure PopUpMessage(Rslt: NLboolean);
  var
    LError: NLenum;
    LStrRslt: String;
  begin
    if Rslt
      then exit;
    LError := nlGetError();
    if LError = NL_SYSTEM_ERROR
      then LStrRslt := nlGetSystemErrorStr(nlGetSystemError())
      else LStrRslt := nlGetErrorStr(error);
    showmessage (LStrRslt);
    abort;
  end;

begin
  LReadError := 0;
  LRslt := nlInit();PopUpMessage(LRslt);
  LRslt := nlSelectNetwork(NL_IP);PopUpMessage(LRslt);

  LClientSocket := nlOpen(0, NL_TCP);
  if (LClientSocket = NL_INVALID)
    then PopUpMessage(false);

  nlGetLocalAddr(LClientSocket, LAddress);

  //Ustawienie adresu drukarki
  LRslt := nlStringToAddr(PChar('127.0.0.1:'+Inttostr(TCP_PORT)), LAddress); PopUpMessage(Lrslt);
  //Połączenie z drukarką
  LRslt := nlConnect(LClientSocket, LAddress); PopUpMessage(Lrslt);

  LWrittenBytes := nlWrite(LClientSocket, LBuffer, 5);

  if (LWrittenBytes = NL_INVALID)
    then PopUpMessage(false);

  //nlClose(LClientSocket);
end;

Код сервера:

procedure Serwer;
const
  TCP_PORT = 9800;
  READ_SIZE_CHUNK = 1048576;//1mb
Type
  TChunk = record
    data: pointer;
    datasize: NLint;
  end;
var
  LRslt: NLboolean;
  LServerSock, LNewSock: NLsocket;
  LChunkData: TChunk;
  LNumRead: Integer;
  LReadError: NLenum;

  procedure PopUpMessage(Rslt: NLboolean);
  var
    LError: NLenum;
    LStrRslt: String;
  begin
    if Rslt
      then exit;
    LError := nlGetError();
    if LError = NL_SYSTEM_ERROR
      then LStrRslt := nlGetSystemErrorStr(nlGetSystemError())
      else LStrRslt := nlGetErrorStr(error);
    showmessage (LStrRslt);
    abort;
  end;

begin
  LReadError := 0;
  LRslt := nlInit();PopUpMessage(LRslt);
  LRslt := nlSelectNetwork(NL_IP);PopUpMessage(LRslt);

  LServerSock := nlOpen(TCP_PORT, NL_RELIABLE);
  if (LServerSock = NL_INVALID)
    then PopUpMessage(false);

  if not nlListen(LServerSock)
    then PopUpMessage(false);

  repeat
    LNewSock := nlAcceptConnection(LServerSock);
    if LNewSock <> NL_INVALID then
    begin
      GetMem (LChunkData.data, READ_SIZE_CHUNK);
      repeat
        LNumRead := nlRead(LNewSock, LChunkData.data^, READ_SIZE_CHUNK);
        //Here I am receiving 0 bytes reads from LNumRead....
        if LNumRead > 0 then
        begin
          beep;
          //I am receiving stream, do nothing with that
        end else
        begin
          if LNumRead = NL_INVALID then
          begin
            LReadError := nlGetError();
            if (LReadError = NL_INVALID_SOCKET) or
               (LReadError = NL_MESSAGE_END)
              then PopUpMessage(false);
          end;
        end;
      until LReadError <> 0;//(LReadError = NL_NO_PENDING)
      beep;//Here it should exit if all would work ok.
    end else
    begin
      if nlGetError() = NL_SYSTEM_ERROR
        then PopUpMessage(false);
    end;
  until 1=2;
  beep;
end;

1 Ответ

1 голос
/ 29 июня 2011

Вы уверены в своем API? (Документация , которую я нашел , удивительно тиха в отношении возвращаемого значения 0 или упорядоченного закрытия сокета.)

Базовые recv() или read() вызовы стека TCP возвращают 0, когда узел закрыл соединение. Из справочной страницы recv(2) в моей системе Linux:

RETURN VALUE
   These calls return the number of bytes received, or -1 if an
   error occurred.  The return value will be 0 when the peer has
   performed an orderly shutdown.

Обратите внимание, что может быть несколько байтов или вплоть до гигабайта данных или более данных, оставленных в сокете для чтения даже после того, как одноранговый узел закрыл соединение. Ваше серверное приложение может не получить уведомление FIN о закрытом сокете в течение продолжительного времени после завершения работы клиента, в зависимости от задержки и пропускной способности ваших каналов, стратегии ввода-вывода вашего приложения и доступной памяти Стек TCP / IP для буферизации данных сокета.

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

Кроме того, nlShutdown выглядит очень отличающимся от обычного значения TCP для shutdown(), которое просто сообщает стеку TCP, что приложение завершило чтение, запись или чтение и запись на одном конкретная розетка. Использование его в вашем клиенте может быть немного драконовским.

Редактировать

Я немного просмотрел источник HawkNL, и теперь я еще больше запутался в поведении, которое вы видите.

NL_EXP NLint NL_APIENTRY nlRead(NLsocket socket, NLvoid *buffer, NLint nbytes)
{
    if(driver)
    {
        if(nlIsValidSocket(socket) == NL_TRUE)
        {
            if(buffer == NULL)
            {
                nlSetError(NL_NULL_POINTER);
            }
            else
            {
                NLint received;

                if(nlLockSocket(socket, NL_READ) == NL_FALSE)
                {
                    return NL_INVALID;
                }
                received = driver->Read(socket, buffer, nbytes); /* AAA */

                if(received > 0)
                {
                    nlUpdateSocketInStats(socket, received, 1);
                    nlUpdateInStats(received, 1);
                }
                nlUnlockSocket(socket, NL_READ);
                return received;
            }
        }
        else
        {
            nlSetError(NL_INVALID_SOCKET);
        }
        return NL_INVALID;
    }
    nlSetError(NL_NO_NETWORK);
    return NL_INVALID;
}

Точка, которую я отметил /* AAA */, вызывает функцию Read для каждого сетевого стека и просто возвращает любое значение, возвращаемое функцией Read:

NLint sock_Read(NLsocket socket, NLvoid *buffer, NLint nbytes)
{
    nl_socket_t *sock = nlSockets[socket];
    NLint       count;

    if(nbytes < 0)
    {
        return 0;
    }
    if(sock->type == NL_RELIABLE || sock->type == NL_RELIABLE_PACKETS) /* TCP */
    {

        /* I've removed all the code for pending non-blocking connections */

        /* check for reliable packets */
        if(sock->type == NL_RELIABLE_PACKETS)
        {
            return sock_ReadPacket(socket, buffer, nbytes, NL_FALSE);
        }
        count = recv((SOCKET)sock->realsocket, (char *)buffer, nbytes, 0); /* BBB */
        if(count == 0)
        {
            /* end of message */
            nlSetError(NL_MESSAGE_END);
            return NL_INVALID;
        }
    }
    /* I removed UDP handling */

Часть, отмеченная /* BBB */, является вызовом системной функции recv(). Когда система recv() возвращает 0 (чтобы указать, что узел корректно закрыл сокет), она соответствующим образом устанавливает состояние ошибки и возвращает ошибку.

Исходя из вашего описания проблемы и этого источника, я действительно удивлен. Возврат 0 возможен только через первое if условие функции sock_Read(): запрос чтения менее чем 0 байтов или через соединение NL_RELIABLE_PACKETS, если вы не все же получил весь пакет более высокого уровня данных. Ни один из них не выглядит слишком правдоподобным, поэтому мне очень любопытно увидеть вашу программу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...