windows-nt/Source/XPSP1/NT/com/rpc/perf/rawio/iosvr.c
2020-09-26 16:20:57 +08:00

1060 lines
29 KiB
C

/*++
Copyright (C) Microsoft Corporation, 1996 - 1999
Module Name:
iosvr.c
Abstract:
I/O completion port perf test.
Author:
Mario Goertzel [MarioGo]
Revision History:
MarioGo 3/3/1996 Based on win32 sdk winnt\sockets sample.
--*/
#include "ioperf.h"
// PERF CHECK: Must be less for 4K for optimum perf on small tests ??
enum PROTOCOL {
TCP = 0,
SPX,
NMPIPE,
UDP
} Protocol = TCP;
CHAR *ProtocolNames[] = { "TCP/IP", "SPX", "Named Pipes", "UDP/IP" };
DWORD ProtocolFrameSize[] = { 1460, 1400 /* ? */, ((64 * 1024) - 1)/4, 1472 };
DWORD MaxWriteSize;
BOOL fUseSend = FALSE;
const char *USAGE = "-n <# clients> -n <request size> -n <reply size> -n <test case> -n <# threads> -n <concurrency factor> -t <protocol>\n"
"\t-n <# clients> - for connection protocols. On datagram this\n"
"\t controls the number of outstanding recv's\n"
"\t-n <request size> - bytes, default 24\n"
"\t-n <reply size> - bytes, default 24\n"
"\t-n <test case>\n"
"\t 1 - Uses async writes (64*frame size)\n"
"\t 2 - Uses async writes (4096) - default \n"
"\t 3 - (winsock only) Uses send() for reply\n"
"\t-n <# threads> - worker threads, default # of processors * 2\n"
"\t-n <concurrency factor> - default # of processors\n"
"\t-t - tcp, spx, nmpipe (protseqs ok)\n"
;
typedef long STATUS;
typedef struct _PER_CLIENT_DATA {
HANDLE hClient;
struct _PER_CLIENT_DATA *pMe;
OVERLAPPED OverlappedRead;
struct _PER_CLIENT_DATA *pMe2;
OVERLAPPED OverlappedWrite;
PMESSAGE pRequest;
PMESSAGE pReply;
DWORD dwPreviousRead;
DWORD dwPreviousWrite;
DWORD dwTotalToWrite;
DWORD dwRequestsProcessed;
SOCKADDR_IN DgSendAddr;
SOCKADDR_IN DgRecvAddr;
DWORD dwRecvAddrSize;
} PER_CLIENT_DATA, *PPER_CLIENT_DATA;
PPER_CLIENT_DATA *ClientData;
typedef struct _PER_THREAD_DATA {
DWORD TotalTransactions;
DWORD TotalRequestBytes;
DWORD TotalReplyBytes;
} PER_THREAD_DATA, *PPER_THREAD_DATA;
PPER_THREAD_DATA *ThreadData;
DWORD dwNumberOfClients;
DWORD dwNumberOfWorkers;
DWORD dwConcurrency;
DWORD dwWorkIndex;
DWORD dwRequestSize;
DWORD dwReplySize;
SYSTEM_INFO SystemInfo;
HANDLE CompletionPort;
DWORD dwActiveClientCount;
HANDLE hBenchmarkStart;
BOOL fClientsGoHome = FALSE;
BOOL
WINAPI
CreateNetConnections(
VOID
);
BOOL
WINAPI
CreateWorkers(
VOID
);
DWORD
WINAPI
WorkerThread(
LPVOID WorkContext
);
VOID
WINAPI
CompleteBenchmark(
VOID
);
int __cdecl
main (
int argc,
char *argv[],
char *envp[]
)
{
ParseArgv(argc, argv);
//
// try to get timing more accurate... Avoid context
// switch that could occur when threads are released
//
SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_TIME_CRITICAL);
//
// Figure out how many processors we have to size the minimum
// number of worker threads and concurrency
//
GetSystemInfo (&SystemInfo);
dwNumberOfClients = 1;
dwNumberOfWorkers = 2 * SystemInfo.dwNumberOfProcessors;
dwConcurrency = SystemInfo.dwNumberOfProcessors;
dwRequestSize = 24;
dwReplySize = 24;
if (Iterations == 1000)
{
Dump("Assuming 4 iterations for scalability test\n");
Iterations = 4;
}
if (sizeof(MESSAGE) > 24)
{
ApiError("Configuration problem, message size > 24", 0);
}
if (_stricmp(Protseq, "tcp") == 0 || _stricmp(Protseq, "ncacn_ip_tcp") == 0 )
{
Protocol = TCP;
}
else if ( _stricmp(Protseq, "spx") == 0 || _stricmp(Protseq, "ncacn_spx") == 0 )
{
Protocol = SPX;
}
else if ( _stricmp(Protseq, "nmpipe") == 0 || _stricmp(Protseq, "ncacn_np") == 0 )
{
Protocol = NMPIPE;
}
else if ( _stricmp(Protseq, "udp") == 0 || _stricmp(Protseq, "ncadg_ip_udp") == 0 )
{
Protocol = UDP;
}
if (Options[0] > 0)
dwNumberOfClients = Options[0];
if (Options[1] > 0)
{
dwRequestSize = Options[1];
}
if (Options[2] > 0)
{
dwReplySize = Options[2];
}
if (Options[3] > 0)
{
switch(Options[3])
{
case 1:
MaxWriteSize = 4 * ProtocolFrameSize[Protocol];
break;
case 2:
MaxWriteSize = 4096;
break;
case 3:
fUseSend = TRUE;
break;
default:
printf("Invalid test case: %d\n", Options[3]);
return(0);
break;
}
}
else
{
MaxWriteSize = 4096;
}
if (Options[4] > 0)
{
dwNumberOfWorkers = Options[4];
}
if (Options[5] > 0)
{
dwConcurrency = Options[5];
}
printf("%2d Clients %2d Workers Concurrency %d, listening on %s\n",
dwNumberOfClients,
dwNumberOfWorkers,
dwConcurrency,
ProtocolNames[Protocol]
);
ClientData = (PPER_CLIENT_DATA *)Allocate(sizeof(PPER_CLIENT_DATA) * dwNumberOfClients);
ThreadData = (PPER_THREAD_DATA *)Allocate(sizeof(PPER_THREAD_DATA) * dwNumberOfWorkers);
if (!ThreadData || !ClientData)
{
ApiError("malloc", GetLastError());
}
if (!CreateNetConnections())
{
return 1;
}
if (!CreateWorkers())
{
return 1;
}
CompleteBenchmark();
return 0;
}
VOID
SubmitWrite(PER_CLIENT_DATA *pClient, DWORD size)
{
DWORD t1, t2;
INT write_type = Protocol;
BOOL b;
INT err;
DWORD status;
WSABUF buf;
pClient->dwTotalToWrite = size;
pClient->dwPreviousWrite = size;
if ( ( write_type == TCP
|| write_type == SPX )
&& !fUseSend )
{
// We want to use WriteFile for TCP & SPX if not using send.
write_type = NMPIPE;
}
switch(write_type)
{
case NMPIPE:
if (size > MaxWriteSize)
{
size = MaxWriteSize;
}
pClient->dwPreviousWrite = size;
b = WriteFile(pClient->hClient,
pClient->pReply,
size,
&t1,
&pClient->OverlappedWrite);
if (!b && GetLastError() != ERROR_IO_PENDING)
{
ApiError("WriteFile", GetLastError());
}
break;
case UDP:
memcpy(&pClient->DgSendAddr, &pClient->DgRecvAddr, sizeof(SOCKADDR_IN));
buf.buf = (PCHAR) pClient->pReply;
buf.len = size;
t1 = 0;
err = WSASendTo((SOCKET)pClient->hClient,
&buf,
1,
&t1,
0,
(PSOCKADDR)&pClient->DgSendAddr,
sizeof(SOCKADDR_IN),
&pClient->OverlappedWrite,
0);
if (err != 0 && GetLastError() != ERROR_IO_PENDING)
{
ApiError("WSASendTo", GetLastError());
}
break;
case TCP:
case SPX:
err = send((SOCKET)pClient->hClient,
(PCHAR)pClient->pReply,
sizeof(MESSAGE),
0);
if (err == SOCKET_ERROR)
{
ApiError("send", GetLastError());
}
break;
default:
ApiError("Bad protocol", 0);
}
return;
}
VOID
SubmitRead(PER_CLIENT_DATA *pClient)
{
DWORD t1, t2, t3;
BOOL b;
DWORD status;
INT err;
if (Protocol == UDP)
{
WSABUF buf;
t1 = t2 = 0;
pClient->dwRecvAddrSize = sizeof(pClient->DgRecvAddr);
buf.buf = (PCHAR)pClient->pRequest;
buf.len = dwRequestSize;
status = WSARecvFrom((SOCKET)pClient->hClient,
&buf,
1,
&t1,
&t2,
(PSOCKADDR)&pClient->DgRecvAddr,
&pClient->dwRecvAddrSize,
&pClient->OverlappedRead,
0);
if (status != NO_ERROR && GetLastError() != ERROR_IO_PENDING)
{
ApiError("WSARecvFrom", GetLastError());
}
}
else
{
b = ReadFile(pClient->hClient,
pClient->pRequest,
dwRequestSize,
&t1,
&pClient->OverlappedRead
);
if (!b && GetLastError () != ERROR_IO_PENDING)
{
ApiError("ReadFile", GetLastError());
}
}
return;
}
VOID
WINAPI
CompleteBenchmark (
VOID
)
{
DWORD StartCalls;
DWORD TotalTicks, FinalCalls, MaxCalls, MinCalls, TotalCalls;
DWORD i, j;
PPER_CLIENT_DATA pClient;
SetEvent(hBenchmarkStart);
Sleep(1000);
for (i = 0; i < Iterations; i++)
{
StartTime();
StartCalls = 0;
for (j = 0; j < dwNumberOfClients; j++ )
{
pClient = ClientData[j];
StartCalls += pClient->dwRequestsProcessed;
}
Sleep(Interval * 1000);
FinalCalls = MaxCalls = 0;
MinCalls = ~0;
for (j = 0; j < dwNumberOfClients; j++)
{
pClient = ClientData[j];
FinalCalls += pClient->dwRequestsProcessed;
if (pClient->dwRequestsProcessed < MinCalls )
{
MinCalls = pClient->dwRequestsProcessed;
}
if (pClient->dwRequestsProcessed > MaxCalls)
{
MaxCalls = pClient->dwRequestsProcessed;
}
}
TotalCalls = FinalCalls - StartCalls;
TotalTicks = FinishTiming();
Dump("Ticks: %4d, Total: %4d, Average %4d, TPS %3d\n",
TotalTicks,
TotalCalls,
TotalCalls / dwNumberOfClients,
TotalCalls * 1000 / TotalTicks
);
Verbose("Max: %d, Min: %d\n", MaxCalls, MinCalls);
}
// Clients will be shutdown on next call...
fClientsGoHome = TRUE;
Sleep(5000);
printf("Test Complete\n");
for (i = 0; i < dwNumberOfWorkers; i++)
{
printf("\tThread[%2d] %d request and %d reply bytes in %d IOs\n",
i,
ThreadData[i]->TotalRequestBytes,
ThreadData[i]->TotalReplyBytes,
ThreadData[i]->TotalTransactions
);
}
}
BOOL
WINAPI
CreateNetConnections(
void
)
{
STATUS status;
DWORD i;
SOCKET listener;
INT err;
WSADATA WsaData;
DWORD nbytes;
BOOL b;
PPER_CLIENT_DATA pClient;
if (Protocol == TCP || Protocol == SPX)
{
status = WSAStartup (0x2, &WsaData);
CHECK_STATUS(status, "WSAStartup");
//
// Open a socket to listen for incoming connections.
//
if (Protocol == TCP)
{
SOCKADDR_IN localAddr;
listener = WSASocketW(AF_INET, SOCK_STREAM, 0, 0, 0, WSA_FLAG_OVERLAPPED);
if (listener == INVALID_SOCKET)
{
ApiError("socket", GetLastError());
}
//
// Bind our server to the agreed upon port number.
//
ZeroMemory (&localAddr, sizeof (localAddr));
localAddr.sin_port = htons (TCP_PORT);
localAddr.sin_family = AF_INET;
err = bind (listener, (PSOCKADDR) & localAddr, sizeof (localAddr));
if (err == SOCKET_ERROR)
{
ApiError("bind", GetLastError());
}
}
else if (Protocol == SPX)
{
SOCKADDR_IPX localAddr;
listener = socket (AF_IPX, SOCK_STREAM, NSPROTO_SPX);
if (listener == INVALID_SOCKET)
{
ApiError("socket", GetLastError());
}
ZeroMemory (&localAddr, sizeof (localAddr));
localAddr.sa_socket = htons (SPX_PORT);
localAddr.sa_family = AF_IPX;
err = bind (listener, (PSOCKADDR) & localAddr, sizeof (localAddr));
if (err == SOCKET_ERROR)
{
ApiError("bind", GetLastError());
}
}
else if (Protocol == SPX)
{
ApiError("Case not implemented", 0);
}
// Prepare to accept client connections. Allow up to 5 pending
// connections.
err = listen (listener, 5);
if (err == SOCKET_ERROR)
{
ApiError("listen", GetLastError());
}
//
// Only Handle a single Queue
//
for (i = 0; i < dwNumberOfClients; i++)
{
SOCKET s;
pClient = Allocate(sizeof(PER_CLIENT_DATA));
if (!pClient)
{
ApiError("Allocate", GetLastError());
}
pClient->pRequest = Allocate(dwRequestSize);
pClient->pReply = Allocate(dwReplySize);
if ( !pClient->pRequest
|| !pClient->pReply)
{
ApiError("Allocate", GetLastError());
}
// Accept incoming connect requests
s = accept (listener, NULL, NULL);
if (s == INVALID_SOCKET)
{
// exiting anyway, no need to cleanup.
ApiError("accept", GetLastError());
}
dbgprintf("Accepted client %d\n", i);
// Note that dwConcurrency says how many concurrent cpu bound threads to
// allow thru this should be tunable based on the requests. CPU bound requests
// will really really honor this.
pClient->hClient = (HANDLE)s;
CompletionPort = CreateIoCompletionPort(pClient->hClient,
CompletionPort,
(ULONG_PTR)pClient,
dwConcurrency);
if (!CompletionPort)
{
ApiError("CreateIoCompletionPort", GetLastError());
}
//
// Start off an asynchronous read on the socket.
//
pClient->dwPreviousRead = 0;
pClient->dwRequestsProcessed = 0;
ZeroMemory(&pClient->OverlappedRead, sizeof(OVERLAPPED));
ZeroMemory(&pClient->OverlappedWrite, sizeof(OVERLAPPED));
b = ReadFile(pClient->hClient,
pClient->pRequest,
dwRequestSize,
&nbytes,
&pClient->OverlappedRead
);
if (!b && GetLastError() != ERROR_IO_PENDING)
{
ApiError("ReadFile", GetLastError());
}
ClientData[i] = pClient;
}
dwActiveClientCount = dwNumberOfClients;
}
else if (Protocol == NMPIPE)
{
HANDLE h;
OVERLAPPED *lpo;
DWORD nbytes, index;
BOOL b;
for (i = 0; i < dwNumberOfClients; i++)
{
h = CreateNamedPipe(NM_PORT,
PIPE_ACCESS_DUPLEX
| FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE
| (PIPE_READMODE_MESSAGE, 0) // ************
| PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
4096, // ***************
4096, // ***************
INFINITE,
0);
if (!h)
{
ApiError("CreateNamedPipe", GetLastError());
}
//
// Wait for clients to connect
//
pClient = Allocate(sizeof(PER_CLIENT_DATA));
if (!pClient)
{
ApiError("Allocate", GetLastError());
}
ZeroMemory(pClient, sizeof(PER_CLIENT_DATA));
pClient->pRequest = Allocate(dwRequestSize);
pClient->pReply = Allocate(dwReplySize);
if ( !pClient->pRequest
|| !pClient->pReply)
{
ApiError("Allocate", GetLastError());
}
// Accept incoming connect requests
pClient->hClient = h;
b = ConnectNamedPipe(pClient->hClient,
&pClient->OverlappedRead);
dbgprintf("ConnectNamedPipe: %d %d\n", b, GetLastError());
if (b == 0)
{
if (GetLastError() == ERROR_IO_PENDING)
{
b = GetOverlappedResult(pClient->hClient,
&pClient->OverlappedRead,
&nbytes,
TRUE);
if (b == 0)
{
ApiError("GetOverlappedResult", GetLastError());
}
dbgprintf("Client connected\n");
}
else
{
ApiError("ConnectNamedPipe", GetLastError());
}
}
// Add the clients pipe instance to the completion port.
CompletionPort = CreateIoCompletionPort(h,
CompletionPort,
(ULONG_PTR)pClient,
dwConcurrency);
if (!CompletionPort)
{
ApiError("CreteIoCompletionPort", GetLastError());
}
//
// Start off an asynchronous read on the socket.
//
pClient->dwPreviousRead = 0;
pClient->dwRequestsProcessed = 0;
ZeroMemory(&pClient->OverlappedRead, sizeof(OVERLAPPED));
ZeroMemory(&pClient->OverlappedWrite, sizeof(OVERLAPPED));
b = ReadFile(pClient->hClient,
pClient->pRequest,
dwRequestSize,
&nbytes,
&pClient->OverlappedRead
);
if (!b && GetLastError() != ERROR_IO_PENDING)
{
ApiError("ReadFile", GetLastError());
}
ClientData[i] = pClient;
}
dwActiveClientCount = dwNumberOfClients;
}
else if (Protocol == UDP)
{
SOCKADDR_IN localAddr;
status = WSAStartup (0x2, &WsaData);
CHECK_STATUS(status, "WSAStartup");
listener = WSASocketW(AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0,
0, WSA_FLAG_OVERLAPPED);
if (listener == INVALID_SOCKET)
{
ApiError("socket", GetLastError());
}
//
// Bind our server to the agreed upon port number.
//
ZeroMemory (&localAddr, sizeof (localAddr));
localAddr.sin_port = htons (UDP_PORT);
localAddr.sin_family = AF_INET;
err = bind (listener, (PSOCKADDR) & localAddr, sizeof (localAddr));
if (err == SOCKET_ERROR)
{
ApiError("bind", GetLastError());
}
CompletionPort = CreateIoCompletionPort((HANDLE) listener,
CompletionPort,
0,
dwConcurrency);
if (!CompletionPort)
{
ApiError("CreateIoCompletionPort", GetLastError());
}
//
// Start off asynchronous reads on the socket.
//
for(i = 0; i < dwNumberOfClients; i++)
{
pClient = Allocate(sizeof(PER_CLIENT_DATA));
if (!pClient)
{
ApiError("Allocate", GetLastError());
}
pClient->pRequest = Allocate(dwRequestSize);
pClient->pReply = Allocate(dwReplySize);
if ( !pClient->pRequest
|| !pClient->pReply)
{
ApiError("Allocate", GetLastError());
}
ZeroMemory(&pClient->OverlappedRead, sizeof(OVERLAPPED));
ZeroMemory(&pClient->OverlappedWrite, sizeof(OVERLAPPED));
pClient->hClient = (HANDLE)listener;
pClient->dwPreviousRead = 0;
pClient->dwRequestsProcessed = 0;
pClient->pMe = pClient;
pClient->pMe2 = pClient;
Trace("Created: %p %p %p\n", pClient, &pClient->OverlappedRead,
&pClient->OverlappedWrite);
ClientData[i] = pClient;
SubmitRead(pClient);
}
dwActiveClientCount = dwNumberOfClients;
}
else
{
ApiError("Invalid protocol", 0);
}
// Protocol independent part
hBenchmarkStart = CreateEvent (NULL, TRUE, FALSE, NULL);
if (!hBenchmarkStart)
{
ApiError("CreateEvent", GetLastError());
}
return TRUE;
}
BOOL
WINAPI
CreateWorkers(
void
)
{
DWORD ThreadId;
HANDLE ThreadHandle;
DWORD i;
PPER_THREAD_DATA pThreadData;
for (i = 0; i < dwNumberOfWorkers; i++)
{
pThreadData = Allocate(sizeof(PER_THREAD_DATA));
if (!pThreadData)
{
ApiError("malloc", GetLastError());
}
ZeroMemory(pThreadData, sizeof(PER_THREAD_DATA));
ThreadHandle = CreateThread(NULL,
0,
WorkerThread,
(LPVOID)pThreadData,
0,
&ThreadId
);
if (!ThreadHandle)
{
ApiError("CreateThread", GetLastError());
}
CloseHandle(ThreadHandle);
ThreadData[i] = pThreadData;
}
return TRUE;
}
DWORD
WINAPI
WorkerThread (
LPVOID WorkContext
)
{
PPER_THREAD_DATA Me;
INT err;
DWORD ResponseLength;
BOOL b;
LPOVERLAPPED lpo;
DWORD nbytes;
ULONG_PTR WorkIndex;
PPER_CLIENT_DATA pClient;
LONG count;
WaitForSingleObject (hBenchmarkStart, INFINITE);
Me = (PPER_THREAD_DATA) WorkContext;
for (;;)
{
lpo = 0;
b = GetQueuedCompletionStatus(CompletionPort,
&nbytes,
&WorkIndex,
&lpo,
INFINITE
);
// dbgprintf("GetQueuedCompletionStatus: %d %d %p\n", b, nbytes, lpo);
if (WorkIndex == 0)
{
// Must be a datagram read or write
WorkIndex = (ULONG_PTR)((unsigned char *)lpo - 4);
WorkIndex = *(PDWORD)WorkIndex;
}
Me->TotalTransactions++;
if (b || lpo)
{
if (b)
{
DWORD nbytes2;
pClient = (PPER_CLIENT_DATA)WorkIndex;
if (lpo == &pClient->OverlappedWrite)
{
dbgprintf("Write completed %d (%p)\n", nbytes, pClient);
Me->TotalReplyBytes += nbytes;
nbytes = pClient->dwTotalToWrite - pClient->dwPreviousWrite;
if (nbytes)
{
if (nbytes > MaxWriteSize)
{
nbytes = MaxWriteSize;
}
pClient->dwPreviousWrite += nbytes;
b = WriteFile(pClient->hClient,
(PBYTE)pClient->pReply + pClient->dwPreviousWrite - nbytes,
nbytes,
&nbytes2,
&pClient->OverlappedWrite);
dbgprintf("Write completed: %d %d (of %d) %d (of %d)\n", b, nbytes2, nbytes, pClient->dwPreviousWrite, dwReplySize);
if (!b && GetLastError() != ERROR_IO_PENDING)
{
ApiError("WriteFile", GetLastError());
}
}
}
else
{
Me->TotalRequestBytes += nbytes;
dbgprintf(" Read completed %d (%p)\n", nbytes, pClient);
if (nbytes == 0)
{
Trace("Connection closed (zero byte read)\n");
CloseHandle(pClient->hClient);
continue;
}
switch (pClient->pRequest->MessageType)
{
case CONNECT:
// Send test parameters back to the client
pClient->pReply->MessageType = SETUP;
pClient->pReply->u.Setup.RequestSize = dwRequestSize;
pClient->pReply->u.Setup.ReplySize = dwReplySize;
SubmitWrite(pClient, sizeof(MESSAGE));
pClient->dwPreviousRead = 0;
SubmitRead(pClient);
break;
case DATA_RQ:
// Make sure we got all the data and no more.
pClient->dwRequestsProcessed++;
nbytes += pClient->dwPreviousRead;
if (nbytes > dwRequestSize)
{
ApiError("Too much data returned\n", 0);
}
if (nbytes < dwRequestSize)
{
dbgprintf("Partial receive of %d (of %d)\n", nbytes, dwRequestSize);
// Resubmit the IO for the rest of the request.
pClient->dwPreviousRead = nbytes;
b = ReadFile(pClient->hClient,
((PBYTE)pClient->pRequest) + nbytes,
dwRequestSize - nbytes,
&nbytes,
&pClient->OverlappedRead);
if (!b && GetLastError () != ERROR_IO_PENDING)
{
ApiError("ReadFile for remainder", GetLastError());
}
// Pickup this or another IO
break;
}
if (nbytes != pClient->pRequest->u.Data.TotalSize)
{
printf("Invalid request size, got %d, expected %d\n",
nbytes, pClient->pRequest->u.Data.TotalSize);
ApiError("test sync", 0);
}
pClient->dwPreviousRead = 0;
// Could sleep/do work here.
// Send a response and post another asynchronous read on the
// socket.
if (fClientsGoHome == FALSE)
{
pClient->pReply->MessageType = DATA_RP;
}
else
{
pClient->pReply->MessageType = FINISH;
}
pClient->pReply->u.Data.TotalSize = dwReplySize;
SubmitWrite(pClient, dwReplySize);
SubmitRead(pClient);
break;
default:
ApiError("Invalid message type", pClient->pRequest->MessageType);
}
} // read or write
}
else
{
Trace("Client closed connection\n", GetLastError());
}
}
else
{
ApiError("Wait failed", GetLastError());
}
} // loop
// not reached
return 0;
}