/*
This example demonstrates two problems with WSARecv and IOCP on Vista and up.
I'm using Windows 7 Ultimate, and it still occurs here. One problem occurs
when using FILE_SKIP_COMPLETION_PORT_ON_SUCCESS and one is when it is not in
use. This example runs fine on XP, obviously with USE_SKIP_FEATURE = 0.
The example simply accepts a socket and reads from it as fast as possible.
With USE_SKIP_FEATURE = 1, it eventually (5-30 seconds) gets a timeout from
GetQueuedCompletionStatus, which should not occur. You should see this example
die with:
queueing new event
event completion pending
GQCS wait timeout?
With USE_SKIP_FEATURE = 0, it quickly (1-5 seconds) gets a return value of 0
from WSARecv with lpNumberOfBytesRecvd = 0, indicating socket closure, which
should not be true. You should see this example will die with:
queueing new event
event completed right away: 0 bytes
eof
MSDN says:
http://msdn.microsoft.com/en-us/library/ms741688(VS.85).aspx
"For byte streams, zero bytes having been read (as indicated by a zero
return value to indicate success, and lpNumberOfBytesRecvd value of zero)
indicates graceful closure and that no more bytes will ever be read."
...
"Overlapped Socket I/O
If an overlapped operation completes immediately, WSARecv returns a value
of zero and the lpNumberOfBytesRecvd parameter is updated with the number
of bytes received and the flag bits indicated by the lpFlags parameter are
also updated."
I recommend directing the output of this example to a file, to prevent the
console from slowing down the the application and making the bug difficult to
encounter.
For the connecting and sending side, anything which connects to localhost port
27015 and sends as fast as it can should do. Here is a very simple python
script:
import socket
s = socket.socket()
s.connect(("localhost", 27015))
while 1:
s.send(" " * 65536)
In both cases the sender dies with:
socket.error: [Errno 10054] An existing connection was forcibly closed by the remote host
If there are any questions or obvious bugs, please contact me at: ghazel at gmail dot com
*/
#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include "winsock2.h"
#include "mswsock.h"
#include "malloc.h"
#include <windows.h>
#define BUFSIZE 16384
#define MAX_PENDING 5
#define USE_SKIP_FEATURE 0
void main()
{
//----------------------------------------
// Declare and initialize variables
WSADATA wsaData;
HANDLE hCompPort;
SOCKET ListenSocket, AcceptSocket;
sockaddr_in service;
sockaddr a_addr;
int a_addrlen = sizeof(a_addr);
printf("use skip feature: %d\n", USE_SKIP_FEATURE);
//----------------------------------------
// Initialize Winsock
int iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != NO_ERROR) {
printf("Error at WSAStartup\n");
return;
}
//----------------------------------------
// Create a handle for the completion port
hCompPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, (u_long)0, 0);
//----------------------------------------
// Create a listening socket
ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ListenSocket == INVALID_SOCKET) {
printf("Error at socket(): ListenSocket\n");
WSACleanup();
return;
}
//----------------------------------------
// Associate the listening socket with the completion port
CreateIoCompletionPort((HANDLE)ListenSocket, hCompPort, (u_long)0, 0);
//----------------------------------------
// Bind the listening socket to the local IP address
// and port 27015
hostent* thisHost;
char* ip;
u_short port;
port = 27015;
thisHost = gethostbyname("localhost");
ip = inet_ntoa (*(struct in_addr *)*thisHost->h_addr_list);
service.sin_family = AF_INET;
service.sin_addr.s_addr = inet_addr(ip);
service.sin_port = htons(port);
if (bind(ListenSocket,(SOCKADDR*)&service, sizeof(service)) == SOCKET_ERROR) {
printf("bind failed\n");
closesocket(ListenSocket);
return;
}
//----------------------------------------
// Start listening on the listening socket
if (listen( ListenSocket, 100 ) == SOCKET_ERROR) {
printf("error listening\n");
}
printf("Listening on address: %s:%d\n", ip, port);
AcceptSocket = accept(ListenSocket, &a_addr, &a_addrlen);
if (AcceptSocket == INVALID_SOCKET) {
printf("accept failed\n");
return;
}
CreateIoCompletionPort((HANDLE)AcceptSocket, hCompPort, (u_long)0, 0);
#if USE_SKIP_FEATURE
if (!SetFileCompletionNotificationModes((HANDLE)AcceptSocket, FILE_SKIP_COMPLETION_PORT_ON_SUCCESS)) {
printf("SetFileCompletionNotificationModes failed! %d\n", GetLastError());
return;
}
#endif
int pending_completions = 0;
while (1) {
DWORD bytes;
DWORD key;
DWORD rc;
DWORD flags = 0;
char buf[BUFSIZE];
WSABUF wsabuf;
WSAOVERLAPPED *ov = (WSAOVERLAPPED *)malloc(sizeof(WSAOVERLAPPED));
memset(ov, 0, sizeof(WSAOVERLAPPED));
wsabuf.len = 16384;
wsabuf.buf = buf;
printf("queueing new event\n");
rc = WSARecv(AcceptSocket, &wsabuf, 1, &bytes, &flags, ov, 0);
if (rc == SOCKET_ERROR) {
rc = WSAGetLastError();
if (rc != WSA_IO_PENDING) {
printf("WSARecv error (a): %d\n", rc);
return;
}
pending_completions += 1;
printf("event completion pending\n");
} else if (rc != 0) {
printf("WSARecv error (b): %d\n", rc);
return;
}
if (rc == 0) {
#if USE_SKIP_FEATURE
// no completion pending notice, because of FILE_SKIP_COMPLETION_PORT_ON_SUCCESS
free(ov);
#else
// we still need to receive the completion notice, even though the event is complete
pending_completions += 1;
#endif
printf("event completed right away: %u bytes\n", bytes);
if (bytes == 0) {
printf("eof\n");
return;
}
if (pending_completions < MAX_PENDING) {
printf("currently pending: %u of %u\n", pending_completions, MAX_PENDING);
continue;
}
}
while (pending_completions) {
WSAOVERLAPPED *ovp;
rc = GetQueuedCompletionStatus(hCompPort, &bytes, &key, ((OVERLAPPED **)(&ovp)), 2000);
if (!rc) {
rc = GetLastError();
if (rc == WAIT_TIMEOUT) {
printf("GQCS wait timeout?\n");
// Two seconds should be plenty, when the sender is local and just in a tight-loop.
//continue;
return;
}
printf("GQCS error: %d\n", rc);
if (rc == ERROR_NETNAME_DELETED) {
printf("eof\n");
}
return;
}
pending_completions -= 1;
printf("event complete: (%u bytes) pending: %u of %u\n", bytes, pending_completions, MAX_PENDING);
free(ovp);
}
}
}