【发布时间】:2014-09-28 04:46:43
【问题描述】:
我正在尝试编写一个简单的winsock 客户端,将数据发送到
使用 非阻塞 重叠 IO的winsock服务器。问题是WSASend 调用被阻塞。客户端代码如下所示
// Initialize Winsock
WSAStartup(MAKEWORD(2, 2), &wsaData);
// Create a SOCKET for connecting to server
ConnectSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
// Connect to server.
WSAConnect(ConnectSocket, ai_addr, ai_addrlen, 0, 0, 0, 0);
// Make the socket non-blocking
u_long iMode = 1;
ioctlsocket(ConnectSocket, FIONBIO, &iMode);
// Send the data
WSAOVERLAPPED SendOverlapped{};
SendOverlapped.hEvent = WSACreateEvent();
WSASend(ConnectSocket, &DataBuf, 1, &SendBytes, 0, &SendOverlapped, 0);
我已经通过ioctlsocket 函数使套接字变为非阻塞,并且我已经为WSASocket 函数提供了WSA_FLAG_OVERLAPPED 标志。我还为WSASend 函数提供了lpOverlapped 参数。但是,对WSASend 的调用仍处于阻塞状态。我在这里遗漏了什么吗?
请原谅上面代码中缺少错误检查,这只是为了这个问题的目的而保持简单。
澄清:在我看来调用被阻塞的原因是——我在一个循环中调用了WSASend 函数 2560 次,每次循环发送 4MB。将所有数据传输到服务器后,循环在 16 秒内完成。如果它是非阻塞的,我会期望循环更快地完成。正如预期的那样,WSASend 函数确实返回 ERROR_IO_PENDING。
这是完整的客户端代码
// Client.cpp
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <stdio.h>
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <ctime>
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")
#define DEFAULT_BUFLEN 4 * 1024 * 1024
#define DEFAULT_PORT "27015"
int __cdecl main(int argc, char **argv)
{
// Initialize Winsock
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed with error: %d\n", iResult);
return 1;
}
// Resolve the server address and port
addrinfo *result = NULL;
addrinfo hints{};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
iResult = getaddrinfo("127.0.0.1", 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
SOCKET ConnectSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (ConnectSocket == INVALID_SOCKET) {
printf("socket failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
// Connect to server.
iResult = WSAConnect(ConnectSocket, result->ai_addr, result->ai_addrlen, 0, 0, 0, 0);
freeaddrinfo(result);
if (ConnectSocket == INVALID_SOCKET) {
printf("Unable to connect to server!\n");
WSACleanup();
return 1;
}
// Make the socket non-blocking
u_long iMode = 1;
iResult = ioctlsocket(ConnectSocket, FIONBIO, &iMode);
if (iResult != NO_ERROR) {
printf("ioctlsocket failed with error: %ld\n", iResult);
WSACleanup();
return 1;
}
// Prepare the buffer
char *sendbuf = new char[DEFAULT_BUFLEN];
for (int i = 0; i < DEFAULT_BUFLEN; ++i)
sendbuf[i] = 'a';
WSABUF DataBuf;
DWORD SendBytes = 0;
DataBuf.buf = sendbuf;
DataBuf.len = DEFAULT_BUFLEN;
// Send the buffer in a loop
int loopCount = 2560;
WSAOVERLAPPED* SendOverlapped = (WSAOVERLAPPED*)calloc(loopCount, sizeof(WSAOVERLAPPED));
clock_t start = clock();
for (int i = 0; i < loopCount; ++i)
{
SendOverlapped[i].hEvent = WSACreateEvent();
iResult = WSASend(ConnectSocket, &DataBuf, 1, &SendBytes, 0, SendOverlapped + i, 0);
if (iResult == SOCKET_ERROR)
{
if (ERROR_IO_PENDING == WSAGetLastError())
{
continue;
}
printf("send failed with error: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
}
std::cout << "initiating send data took " << clock() - start << " ms" << std::endl;
// Wait for all the events to be signalled
for (int i = 0; i < loopCount; ++i)
{
iResult = WSAWaitForMultipleEvents(1, &SendOverlapped[i].hEvent, TRUE, INFINITE, TRUE);
if (iResult == WSA_WAIT_FAILED) {
printf("WSAWaitForMultipleEvents failed with error: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
DWORD Flags = 0;
BOOL result = WSAGetOverlappedResult(ConnectSocket, SendOverlapped + i, &SendBytes, FALSE, &Flags);
if (result == FALSE) {
printf("WSASend failed with error: %d\n", WSAGetLastError());
break;
}
}
std::cout << "actual send data took " << clock() - start << " ms";
// 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();
////free(SendOverlapped);
return 0;
}
这是服务器端代码
// Server.cpp
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <stdio.h>
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#pragma comment (lib, "Ws2_32.lib")
#define DEFAULT_BUFLEN 4 * 1024 * 1024
#define DEFAULT_PORT "27015"
int __cdecl main(void)
{
// Initialize Winsock
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed with error: %d\n", iResult);
return 1;
}
// Resolve the server address and port
addrinfo hints{};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
addrinfo *result = NULL;
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
SOCKET 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
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
closesocket(ListenSocket);
// Receive until the peer shuts down the connection
char* recvbuf = new char[DEFAULT_BUFLEN];
do {
iResult = recv(ClientSocket, recvbuf, DEFAULT_BUFLEN, 0);
if (iResult > 0) {
printf("Bytes received: %d\n", iResult);
}
else if (iResult == 0)
printf("Connection closing...\n");
else {
printf("recv failed with error: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
} while (iResult > 0);
// shutdown the connection since we're done
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed with error: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
// cleanup
closesocket(ClientSocket);
WSACleanup();
return 0;
}
【问题讨论】:
-
“呼叫被阻塞”到底是什么意思?即使您指定应使用重叠 IO,API 也可能能够在返回之前完成调用。正如文档所说,“如果重叠操作立即完成,WSASend 将返回零值,并且 lpNumberOfBytesSent 参数将使用发送的字节数进行更新”。
-
此代码中的错误检查绝对为零。你不能写这样的代码。
-
@EJP,我知道没有错误检查,那只是为了让这个问题保持整洁。
-
单次非阻塞调用表示:WSASend立即返回,实际I/O操作完成需要等待重叠事件。仅此而已。您对异步调用的期望过高。
-
@EJP,请在否决问题之前考虑一下。如果您需要更多信息,您可以随时询问。失去一个人所拥有的极少数观点并且不鼓励在 * 上提问是很痛苦的。
标签: sockets network-programming winsock2