Асинхронное, неблокирующее поведение сокетов - WSAEWOULDBLOCK - PullRequest
4 голосов
/ 12 марта 2019

Я унаследовал два приложения, одно Test Harness (клиент), работающее на ПК с Windows 7, и одно серверное приложение, работающее на ПК с Windows 10.Я пытаюсь установить связь между ними с помощью сокетов TCP / IP.Клиент отправляет запросы (для данных в форме XML) на сервер, а затем сервер отправляет запрошенные данные (также в формате XML) обратно клиенту.

Настройка показана ниже:

       Client                                    Server
--------------------                      --------------------  
|                  |    Sends Requests    |                  |
|   Client Socket  |  ----------------->  |   Server Socket  |
|                  |  <-----------------  |                  |
|                  |      Sends Data      |                  |
--------------------                      --------------------

Этот процесс всегда работает при начальном соединении (т. Е. Недавно запущенные клиентские и серверные приложения).Клиент имеет возможность отключиться от сервера, что вызывает очистку сокетов.При переподключении я почти всегда (это не всегда происходит, но происходит большую часть времени) получаю следующую ошибку:

"Receive() - The socket is marked as nonblocking and the receive operation would block"

Эта ошибка отображается на клиенте, и рассматриваемый сокет является асинхронным,неблокирующий сокет.

Строка, которая вызывает это SOCKET_ERROR:

numBytesReceived = theSocket->Receive(theReceiveBuffer, 10000));

where:
- numBytesReceived is an integer (int)
- theSocket is a pointer to a class called CClientSocket which is a specialisation of CASyncSocket, which is part of the MFC C++ Library.  This defines the socket object which is embedded within the client.  It is an asynchonous, non-blocking socket.
- Receive() is a virtual function within the CASyncSocket object
- theReceiveBuffer is a char array (10000 elements)

При выполнении описанной выше строки, SOCKET_ERROR возвращается из функции и вызывает theSocket->GetLastError()возвращает WSAEWOULDBLOCK.

SocketTools подчеркивает, что

Когда неблокирующий (асинхронный) сокет пытается выполнить операцию, которая не может быть выполнена немедленно, ошибка10035 будет возвращено.Эта ошибка не является фатальной и должна рассматриваться приложением как рекомендация.Этот код ошибки соответствует ошибке Windows Sockets WSAEWOULDBLOCK.

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

При записи данных в неблокирующий сокет эта ошибка будет возвращена, если локальные буферы сокета заполнены во время ожиданиядля удаленного хоста, чтобы прочитать некоторые данные.Когда буферное пространство становится доступным, срабатывает событие OnWrite, которое указывает на возможность записи большего количества данных.Свойство IsWritable можно использовать для определения возможности записи данных в сокет.

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

Я последовательно получаю эту ошибку ине удается получить что-либо на сокете.

При использовании Wireshark происходит следующая связь с представленными здесь источниками, флагами назначения и битовыми флагами TCP:

Событие: подключить тестовый жгут к серверу через TCP / IP

Client --> Server: SYN
Server --> Client: SYN, ACK
Client --> Server: ACK

This appears to be correct and represents the Three-Way Handshake of connecting.

SocketSniff confirms that a Socket is closed on the client side.  It was not possible to get SocketSniff to work with the Windows 10 Server application.

Событие: отправка запроса данных из тестового жгута

Client --> Server: PSH, ACK
Server --> Client: PSH, ACK
Client --> Server: ACK

Both request data and received data is confirmed to be exchanged successfully

Событие: отсоединение тестового жгута от сервера

Client --> Server: FIN, ACK
Server --> Client: ACK
Server --> Client: FIN, ACK
Client --> Server: ACK

This appears to be correct and represents the Four-Way handshake of connection closure.

SocketSniff confirms that a Socket is closed on the client side.  It was not possible to get SocketSniff to work with the Windows 10 Server application.

Событие: повторное подключение тестового жгута к серверучерез TCP / IP

Client --> Server: SYN
Server --> Client: SYN, ACK
Client --> Server: ACK

This appears to be correct and represents the Three-Way Handshake of connecting.

SocketSniff confirms that a new Socket is opened on the client side.  It was not possible to get SocketSniff to work with the Windows 10 Server application.

Событие: отправить запрос данных из тестового жгута

Client --> Server: PSH, ACK
Server --> Client: ACK

We see no data being pushed (PSH) back to the client, yet we do see an acknowledgement.  

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

Подробнее:

Сервер инициализирует поток прослушивания и связывается с 0.0.0.0:49720.Функции WSAStartup (), bind () и listen () возвращают «0», что указывает на успех.Этот поток сохраняется в течение всего срока службы серверного приложения.

Сервер инициализирует два потока: поток чтения и записи.Поток чтения отвечает за чтение данных запроса из своего сокета и инициализируется следующим образом с помощью класса Connection:

HANDLE theConnectionReadThread 
           = CreateThread(NULL,                                    // Security Attributes
                          0,                                       // Default Stacksize
                          Connection::connectionReadThreadHandler, // Callback
                          (LPVOID)this,                            // Parameter to pass to thread
                          CREATE_SUSPENDED,                        // Don't start yet
                          NULL);                                   // Don't Save Thread ID

