C # Socket TCP Send, сообщения застряли - PullRequest
2 голосов
/ 31 марта 2011

Это немного странно, и я прошу прощения, если я не очень хорошо это объясню.

Я использую следующий простой код для отправки сообщений, которые я вытолкнул из очереди, используя простойTCP-сокет, сообщения отправляются через локальный хост на другой порт на той же машине -

try
{
    Socket.Select(null, writeList, null, 120000000/*120 seconds*/);
}
catch (SocketException e)
{
    log.Error("Select returned an error waiting to send... " + e.Message + " Errorcode: " + e.ErrorCode);
    connected = false;
    socket.Close();
}

bool readyToWrite = false;
for (int i = 0; i < writeList.Count; i++)
{
    readyToWrite = true;
}

if (readyToWrite)
{
    try
    {
        //log.Debug("Sending message type: " + message.header.msgType);
        socket.Send(message, message.header.dataLength, SocketFlags.None);                        
        //log.Debug("Message sent");
    }
    catch (SocketException e)
    {
        log.Error(e.Message + " Error code: " + e.ErrorCode);
        connected = false;
        socket.Close();                        
    }

}
else
{
    log.Error("Not able to write - stopping sender thread and closing socket");
    connected = false;
    socket.Close();
}

Это нормально работает, и фактически мое приложение отправляет несколько сообщений на другой конец без проблем изначально.

Однако затем я добавляю в очередь 10 или около того сообщений в быстрой последовательности, которые удаляются и отправляются нормально, по-видимому - операторы журнала показывают, что Send () возвращается нормально, и когда я смотрю на трассировку сети, кажется, что другойКонец признал их.

Но это не так.Другой конец находится в цикле, вызывающем select () с тайм-аутом в одну секунду, и он продолжает возвращаться без данных для чтения, пока примерно через 30 секунд (то же самое каждый раз) все сообщения приходят на другой конец одновременно.

Код C ++ с другой стороны соединения -

while (m_bRunning && bOK && !bReadyToRead)
{
    m_bIsAlive = true;

    switch(pSocket->Select(1, true))
    {
    case 1:     // Ready to read
        //TRACE("Data ready to be read from RAM\n");
        bReadyToRead = true;
        break;

    case 0:     // Select timed out
        if (GetTickCount() > dwTimeout)
        {
            bOK = false;
        }
        // else No action needed
        break;

        default:    // Error detected
            TRACE("Select returned error...\n");
            bOK = false;
            break;
    }
}

// Try and read a message header
iBytesExpected = sizeof(RAM_HEADER);    

