549 lines
14 KiB
C++
549 lines
14 KiB
C++
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (C) Microsoft Corporation. All Rights Reserved.
|
|
//
|
|
// Module:
|
|
// iocpclient.cpp
|
|
//
|
|
// Abstract:
|
|
// Use the -? commandline switch to determine available options.
|
|
//
|
|
// This application is a very simple minded program that sends buffers of
|
|
// data to a server and waits for the server to echo the data back and then
|
|
// compares the first and last byte of the buffers. The destination server
|
|
// can be specified using the (-n) option and the destination port using the
|
|
// (-e) option. The size of the buffer to send is specified using the (-b)
|
|
// option which is in 1k increments. Multiple threads can be spawned to hit
|
|
// the server.
|
|
//
|
|
// Please note that spawning multiple threads is not a scalable way
|
|
// to handle multiple socket connections. This sample was built for the
|
|
// express purpose of providing a simple and easy to understand client to
|
|
// pound on the iocp socket server.
|
|
//
|
|
// Another point worth noting is that the Win32 API CreateThread() does not
|
|
// initialize the C Runtime and therefore, C runtime functions such as
|
|
// printf() have been avoid or rewritten (see myprintf()) to use just Win32 APIs.
|
|
//
|
|
// Entry Points:
|
|
// main - this is where it all starts
|
|
//
|
|
// Build:
|
|
// Use the headers and libs from the Jan98 Platform SDK or later.
|
|
// Link with ws2_32.lib
|
|
//
|
|
//
|
|
//
|
|
|
|
#pragma warning (disable:4127)
|
|
|
|
#ifdef _IA64_
|
|
#pragma warning(disable:4706 4267)
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#include <strsafe.h>
|
|
|
|
#define MAXTHREADS 64
|
|
|
|
#define xmalloc(s) HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(s))
|
|
#define xfree(p) {HeapFree(GetProcessHeap(),0,(p)); p = NULL;}
|
|
|
|
typedef struct _OPTIONS {
|
|
char szHostname[64];
|
|
char *port;
|
|
int nTotalThreads;
|
|
int nBufSize;
|
|
BOOL bVerbose;
|
|
} OPTIONS;
|
|
|
|
typedef struct THREADINFO {
|
|
HANDLE hThread[MAXTHREADS];
|
|
SOCKET sd[MAXTHREADS];
|
|
} THREADINFO;
|
|
|
|
static OPTIONS default_options = {"localhost", "5001", 1, 4096, FALSE};
|
|
static OPTIONS g_Options;
|
|
static THREADINFO g_ThreadInfo;
|
|
static BOOL g_bEndClient = FALSE;
|
|
static WSAEVENT g_hCleanupEvent[1];
|
|
|
|
static BOOL WINAPI CtrlHandler (DWORD dwEvent);
|
|
static BOOL ValidOptions(char *argv[], int argc);
|
|
static VOID Usage(char *szProgramname, OPTIONS *pOptions);
|
|
static DWORD WINAPI EchoThread(LPVOID lpParameter);
|
|
static BOOL CreateConnectedSocket(int nThreadNum);
|
|
static BOOL SendBuffer(int nThreadNum, char *outbuf);
|
|
static BOOL RecvBuffer(int nThreadNum, char *inbuf);
|
|
static int myprintf(const char *lpFormat, ...);
|
|
|
|
int __cdecl main(int argc, char *argv[]) {
|
|
|
|
OSVERSIONINFO verInfo = {0};
|
|
WSADATA WSAData;
|
|
DWORD dwThreadId = 0;
|
|
DWORD dwRet = 0;
|
|
BOOL bInitError = FALSE;
|
|
int nThreadNum[MAXTHREADS];
|
|
int i = 0;
|
|
int nRet = 0;
|
|
|
|
verInfo.dwOSVersionInfoSize = sizeof(verInfo);
|
|
GetVersionEx(&verInfo);
|
|
if( verInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ) {
|
|
|
|
//
|
|
// Since this application can heavily stress system resources
|
|
// we decided to limit running it on NT.
|
|
//
|
|
myprintf("Please run %s only on NT, thank you\n", argv[0]);
|
|
return(0);
|
|
}
|
|
|
|
for( i = 0; i < MAXTHREADS; i++ ) {
|
|
g_ThreadInfo.sd[i] = INVALID_SOCKET;
|
|
g_ThreadInfo.hThread[i] = INVALID_HANDLE_VALUE;
|
|
nThreadNum[i] = 0;
|
|
}
|
|
|
|
g_hCleanupEvent[0] = WSA_INVALID_EVENT;
|
|
|
|
if( !ValidOptions(argv, argc) )
|
|
return(1);
|
|
|
|
if( (nRet = WSAStartup(MAKEWORD(2,2), &WSAData)) != 0 ) {
|
|
myprintf("WSAStartup() failed: %d", nRet);
|
|
return(1);
|
|
}
|
|
|
|
if(WSA_INVALID_EVENT == (g_hCleanupEvent[0] = WSACreateEvent()))
|
|
{
|
|
myprintf("WSACreateEvent() failed: %d\n", WSAGetLastError());
|
|
WSACleanup();
|
|
return(1);
|
|
}
|
|
|
|
//
|
|
// be able to gracefully handle CTRL-C and close handles
|
|
//
|
|
if( !SetConsoleCtrlHandler (CtrlHandler, TRUE) ) {
|
|
myprintf("SetConsoleCtrlHandler() failed: %d\n", GetLastError());
|
|
if( g_hCleanupEvent[0] != WSA_INVALID_EVENT ) {
|
|
WSACloseEvent(g_hCleanupEvent[0]);
|
|
g_hCleanupEvent[0] = WSA_INVALID_EVENT;
|
|
}
|
|
WSACleanup();
|
|
return(1);
|
|
}
|
|
|
|
//
|
|
// spawn the threads
|
|
//
|
|
for( i = 0; i < g_Options.nTotalThreads && !bInitError; i++ ) {
|
|
|
|
//
|
|
// if CTRL-C is pressed before all the sockets have connected, closure of
|
|
// the program could take a little while, especially if the server is
|
|
// down and we have to wait for connect to fail. Checking for this
|
|
// global flag allows us to shortcircuit that.
|
|
//
|
|
if( g_bEndClient )
|
|
break;
|
|
else if( CreateConnectedSocket(i) ) {
|
|
|
|
//
|
|
// a unique memory location needs to be passed into each thread,
|
|
// otherwise the value would change by the time all the threads
|
|
// get a chance to run.
|
|
//
|
|
nThreadNum[i] = i;
|
|
g_ThreadInfo.hThread[i] = CreateThread(NULL, 0, EchoThread, (LPVOID)&nThreadNum[i], 0, &dwThreadId);
|
|
if( g_ThreadInfo.hThread[i] == NULL ) {
|
|
myprintf("CreateThread(%d) failed: %d\n", i, GetLastError());
|
|
bInitError = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !bInitError ) {
|
|
|
|
//
|
|
// wait for the threads to exit
|
|
//
|
|
dwRet = WaitForMultipleObjects(g_Options.nTotalThreads, g_ThreadInfo.hThread, TRUE, INFINITE);
|
|
if( dwRet == WAIT_FAILED )
|
|
myprintf("WaitForMultipleObject(): %d\n", GetLastError());
|
|
}
|
|
|
|
if( !GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0) ) {
|
|
myprintf("GenerateConsoleCtrlEvent() failed: %d\n", GetLastError());
|
|
};
|
|
|
|
if( WSAWaitForMultipleEvents(1, g_hCleanupEvent, TRUE, WSA_INFINITE, FALSE) == WSA_WAIT_FAILED ) {
|
|
myprintf("WSAWaitForMultipleEvents() failed: %d\n", WSAGetLastError());
|
|
};
|
|
|
|
if( g_hCleanupEvent[0] != WSA_INVALID_EVENT ) {
|
|
WSACloseEvent(g_hCleanupEvent[0]);
|
|
g_hCleanupEvent[0] = WSA_INVALID_EVENT;
|
|
}
|
|
|
|
WSACleanup();
|
|
|
|
//
|
|
// Restores default processing of CTRL signals.
|
|
//
|
|
SetConsoleCtrlHandler(CtrlHandler, FALSE);
|
|
SetConsoleCtrlHandler(NULL, FALSE);
|
|
|
|
return(0);
|
|
}
|
|
|
|
//
|
|
// Abstract:
|
|
// This is the thread that continually sends and receives a specific size
|
|
// buffer to the server. Upon receipt of the echo from the server, a
|
|
// simple check is performed to check the integrity of the transfer.
|
|
//
|
|
static DWORD WINAPI EchoThread(LPVOID lpParameter) {
|
|
|
|
char *inbuf = NULL;
|
|
char *outbuf = NULL;
|
|
int *pArg = (int *)lpParameter;
|
|
int nThreadNum = *pArg;
|
|
|
|
myprintf("Starting thread %d\n", nThreadNum);
|
|
|
|
inbuf = (char *)xmalloc(g_Options.nBufSize);
|
|
outbuf = (char *)xmalloc(g_Options.nBufSize);
|
|
|
|
if( (inbuf) && (outbuf)) {
|
|
|
|
//
|
|
// NOTE data possible data loss with INT conversion to BYTE
|
|
//
|
|
FillMemory(outbuf, g_Options.nBufSize, (BYTE)nThreadNum);
|
|
|
|
while( TRUE ) {
|
|
|
|
//
|
|
// just continually send and wait for the server to echo the data
|
|
// back. Just do a simple minded comparison.
|
|
//
|
|
if( SendBuffer(nThreadNum, outbuf) &&
|
|
RecvBuffer(nThreadNum, inbuf) ) {
|
|
if( (inbuf[0] == outbuf[0]) &&
|
|
(inbuf[g_Options.nBufSize-1] == outbuf[g_Options.nBufSize-1]) ) {
|
|
if( g_Options.bVerbose )
|
|
myprintf("ack(%d)\n", nThreadNum);
|
|
} else {
|
|
myprintf("nak(%d) in[0]=%d, out[0]=%d in[%d]=%d out[%d]%d\n",
|
|
nThreadNum,
|
|
inbuf[0], outbuf[0],
|
|
g_Options.nBufSize-1, inbuf[g_Options.nBufSize-1],
|
|
g_Options.nBufSize-1, outbuf[g_Options.nBufSize-1]);
|
|
break;
|
|
}
|
|
} else
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( inbuf )
|
|
xfree(inbuf);
|
|
if( outbuf )
|
|
xfree(outbuf);
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
//
|
|
// Abstract:
|
|
// Create a socket and connect to the server process.
|
|
//
|
|
static BOOL CreateConnectedSocket(int nThreadNum) {
|
|
|
|
BOOL bRet = TRUE;
|
|
int nRet = 0;
|
|
struct addrinfo hints = {0};
|
|
struct addrinfo *addr_srv = NULL;
|
|
|
|
//
|
|
// Resolve the interface
|
|
//
|
|
hints.ai_flags = 0;
|
|
hints.ai_family = AF_INET;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_protocol = IPPROTO_TCP;
|
|
|
|
if( getaddrinfo(g_Options.szHostname, g_Options.port, &hints, &addr_srv) != 0 ) {
|
|
myprintf("getaddrinfo() failed with error %d\n", WSAGetLastError());
|
|
bRet = FALSE;
|
|
}
|
|
|
|
if( addr_srv == NULL ) {
|
|
myprintf("getaddrinfo() failed to resolve/convert the interface\n");
|
|
bRet = FALSE;
|
|
} else {
|
|
g_ThreadInfo.sd[nThreadNum] = socket(addr_srv->ai_family, addr_srv->ai_socktype, addr_srv->ai_protocol);
|
|
if( g_ThreadInfo.sd[nThreadNum] == INVALID_SOCKET ) {
|
|
myprintf("socket() failed: %d\n", WSAGetLastError());
|
|
bRet = FALSE;
|
|
}
|
|
}
|
|
|
|
if( bRet != FALSE ) {
|
|
nRet = connect(g_ThreadInfo.sd[nThreadNum], addr_srv->ai_addr, (int) addr_srv->ai_addrlen);
|
|
if( nRet == SOCKET_ERROR ) {
|
|
myprintf("connect(thread %d) failed: %d\n", nThreadNum, WSAGetLastError());
|
|
bRet = FALSE;
|
|
} else
|
|
myprintf("connected(thread %d)\n", nThreadNum);
|
|
|
|
freeaddrinfo(addr_srv);
|
|
}
|
|
|
|
return(bRet);
|
|
}
|
|
|
|
//
|
|
// Abstract:
|
|
// Send a buffer - keep send'ing until the requested amount of
|
|
// data has been sent or the socket has been closed or error.
|
|
//
|
|
static BOOL SendBuffer(int nThreadNum, char *outbuf) {
|
|
|
|
BOOL bRet = TRUE;
|
|
char *bufp = outbuf;
|
|
int nTotalSend = 0;
|
|
int nSend = 0;
|
|
|
|
while( nTotalSend < g_Options.nBufSize ) {
|
|
nSend = send(g_ThreadInfo.sd[nThreadNum], bufp, g_Options.nBufSize - nTotalSend, 0);
|
|
if( nSend == SOCKET_ERROR ) {
|
|
myprintf("send(thread=%d) failed: %d\n", nThreadNum, WSAGetLastError());
|
|
bRet = FALSE;
|
|
break;
|
|
} else if( nSend == 0 ) {
|
|
myprintf("connection closed\n");
|
|
bRet = FALSE;
|
|
break;
|
|
} else {
|
|
nTotalSend += nSend;
|
|
bufp += nSend;
|
|
}
|
|
}
|
|
|
|
return(bRet);
|
|
}
|
|
|
|
//
|
|
// Abstract:
|
|
// Receive a buffer - keep recv'ing until the requested amount of
|
|
// data has been received or the socket has been closed or error.
|
|
//
|
|
static BOOL RecvBuffer(int nThreadNum, char *inbuf) {
|
|
|
|
BOOL bRet = TRUE;
|
|
char *bufp = inbuf;
|
|
int nTotalRecv = 0;
|
|
int nRecv = 0;
|
|
|
|
while( nTotalRecv < g_Options.nBufSize ) {
|
|
nRecv = recv(g_ThreadInfo.sd[nThreadNum], bufp, g_Options.nBufSize - nTotalRecv, 0);
|
|
if( nRecv == SOCKET_ERROR ) {
|
|
myprintf("recv(thread=%d) failed: %d\n", nThreadNum, WSAGetLastError());
|
|
bRet = FALSE;
|
|
break;
|
|
} else if( nRecv == 0 ) {
|
|
myprintf("connection closed\n");
|
|
bRet = FALSE;
|
|
break;
|
|
} else {
|
|
nTotalRecv += nRecv;
|
|
bufp += nRecv;
|
|
}
|
|
}
|
|
|
|
return(bRet);
|
|
}
|
|
|
|
//
|
|
// Abstract:
|
|
// Verify options passed in and set options structure accordingly.
|
|
//
|
|
static BOOL ValidOptions(char *argv[], int argc) {
|
|
|
|
g_Options = default_options;
|
|
HRESULT hRet;
|
|
|
|
for( int i=1; i<argc; i++ ) {
|
|
if( (argv[i][0] == '-') || (argv[i][0] == '/') ) {
|
|
switch( tolower(argv[i][1]) ) {
|
|
case 'b' :
|
|
if( lstrlen(argv[i]) > 3 )
|
|
g_Options.nBufSize = 1024*atoi(&argv[i][3]);
|
|
break;
|
|
|
|
case 'e' :
|
|
if( lstrlen(argv[i]) > 3 )
|
|
g_Options.port = &argv[i][3];
|
|
break;
|
|
|
|
case 'n' :
|
|
if( lstrlen(argv[i]) > 3 )
|
|
{
|
|
hRet = StringCbCopyN(g_Options.szHostname,64,&argv[i][3],64);
|
|
}
|
|
break;
|
|
|
|
case 't' :
|
|
if( lstrlen(argv[i]) > 3 )
|
|
g_Options.nTotalThreads = min(MAXTHREADS, atoi(&argv[i][3]));
|
|
break;
|
|
|
|
case 'v' :
|
|
g_Options.bVerbose = TRUE;
|
|
break;
|
|
|
|
case '?' :
|
|
Usage(argv[0], &default_options);
|
|
return(FALSE);
|
|
break;
|
|
|
|
default:
|
|
myprintf(" unknown options flag %s\n", argv[i]);
|
|
Usage(argv[0], &default_options);
|
|
return(FALSE);
|
|
break;
|
|
}
|
|
} else {
|
|
myprintf(" unknown option %s\n", argv[i]);
|
|
Usage(argv[0], &default_options);
|
|
return(FALSE);
|
|
}
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
//
|
|
// Abstract:
|
|
// Print out usage table for the program
|
|
//
|
|
static VOID Usage(char *szProgramname, OPTIONS *pOptions) {
|
|
|
|
myprintf("usage:\n%s [-b:#] [-e:#] [-n:host] [-t:#] [-v]\n",
|
|
szProgramname);
|
|
myprintf("%s -?\n", szProgramname);
|
|
myprintf(" -?\t\tDisplay this help\n");
|
|
myprintf(" -b:bufsize\tSize of send/recv buffer; in 1K increments (Def:%d)\n",
|
|
pOptions->nBufSize);
|
|
myprintf(" -e:port\tEndpoint number (port) to use (Def:%d)\n",
|
|
pOptions->port);
|
|
myprintf(" -n:host\tAct as the client and connect to 'host' (Def:%s)\n",
|
|
pOptions->szHostname);
|
|
myprintf(" -t:#\tNumber of threads to use\n");
|
|
myprintf(" -v\t\tVerbose, print an ack when echo received and verified\n");
|
|
return;
|
|
}
|
|
|
|
static BOOL WINAPI CtrlHandler (DWORD dwEvent) {
|
|
|
|
int i = 0;
|
|
DWORD dwRet = 0;
|
|
|
|
switch( dwEvent ) {
|
|
case CTRL_C_EVENT:
|
|
case CTRL_BREAK_EVENT:
|
|
case CTRL_LOGOFF_EVENT:
|
|
case CTRL_SHUTDOWN_EVENT:
|
|
case CTRL_CLOSE_EVENT:
|
|
|
|
myprintf("Closing handles and sockets\n");
|
|
|
|
//
|
|
// Temporarily disables processing of CTRL_C_EVENT signal.
|
|
//
|
|
SetConsoleCtrlHandler(NULL, TRUE);
|
|
|
|
g_bEndClient = TRUE;
|
|
|
|
for( i = 0; i < g_Options.nTotalThreads; i++ ) {
|
|
if( g_ThreadInfo.sd[i] != INVALID_SOCKET ) {
|
|
|
|
//
|
|
// force the subsequent closesocket to be abortative.
|
|
//
|
|
LINGER lingerStruct;
|
|
|
|
lingerStruct.l_onoff = 1;
|
|
lingerStruct.l_linger = 0;
|
|
setsockopt(g_ThreadInfo.sd[i], SOL_SOCKET, SO_LINGER,
|
|
(char *)&lingerStruct, sizeof(lingerStruct));
|
|
closesocket(g_ThreadInfo.sd[i]);
|
|
g_ThreadInfo.sd[i] = INVALID_SOCKET;
|
|
|
|
if( g_ThreadInfo.hThread[i] != INVALID_HANDLE_VALUE ) {
|
|
|
|
dwRet = WaitForSingleObject(g_ThreadInfo.hThread[i], INFINITE);
|
|
if( dwRet == WAIT_FAILED )
|
|
myprintf("WaitForSingleObject(): %d\n", GetLastError());
|
|
|
|
CloseHandle(g_ThreadInfo.hThread[i]);
|
|
g_ThreadInfo.hThread[i] = INVALID_HANDLE_VALUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
//
|
|
// unknown type--better pass it on.
|
|
//
|
|
return(FALSE);
|
|
}
|
|
|
|
WSASetEvent(g_hCleanupEvent[0]);
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
static int myprintf (const char *lpFormat, ... ) {
|
|
|
|
int nLen = 0;
|
|
int nRet = 0;
|
|
char cBuffer[512] = {'\0'};
|
|
va_list arglist ;
|
|
HANDLE hOut = NULL;
|
|
HRESULT hRet;
|
|
|
|
ZeroMemory(cBuffer, sizeof(cBuffer));
|
|
|
|
va_start(arglist, lpFormat);
|
|
|
|
nLen = lstrlen( lpFormat ) ;
|
|
|
|
hRet = StringCchVPrintf(cBuffer,512,lpFormat,arglist);
|
|
|
|
if( nRet >= nLen || GetLastError() == 0 ) {
|
|
hOut = GetStdHandle(STD_OUTPUT_HANDLE) ;
|
|
if( hOut != INVALID_HANDLE_VALUE )
|
|
WriteConsole( hOut, cBuffer, lstrlen(cBuffer), (LPDWORD)&nLen, NULL ) ;
|
|
}
|
|
|
|
return nLen ;
|
|
}
|
|
|
|
|
|
|