Поток записи инициализируется аналогичным образом.

В каждомВ этом случае функция CreateThread () возвращает подходящий HANDLE, например,

theConnectionReadThread  = 00000570
theConnectionWriteThread = 00000574  

Потоки фактически запускаются в следующей функции:

void Connection::startThreads()
{
    ResumeThread(theConnectionReadThread);
    ResumeThread(theConnectionWriteThread);
}                                   

И эта функция вызывается из другого класса.с именем ConnectionManager, который управляет всеми возможными соединениями с сервером.В данном случае для простоты меня интересует только одно соединение.

Добавление вывода текста в серверное приложение показывает, что я могу успешно connect/disconnect клиент и сервер несколько раз, пока не обнаружится неисправное поведение.Например, в функциях connectionReadThreadHandler() и connectionWriteThreadHandler() я выводю текст в файл журнала, как только они выполняются.

При соблюдении правильного поведения в файл журнала выводятся следующие строки:

Connection::ResumeThread(theConnectionReadThread) returned 1
Connection::ResumeThread(theConnectionWriteThread) returned 1
ConnectionReadThreadHandler() Beginning
ConnectionWriteThreadHandler() Beginning

При обнаружении неисправного поведения в файл журнала выводятся следующие строки:

Connection::ResumeThread(theConnectionReadThread) returned 1
Connection::ResumeThread(theConnectionWriteThread) returned 1

Функции обратного вызова не вызываются.

Именно в этот момент на клиенте отображается ошибка, указывающая, что:

"Receive() - The socket is marked as nonblocking and the receive operation would block"

На стороне клиента у меня есть класс с именем CClientDoc, который содержиткод сокета на стороне клиента.Сначала он инициализирует theSocket, который является объектом сокета, встроенным в клиент:

private:
    CClientSocket* theSocket = new CClientSocket;

Когда инициализируется соединение между клиентом и сервером, этот класс вызывает функцию с именем CreateSocket(), частью которой являетсяниже, вместе со вспомогательными функциями, которые он вызывает:

void CClientDoc::CreateSocket()
{
    AfxSocketInit();
    int lastError;
    theSocket->Init(this);

    if (theSocket->Create()) // Calls CAyncSocket::Create() (part of afxsock.h)
    {
        theErrorMessage = "Socket Creation Successful"; // this is a CString
        theSocket->SetSocketStatus(WAITING);             
    }
    else
    {
        // We don't fall in here
    }
}

void CClientDoc::Init(CClientDoc* pDoc)
{
    pClient = pDoc; // pClient is a pointer to a CClientDoc
}

void CClientDoc::SetSocketStatus(SOCKET_STATUS sock_stat)
{
    theSocketStatus = sock_stat; // theSocketStatus is a private member of CClientSocket of type SOCKET_STATUS
}

Сразу после CreateSocket(), SetupSocket() вызывается, что также предусмотрено здесь:

void CClientDoc::SetupSocket()
{
    theSocket->AsyncSelect(); // Function within afxsock.h
}

При отключенииклиент с сервера,

void CClientDoc::OnClienDisconnect()
{
    theSocket->ShutDown(2); // Inline function within afxsock.inl
    delete theSocket;
    theSocket = new CClientSocket;
    CreateSocket();
    SetupSocket();        
}

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

Ошибка записывается вКлиент в рамках функции DoReceive().Эта функция вызывает сокет, чтобы попытаться прочитать сообщение.

CClientDoc::DoReceive()
{
    int lastError;
    switch (numBytesReceived = theSocket->Receive(theReceiveBuffer, 10000))
    {
    case 0:
        // We don't fall in here
        break;
    case SOCKET_ERROR: // We come in here when the faulty behaviour occurs
        if (lastError = theSocket->GetLastError() == WSAEWOULDBLOCK)
        {
            theErrorMessage = "Receive() - The socket is marked as nonblocking and the receive operation would block";
        }
        else
        {
            // We don't fall in here
        }
        break;
    default:
        // When connection works, we come in here
        break;
    }
}

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

Спасибо

1 Ответ

0 голосов
/ 12 марта 2019

Ошибка WSAEWOULDBLOCK НЕ означает, что розетка помечена как блокирующая. Это означает, что сокет помечен как неблокирующий, и в данный момент НЕТ ДАННЫХ ДЛЯ ЧТЕНИЯ.

WSAEWOULDBLOCK означает, что сокет заблокировал вызывающий поток в ожидании данных, если сокет был помечен как блокирующий.

Чтобы узнать, когда неблокирующий сокет имеет данные, ожидающие чтения, используйте функцию Winsock select() или метод CClientSocket::AsyncSelect() для запроса FD_READ уведомлений, или другой эквивалент. Не пытайтесь читать, пока есть что почитать.

В своем анализе вы видите, что клиент отправляет данные на сервер, но сервер не отправляет данные клиенту. Итак, у вас явно есть логическая ошибка в вашем коде, вам нужно ее найти и исправить. Либо клиент неправильно завершает свой запрос, либо сервер неправильно получает / обрабатывает / отвечает на него. Но поскольку вы не показали свой настоящий код, мы не можем сказать вам, что на самом деле с ним не так.

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