while ((m_bRunning && bOK) && (iBytesSoFar < iBytesExpected))
{
    m_bIsAlive = true;

    iBytesRead = pSocket->Read(pWritePos, iBytesExpected-iBytesSoFar);

Оболочка выбора C ++ выглядит следующим образом -

int CRawSocket::Select(ULONG ulTimeoutSeconds, bool bCheckRead)
{
    int iResult = -1;           // Error by default
    int iSelectReturn = 0;
    fd_set readSet;
    fd_set writeSet;
    struct timeval timeout;

    timeout.tv_sec = ulTimeoutSeconds;
    timeout.tv_usec = 0;

    FD_ZERO(&readSet);
    FD_ZERO(&writeSet);

    if (bCheckRead)
    {
        FD_SET(m_hSocket, &readSet);
        iSelectReturn = select(1, &readSet, NULL, NULL, &timeout);
    }
    else
    {
        FD_SET(m_hSocket, &writeSet);
        iSelectReturn = select(1, NULL, &writeSet, NULL, &timeout);
    }

    if(iSelectReturn != SOCKET_ERROR)
    {
        if (FD_ISSET(m_hSocket, &readSet))
        {
            iResult = 1;        // Ready to READ
        }
        else if (FD_ISSET(m_hSocket, &writeSet))
        {
            iResult = 2;        // Ready to WRITE
        }
        else
        {
            iResult = 0;        // Select TIMED OUT
        }
    }
    else 
    {
        const int e = WSAGetLastError();
        ERRORLOG("Select socket error %lu\n", e);
        iResult = -1;           // Some error occurred
    }

    return iResult;
}

И метод чтения -

int CReadWriteSocket::Read(void *pData, int nLen) 
{
    char* pcData = (char* )pData;
    int n = nLen;
    // if data size is bigger then network buffer
    // handle it nice
    do
    {
        int r1 = ::recv (m_hSocket, pcData, n, 0);
        if (r1 == SOCKET_ERROR)
        {
            int e = WSAGetLastError();
            if (e == WSAEWOULDBLOCK)
            {
                return nLen - n;
            }
            else
            {
                TRACE("Socket Read error %d\n", e);
                return -1;      // error other than would block detected
            }
        }
        else if (r1 == 0)       // Connection has closed
        {
            TRACE("Socket appears to have closed (zero bytes read)\n");
            return -1;          // Show this as an "error"
        }
        else if (r1 < 0)
        {
            ASSERT(0);
            return nLen - n;
        }

        pcData += r1;
        n -= r1;
    } while (n > 0);

    ASSERT(n == 0);
    return nLen;
}

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

Было предложено попробовать NoDelayопция сокета, но это не имело никакого эффекта - и на самом деле это никогда не приведет к задержкам такой длины, насколько я знаю.

Любые предложения будут с благодарностью!Спасибо.

Ответы [ 5 ]

4 голосов
/ 31 марта 2011

TCP - это потоковый протокол. Вы не можете предполагать, что «пакеты», которые вы посылаете (), будут доставлены и получены в такт. На передающей стороне алгоритм Nagle пытается объединить данные, которые были записаны в отдельных вызовах Send (), для оптимизации доставки данных. На приемном конце вы прочитаете (), что хранится в буфере TCP. Если будет комбинация передаваемых пакетов, если есть задержка. Это дополнительно усложняется маршрутизаторами между передатчиком и приемником, которым разрешено фрагментировать IP-пакет, превращая большой пакет в несколько маленьких, чтобы приспособиться к максимальному размеру пакета канала передачи (MTU).

Другими словами, нет надежного способа гарантировать, что пакеты будут доставлены так, как они были отправлены. Простой обходной путь для этого - сначала передать пакет размером . На принимающей стороне вы сначала читаете этот размер, а затем знаете, как отсчитать полученные байты для восстановления пакета.

1 голос
/ 31 марта 2011

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

Кажется, я помню, что Socket.Select() вернется, указывая, что в нем нет данныхчитать, если поток имеет ошибку.Возможно, вы захотите попробовать передать копию списка потоков в параметр списка ошибок и посмотреть, нет ли у вас ошибки по ошибке.

Ваш код, похоже, отправляет в один и тот же сокет независимо от того, что находится в writelist.Я бы изменил его для работы на writelist, даже если у вас есть только один сокет.В противном случае вы будете пытаться отправить в сокет, даже если он не обязательно указал, что он готов к данным (например, если Socket.Select() возвращается по какой-то другой причине, например, из-за моей догадки выше).Это может привести к блокировке операции записи, и может стать причиной задержки, свидетелем которой вы являетесь, если вы работаете с более чем одним сокетом.

Наконец, вы можете выйти из вашего readyToWrite цикл, как только флаг установлен.Еще лучше, вы можете перекодировать это как:

bool readyToWrite = writelist.Any();

Но я бы все же предложил вам заменить это на foreach цикл на writelist:

foreach (Socket sock in writelist)
{
    // do stuff
}
0 голосов
/ 04 апреля 2011

Моя ошибка, кажется, что это была проблема с блокировкой чтения в конце.

0 голосов
/ 31 марта 2011

Как выглядит ваш клиентский код?

Обычно я бы сказал, что, поскольку вы работаете, вы отправляете сообщения только локально, то поведение, которое вы видите, почти наверняка вызвано алгоритмом Nagle, но установка свойства NoDelay отключает это.

0 голосов
/ 31 марта 2011

Это очень странно. Пожалуйста, рассмотрите возможность использования WireShark , чтобы отслеживать сообщения и видеть, куда они направляются. Если я не пропускаю что-то очевидное, мне трудно понять, почему сообщения считают, что они были получены, но на самом деле не получили. Может ли клиент ждать?

попробуйте int.MaxValue вместо 120000000

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