Клиент и сервер не могут обмениваться данными, потому что оба " застревают”
ВСТУПЛЕНИЕ:
Я изучил примеры MSDN для блокировка TCP-сервера[^] и блокировка TCP-клиента[^].
Я хотел попробовать что-то простое, имея в виду изменение этих примеров для создания простого приложения чата.
Для начала я попытался реализовать следующее:
- отправить сообщение с сервера
- получите и отобразите это сообщение на клиенте
- отправить ответ от клиента
- получение и отображение ответа от клиента
СООТВЕТСТВУЮЩАЯ ИНФОРМАЦИЯ
Заранее прошу прощения за длинный код, но я твердо верю это важно для меня, чтобы представить SSCCE[^] как для клиента, так и для сервера, чтобы у сообщества был шанс решить эту проблему.
Я старался сделать код как можно более минимальным, но не хотел опускать элементарную проверку ошибок.
Вы можете копировать/вставлять оба варианта в один
.cpp
файл, и они должны компилироваться и запускаться без проблем:Серверный код:
#undef UNICODE #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <winsock2.h> #include <ws2tcpip.h> #include <string> #pragma comment (lib, "Ws2_32.lib") #define DEFAULT_BUFLEN 512 #define DEFAULT_PORT "27015" int __cdecl main(void) { WSADATA wsaData; SOCKET ListenSocket = INVALID_SOCKET; SOCKET ClientSocket = INVALID_SOCKET; struct addrinfo *result = NULL; struct addrinfo hints; int iResult; char recvbuf[DEFAULT_BUFLEN] = ""; int recvbuflen = DEFAULT_BUFLEN; // Initialize Winsock iResult = WSAStartup(MAKEWORD(2,2), &wsaData); if (iResult != 0) { printf("WSAStartup failed with error: %d\n", iResult); return 1; } ZeroMemory(&hints, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_PASSIVE; // Resolve the server address and port iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result); if ( iResult != 0 ) { printf("getaddrinfo failed with error: %d\n", iResult); WSACleanup(); return 1; } // Create a SOCKET for connecting to server ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); if (ListenSocket == INVALID_SOCKET) { printf("socket failed with error: %ld\n", WSAGetLastError()); freeaddrinfo(result); WSACleanup(); return 1; } // Setup the TCP listening socket iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen); if (iResult == SOCKET_ERROR) { printf("bind failed with error: %d\n", WSAGetLastError()); freeaddrinfo(result); closesocket(ListenSocket); WSACleanup(); return 1; } freeaddrinfo(result); iResult = listen(ListenSocket, SOMAXCONN); if (iResult == SOCKET_ERROR) { printf("listen failed with error: %d\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } // Accept a client socket ClientSocket = accept(ListenSocket, NULL, NULL); if (ClientSocket == INVALID_SOCKET) { printf("accept failed with error: %d\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } // No longer need server socket, // because I want to accept only 1 client closesocket(ListenSocket); // ===================== let us try to send a message... std::string message = "Test message from server !!!"; int total = message.size(); const int messageLength = message.size(); while (iResult = send( ClientSocket, // send only the missing part of the string, if send failed to deliver entire packet: // we move the start of the string forward by messageLength - total // while we send remaining number of bytes, which is held in total message.substr(messageLength - total, total).c_str(), total, 0), iResult > 0) { total -= iResult; } if (iResult == SOCKET_ERROR) { printf("send failed with error: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return 1; } /* // adding this, seems to solve the problem ??? iResult = shutdown(ClientSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("shutdown failed with error: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return 1; } */ // receive response from client... while (iResult = recv(ClientSocket, recvbuf, recvbuflen, 0), iResult > 0) { printf("%s", recvbuf); memset(recvbuf, '\0', sizeof(recvbuf)); } if(iResult < 0) { printf("recv failed with error: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return 1; } // cleanup closesocket(ClientSocket); WSACleanup(); getchar(); // so I can stop the console from immediately closing... return 0; }
Клиентский код:
#define WIN32_LEAN_AND_MEAN #include <windows.h> #include <winsock2.h> #include <ws2tcpip.h> #include <string> #pragma comment (lib, "Ws2_32.lib") #define DEFAULT_BUFLEN 512 #define DEFAULT_PORT "27015" int __cdecl main(int argc, char **argv) { WSADATA wsaData; SOCKET ConnectSocket = INVALID_SOCKET; struct addrinfo *result = NULL, *ptr = NULL, hints; char recvbuf[DEFAULT_BUFLEN] = ""; int iResult; int recvbuflen = DEFAULT_BUFLEN; // Initialize Winsock iResult = WSAStartup(MAKEWORD(2,2), &wsaData); if (iResult != 0) { printf("WSAStartup failed with error: %d\n", iResult); return 1; } ZeroMemory( &hints, sizeof(hints) ); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; // Resolve the server address and port iResult = getaddrinfo("127.0.0.1", DEFAULT_PORT, &hints, &result); if ( iResult != 0 ) { printf("getaddrinfo failed with error: %d\n", iResult); WSACleanup(); return 1; } // Attempt to connect to an address until one succeeds for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) { // Create a SOCKET for connecting to server ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); if (ConnectSocket == INVALID_SOCKET) { printf("socket failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } // Connect to server. iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen); if (iResult == SOCKET_ERROR) { closesocket(ConnectSocket); ConnectSocket = INVALID_SOCKET; continue; } break; } freeaddrinfo(result); if (ConnectSocket == INVALID_SOCKET) { printf("Unable to connect to server!\n"); WSACleanup(); return 1; } // receive message from server... while (iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0), iResult > 0) { printf("%s", recvbuf); memset(recvbuf, '\0', sizeof(recvbuf)); } if(iResult < 0) { printf("recv failed with error: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } // ===================== let us try to send a message... std::string message = "Client response..."; int total = message.size(); const int messageLength = message.size(); while (iResult = send( ConnectSocket, // send only the missing part of the string, if send failed to deliver entire packet: // we move the start of the string forward by messageLength - total // while we send remaining number of bytes, which is held in total message.substr(messageLength - total, total).c_str(), total, 0), iResult > 0) { total -= iResult; } if (iResult == SOCKET_ERROR) { printf("send failed with error: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } // shutdown the connection since no more data will be sent iResult = shutdown(ConnectSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("shutdown failed with error: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } // cleanup closesocket(ConnectSocket); WSACleanup(); getchar(); // so I can stop the console from immediately closing... return 0; }
ПРОБЛЕМА:
Я реализовал решение, но не получил ожидаемого результата.
Сервер отправляет сообщение, клиент успешно получает и отображает его, но затем клиент бесконечно застревает, вместо того чтобы отправить свой ответ серверу, который также бесконечно блокирует сервер.
Что я уже пробовал:
Первая попытка:
Используя отладчик, я поместил точку останова после блока приема клиента только для того, чтобы определить, что клиент никогда не попадет туда после получения первого сообщения.
Я считаю, что цикл while должен снова вызвать recv, который должен вернуть 0, тем самым заставляя цикл завершиться.
Отладчик даже не продолжает показывать содержимое буфера приема клиента после того, как я нажму продолжить, вместо этого он демонстрирует поведение, которое я не могу описать в данный момент, так как я не являюсь носителем английского языка.
Вторая попытка:
Я также попытался поместить цикл приема с сервера в поток, используя CreateThread, но это тоже не помогло.
Я также попытался поместить цикл приема от клиента в поток, но это тоже не удалось.
Я пытался поместить как клиентские, так и серверные циклы приема в поток, но это тоже не удалось.
Третья попытка:
Наконец, я добавил вызов shutdown( ClientSocket, SD_SEND)в серверный код, вы найдете его в нижней части кода, он закомментирован.
Это, кажется, решает проблему, но я не уверен, что это правильное решение, так как я только начинаю с Winsock.