|
|
//+-------------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (C) Microsoft Corporation, 1994 - 1999
//
// File: client.c
//
//--------------------------------------------------------------------------
/////////////////////////////////////////////////////////////////////////
//
// Filename: client.c
//
// Description: This file contains the source code for IPC performance.
// This module is written using win32 API calls, and will
// generate a console server app.
//
// Authors: Scott Holden (Translator from NT API to win32 API)
// Mahesh Keni (Mahesh wrote this application using mostly
// NT native API calls)
//
/////////////////////////////////////////////////////////////////////////
#include "rawcom.h"
#include "client.h"
/************************************************************************/ // Global variables
/************************************************************************/
struct client Clients[MAXCLIENTS]; HANDLE Threads[MAXCLIENTS]; HANDLE EventHandle; PCHAR ServerName=NULL; USHORT ThreadError=0; BOOLEAN Failure = FALSE; USHORT NClients = 1; // number of clients
USHORT MachineNumber = 1; // This client Number
USHORT IPCType = NP; // Ipc Type to be used
ULONG SendSize = 32; ULONG NumSends = 1; ULONG RecvSize = 32; ULONG NumRecvs = 1; ULONG Iterations = 100; // number of Iterations
DWORD Timeout; CHAR Xport[9]; // Xport type name
CHAR TestCmd = 'P';
// function pointers for redirecting calls according to Xport types
NTSTATUS (* IPC_Initialize)(); NTSTATUS (* IPC_PerClientInit)(); NTSTATUS (* IPC_Connect_To_Server)(); NTSTATUS (* IPC_Disconnect_From_Server)(); NTSTATUS (* IPC_Cleanup)(); NTSTATUS (* IPC_Allocate_Memory)(); NTSTATUS (* IPC_DoHandshake)(); NTSTATUS (* IPC_ReadFromIPC)(); NTSTATUS (* IPC_WriteToIPC)(); NTSTATUS (* IPC_XactIO)(); NTSTATUS (* IPC_Deallocate_Memory)(); NTSTATUS (* IPC_ThreadCleanUp)();
// Later all of this should move under an union
// Globals for NamedPipe
/*
OBJECT_ATTRIBUTES objectAttributes; UNICODE_STRING unicodePipeName; */ LPCSTR pipeName; ULONG Quotas = 32768; // read/write quota
ULONG PipeType = PIPE_TYPE_MESSAGE; // pipe type
ULONG PipeMode = PIPE_READMODE_MESSAGE; // read mode
ULONG BlockorNot = PIPE_NOWAIT; // non blocking
// Globals for NetBIOS
USHORT LanaCount = 1; USHORT LanaBase = 0; UCHAR NameNumber = 1; CHAR LocalName[NCBNAMSZ]; CHAR RemoteName[NCBNAMSZ];
// GLobals for Sockets
PCHAR HostName; int AddrFly;
// For XNS socket support
CHAR Remote_Net_Number[4]; // NetNumber
CHAR Remote_Node_Number[6]; // Card address
/************************************************************************/ void __cdecl main ( IN USHORT argc, IN PSZ argv[], IN PSZ envp[] )
{ NTSTATUS mstatus; // DWORD Timeout;
USHORT Cindex = 0; // client index
OutputDebugString("Raw network client has started!\n");
// initialize the Client & Server name
mstatus = Parse_Cmd_Line(argc, argv); if (!NT_SUCCESS(mstatus)) { exit(1); }
// Based on the xport type setup all the function pointers
Setup_Function_Pointers();
// do IPC dependent Initializing
mstatus = IPC_Initialize(NClients, ServerName, CLI); if (!NT_SUCCESS(mstatus)) { exit(1); }
// Now create worker threads to execute test scenarios
// Create an event to synchronize all the client threads
EventHandle = CreateEvent(NULL, // EventobjectAttributes,
TRUE, // manual reset event
FALSE, // initial state of the event is non-signalled
NULL); // no name given to the event
if (!EventHandle) { printf ("Failed to create an event err=%lx\n",mstatus); Cleanup(); exit(1); }
printf("Creating %s Client threads..", Xport);
for (Cindex = 0; Cindex < NClients; Cindex++) { // do appropriate per client initialization
mstatus = IPC_PerClientInit(Cindex, CLI); // set up parameters for other client threads
Clients[Cindex].c_client_num = Cindex; // client number
Clients[Cindex].c_reqbuf.Iterations = Iterations; Clients[Cindex].c_reqbuf.SendSize = SendSize; Clients[Cindex].c_reqbuf.RecvSize = RecvSize; Clients[Cindex].c_reqbuf.NumSends = NumSends; Clients[Cindex].c_reqbuf.NumRecvs = NumRecvs; Clients[Cindex].c_reqbuf.TestCmd = TestCmd; Clients[Cindex].c_reqbuf.ClientNumber = Cindex;
// use win32 API instead of NT native so that NetBIOS works fine
Clients[Cindex].c_hThHandle = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)&CliService, (PUSHORT)&(Clients[Cindex].c_client_num), 0, (LPDWORD)&(Clients[Cindex].c_ThClientID)); Threads[Cindex] = Clients[Cindex].c_hThHandle; printf("%d..",Cindex+1); } // printf("\n");
// First delay the main thread to let worker threads to get ready
mstatus = Delay_Trigger_Wait();
if (!NT_SUCCESS(mstatus)) { printf ("Failed in Delay_Trigger_Wait: err=%lx\n",mstatus); Cleanup(); }
// If any error then interprete it otherwise display results
if (ThreadError) { printf("Client Error=%d\n",ThreadError); } else { // display results for all the clients
Display_Results(); }
// now do the cleanup. i.e. clear all memory and close all handles
IPC_Cleanup();
Cleanup(); exit(0); } // main
/************************************************************************/ NTSTATUS Delay_Trigger_Wait(VOID) { NTSTATUS dstatus; DWORD Timeout = 1000;
dstatus = SleepEx( Timeout, // 10 ms delay
TRUE ); // alertable
if (!NT_SUCCESS(dstatus)) { printf ("Failed on Delayed execution err=%lx\n",dstatus); return(dstatus); }
dstatus = PulseEvent(EventHandle);
if (!NT_SUCCESS(dstatus)) { printf ("Failed to Pulse an event err=%lx\n",dstatus); return(dstatus); } // then signals all the waiting threads to resume I/O to namedpipe
// by sending a pulse event
dstatus = Wait_For_Client_Threads(); if (!NT_SUCCESS(dstatus)) { printf ("Failed on wait err=%lx\n",dstatus); return(dstatus); }
// printf("wait done. now do the cleanup work\n");
return(dstatus); } /************************************************************************/ VOID CliService( // provide Client service
IN PUSHORT pTindex )
{
NTSTATUS tstatus; USHORT tCindex; //IO_STATUS_BLOCK ioStatusBlock;
USHORT error = 0; //UCHAR Retcode;
ULONG Iterations; ULONG SendLen; ULONG RecvLen;
DWORD StartTime; DWORD StopTime; DWORD Duration; BOOLEAN First = FALSE;
tCindex = *pTindex; ThreadError = 0; // No error so far
Failure = FALSE;
MyDbgPrint("CLI: Connecting to Server\n"); tstatus = IPC_Connect_To_Server(tCindex); // First connect to the server
ThreadError = 1; FAIL_CHECK_EXIT(PERFCLI, "Connect to Srv", tstatus);
// now open the sync event and wait on it till set by the main thread
tstatus = WaitForSingleObjectEx(EventHandle, INFINITE, TRUE);
ThreadError = 2; FAIL_CHECK_EXIT(PERFCLI, "Wait for Event", tstatus);
MyDbgPrint("CLI: Doing Handshake \n");
// Do the handshake before the actual test run. This will send a request
// packet to the server with test details
tstatus = IPC_DoHandshake(tCindex, CLI);
ThreadError = 3; FAIL_CHECK_EXIT(PERFCLI, "Do Handshake ", tstatus);
// Allocate memory required for recv and send buffers
tstatus = IPC_Allocate_Memory(tCindex);
ThreadError = 4; FAIL_CHECK_EXIT(PERFCLI, "Allocate Memory ", tstatus);
// Now we are ready to run all the tests
Iterations = Clients[tCindex].c_reqbuf.Iterations;
StartTime = GetCurrentTime(); while (Iterations--) {
if (Clients[tCindex].c_reqbuf.TestCmd == 'P') { // Client first sends X messages and Receives Y messages.
tstatus = IPC_WriteToIPC(tCindex, &SendLen, CLI);
ThreadError = 5; FAIL_CHECK(PERFCLI, "Write to IPC", tstatus);
// Check for send length here
tstatus = IPC_ReadFromIPC(tCindex, &RecvLen, CLI);
ThreadError = 6; FAIL_CHECK(PERFCLI, "Receive from IPC", tstatus);
// Check for Receive length and data integrity here
} else { // for 'U' or 'T' tests do transaction type I/O
// Note that First is not used by the client
tstatus = IPC_XactIO(tCindex, &SendLen, &RecvLen, CLI,First);
ThreadError = 65; FAIL_CHECK(PERFCLI, "Xact from IPC", tstatus); }
}
StopTime = GetCurrentTime(); Duration = StopTime - StartTime; Clients[tCindex].c_Duration = Duration; // in msecs
// Deallocate all the memory
tstatus = IPC_Deallocate_Memory(tCindex);
if (!ThreadError) {ThreadError = 7;} FAIL_CHECK_EXIT(PERFCLI, "Deallocate Memory ", tstatus);
// Disconnect from server
tstatus = IPC_Disconnect_From_Server(tCindex);
if (!ThreadError) { ThreadError = 8; } FAIL_CHECK_EXIT(PERFCLI, "Disconnect from Server ", tstatus);
// Clear all flags so that we can print results
if (!Failure) { ThreadError = 0; } // based on error return status
TerminateThread(GetCurrentThread(), STATUS_SUCCESS); }
/************************************************************************/ /*++
This routine is responsible for displaying results for all the client thread. It displays both per client and total throughput. --*/
VOID Display_Results(VOID) {
USHORT Cindex = 0; ULONG TotTps = 0L; ULONG TotSendThrpt = 0L; ULONG TotRecvThrpt = 0L; ULONG CliThrput = 0L; ULONG Remainder = 0L; ULONG BytesSent = 0L; ULONG BytesRcvd = 0L;
BytesSent = (Clients[Cindex].c_reqbuf.SendSize) * (Clients[Cindex].c_reqbuf.NumSends) * (Clients[Cindex].c_reqbuf.Iterations);
BytesRcvd = (Clients[Cindex].c_reqbuf.RecvSize) * (Clients[Cindex].c_reqbuf.NumRecvs) * (Clients[Cindex].c_reqbuf.Iterations);
printf("Total: Trans: %ld Send: %lu Bytes Receive: %lu Bytes\n", (Clients[Cindex].c_reqbuf.NumSends * Clients[Cindex].c_reqbuf.Iterations), BytesSent, BytesRcvd);
// Display all the results
for (Cindex = 0; Cindex < NClients; Cindex++) {
if (!(Clients[Cindex].c_Duration)) { Clients[Cindex].c_Duration++; }
// First put the TPS number
// Get total TPS
CliThrput = (Clients[Cindex].c_reqbuf.NumSends * 1000) * (Clients[Cindex].c_reqbuf.Iterations);
CliThrput /= Clients[Cindex].c_Duration;
TotTps += CliThrput;
printf("Cli:%d Dura: %ld ms Throughput: %ld TPS ", Cindex, Clients[Cindex].c_Duration, CliThrput);
// Now put the BPS number
// First get Send BPS numbers
CliThrput= BytesSent / (Clients[Cindex].c_Duration);
// now calculate the remainder and get first three digits
Remainder = BytesSent - (Clients[Cindex].c_Duration * CliThrput); Remainder = (Remainder*1000)/Clients[Cindex].c_Duration; CliThrput = (CliThrput * 1000) + Remainder; // duration was in msec
TotSendThrpt += CliThrput;
printf(" Send: %ld BPS ", CliThrput);
// First get Receive BPS numbers
CliThrput = BytesRcvd / (Clients[Cindex].c_Duration);
// now calculate the remainder and get first three digits
Remainder = BytesRcvd - (Clients[Cindex].c_Duration * CliThrput); Remainder = (Remainder*1000)/Clients[Cindex].c_Duration; CliThrput = (CliThrput * 1000) + Remainder; // duration was in msec
TotRecvThrpt += CliThrput;
printf(" RecvThrpt: %ld BPS; \n", CliThrput ); } printf("--------------------------------------------------------\n"); printf("Total Throughput: %ld TPS ",TotTps); printf(" Send-BPS: %ld BPS ",TotSendThrpt); printf(" Recv-BPS: %ld BPS\n",TotRecvThrpt);
} /************************************************************************/
NTSTATUS Parse_Cmd_Line(USHORT argc, CHAR *argv[]) {
USHORT i; CHAR *s; NTSTATUS pstatus = 0L; BOOLEAN doingok = TRUE;
if (argc > 12) { printf("Too many arguments \n"); pstatus = -1L; }
strncpy(Xport, NamePipe, 8);
for (i=1; (doingok) && (i< argc); i++) {
s = argv[i];
if ((*s == '/') && ((*(s+2) == ':') || (*(s+3) == ':') )) { s++;
switch(*s) { case 'a' : // Net number for XNS:SPX
case 'A' : RtlCopyMemory( Remote_Net_Number, get_network_number((PCHAR)s+2), 4);
if (Remote_Net_Number[0] == 'X') { printf("incorrect net number: "); printf(" e.g. /a:11223344 (8 hex) \n");
Usage(argv[0]); pstatus = -1L; } break;
case 'b' : case 'B' : LanaBase = (USHORT)atoi(s+2); break;
case 'c' : // number of clients
case 'C' : NClients = (USHORT)atoi(s+2); break;
case 'l' : case 'L' : LanaCount = (USHORT)atoi(s+2); break;
case 'm' : case 'M' : MachineNumber = (USHORT)atoi(s+2); break;
case 'i' : // iterations
case 'I' : Iterations = (USHORT)atoi(s+2); break;
case 'n' : // number of Sends/Recvs
case 'N' : switch(*(s+1)) { case 'r': case 'R': NumRecvs = (USHORT)atoi(s+3); break;
case 's': case 'S': NumSends = (USHORT)atoi(s+3); break;
default: doingok = FALSE; } break;
case 'p' : // NamedPipe mode
case 'P' : switch(*(s+2)) { case 'm': case 'M': PipeType = PIPE_TYPE_MESSAGE; PipeMode = PIPE_READMODE_MESSAGE; break;
case 's': case 'S': PipeType = PIPE_TYPE_BYTE; PipeMode = PIPE_READMODE_BYTE; break;
default: doingok = FALSE; } break;
case 'r' : // Send size
case 'R' : RecvSize = (USHORT)atoi(s+2); break;
case 's' : // Send size
case 'S' : SendSize = (USHORT)atoi(s+2); break;
case 't' : // Test Command
case 'T' : TestCmd =(UCHAR) *(s+2); switch(TestCmd){ case 't': TestCmd = 'T'; break; case 'u': case 'U': TestCmd = 'U'; break; case 'P': case 'T': break;
default : printf("incorrect test command\n"); Usage(argv[0]); pstatus = -1L; } break;
case 'h' : // Server Name or host IP address
case 'H' : ServerName = (PCHAR)malloc(SRVNAME_LEN); strcpy(ServerName,(PCHAR)(s+2));
// if XNS:SPX or IPX then get Node Address
if ((IPCType == SCXNS)|| (IPCType == SCIPX)){ RtlCopyMemory( Remote_Node_Number, get_node_number(ServerName), 6);
if (Remote_Node_Number[0] == 'X') { printf("incorrect node number: "); printf("e.g. /h:112233445566 \n"); Usage(argv[0]); pstatus = -1L; } } break;
case 'x' : // Xport Type
case 'X' : strncpy(Xport, (PUCHAR) (s+2), 8); if (!_stricmp(Xport,NamePipe)) { IPCType = NP; break; } if (!_stricmp(Xport,NetBIOS)) { IPCType = NB; break; } if (!_stricmp(Xport,SocketXNS)) { IPCType = SCXNS; AddrFly = AF_NS; break; } if (!_stricmp(Xport,SocketTCP)) { IPCType = SCTCP; AddrFly = AF_INET; break; } if (!_stricmp(Xport,UDP)) { IPCType = SCUDP; AddrFly = AF_INET; break; } if (!_stricmp(Xport,IPX)) { IPCType = SCIPX; AddrFly = AF_NS; break; } if (!_stricmp(Xport,DGNetBIOS)) { IPCType = DGNB; break; } // bad choice of Xport
doingok = FALSE; break;
default : doingok = FALSE; } } else { doingok = FALSE; } }
if (!doingok) { Usage(argv[0]); pstatus = -1L; } else { // if successful then
if ((IPCType == SCXNS) || (IPCType == SCIPX)) { // make server name a 10 byte address
RtlCopyMemory(ServerName,Remote_Node_Number,6); RtlCopyMemory(ServerName+6,Remote_Net_Number,4); }
if (((IPCType != NB) && (IPCType != NP) && (IPCType != DGNB)) && (ServerName == NULL)) { printf("Please enter Server Address \n"); pstatus = -1L; } }
return(pstatus); }
/************************************************************************/ VOID Usage(char * PrgName) {
fprintf(stderr, "Usage: %s [/c: ] [/h:] [/s:] [/r:] [/b:] [/l:]\n",PrgName); fprintf(stderr, " Opt Default Defines\n"); fprintf(stderr, " === ======= =======\n"); fprintf(stderr, " /c: 1 Number of clients\n"); fprintf(stderr, " /h: NULL SrvName/HostIPaddr.\n"); fprintf(stderr, " /Node Number for XNS\n"); fprintf(stderr, " /I: 1000 Number of Iterations\n"); fprintf(stderr, " /ns: 1 Number of Sends\n"); fprintf(stderr, " /nr: 1 Number of Receives\n"); fprintf(stderr, " /r: 32 Receive size\n"); fprintf(stderr, " /s: 32 Send size\n"); fprintf(stderr, " /t: NULL Special Test cmd,U,T\n"); fprintf(stderr, " /x: Nmp Xport(IPC)type\n"); fprintf(stderr, " Nmp/NetB/SockTCP/\n"); fprintf(stderr, " SockXNS/UDP/IPX/DGNetB\n"); fprintf(stderr, " For NNamedPipe: \n"); fprintf(stderr, " /p: m Nmp : Pipe Type m/s\n"); fprintf(stderr, " For NetBIOS: \n"); fprintf(stderr, " /b: 0 NetB: lana base\n"); fprintf(stderr, " /l: 1 NetB: lana count\n"); fprintf(stderr, " /m: 1 machine number\n"); fprintf(stderr, " For XNS: \n"); fprintf(stderr, " /a: 1 Net Number for XNS\n"); }
/************************************************************************/
/*++
This routine sets up all the function pointers based on Xport type --*/
VOID Setup_Function_Pointers() {
// I could do real OOP here and just set up one pointer to all functions
// based on Xport type take the action
switch(IPCType) { case NP: IPC_Initialize = NMP_Initialize; IPC_PerClientInit = NMP_PerClientInit; IPC_Connect_To_Server = NMP_Connect_To_Server; IPC_Disconnect_From_Server = NMP_Disconnect_From_Server; IPC_Cleanup = NMP_Cleanup; IPC_Allocate_Memory = NMP_Allocate_Memory; IPC_DoHandshake = NMP_DoHandshake; IPC_ReadFromIPC = NMP_ReadFromIPC; IPC_WriteToIPC = NMP_WriteToIPC; IPC_XactIO = NMP_XactIO; IPC_Deallocate_Memory = NMP_Deallocate_Memory; IPC_ThreadCleanUp = NMP_ThreadCleanUp; break; case NB: IPC_Initialize = NB_Initialize; IPC_PerClientInit = NB_PerClientInit; IPC_Cleanup = NB_Cleanup; IPC_Connect_To_Server = NB_Connect_To_Server; IPC_Disconnect_From_Server = NB_Disconnect_From_Server; IPC_Allocate_Memory = NB_Allocate_Memory; IPC_DoHandshake = NB_DoHandshake; IPC_ReadFromIPC = NB_ReadFromIPC; IPC_WriteToIPC = NB_WriteToIPC; IPC_XactIO = NB_XactIO; IPC_Deallocate_Memory = NB_Deallocate_Memory; IPC_ThreadCleanUp = NB_ThreadCleanUp; break;
case SCTCP: IPC_Initialize = SCTCP_Initialize; IPC_PerClientInit = SCTCP_PerClientInit; IPC_Cleanup = SCTCP_Cleanup; IPC_Connect_To_Server = SCTCP_Connect_To_Server; IPC_Disconnect_From_Server = SCTCP_Disconnect_From_Server; IPC_Allocate_Memory = SCTCP_Allocate_Memory; IPC_DoHandshake = SCTCP_DoHandshake; IPC_ReadFromIPC = SCTCP_ReadFromIPC; IPC_WriteToIPC = SCTCP_WriteToIPC; IPC_XactIO = SCTCP_XactIO; IPC_Deallocate_Memory = SCTCP_Deallocate_Memory; IPC_ThreadCleanUp = SCTCP_ThreadCleanUp; break;
case SCXNS: IPC_Initialize = SCXNS_Initialize; IPC_PerClientInit = SCXNS_PerClientInit; IPC_Connect_To_Server = SCXNS_Connect_To_Server; IPC_Disconnect_From_Server = SCXNS_Disconnect_From_Server; IPC_Cleanup = SCXNS_Cleanup; IPC_Allocate_Memory = SCXNS_Allocate_Memory; IPC_DoHandshake = SCXNS_DoHandshake; IPC_ReadFromIPC = SCXNS_ReadFromIPC; IPC_WriteToIPC = SCXNS_WriteToIPC; IPC_XactIO = SCXNS_XactIO; IPC_Deallocate_Memory = SCXNS_Deallocate_Memory; IPC_ThreadCleanUp = SCXNS_ThreadCleanUp; break;
case SCUDP: IPC_Initialize = SCUDP_Initialize; IPC_PerClientInit = SCUDP_PerClientInit; IPC_Connect_To_Server = SCUDP_Connect_To_Server; IPC_Disconnect_From_Server = SCUDP_Disconnect_From_Server; IPC_Cleanup = SCUDP_Cleanup; IPC_Allocate_Memory = SCUDP_Allocate_Memory; IPC_DoHandshake = SCUDP_DoHandshake; IPC_ReadFromIPC = SCUDP_ReadFromIPC; IPC_WriteToIPC = SCUDP_WriteToIPC; IPC_Deallocate_Memory = SCUDP_Deallocate_Memory; IPC_ThreadCleanUp = SCUDP_ThreadCleanUp; break;
case SCIPX: IPC_Initialize = SCIPX_Initialize; IPC_PerClientInit = SCIPX_PerClientInit; IPC_PerClientInit = SCIPX_PerClientInit; IPC_Connect_To_Server = SCIPX_Connect_To_Server; IPC_Disconnect_From_Server = SCIPX_Disconnect_From_Server; IPC_Cleanup = SCIPX_Cleanup; IPC_Allocate_Memory = SCIPX_Allocate_Memory; IPC_DoHandshake = SCIPX_DoHandshake; IPC_ReadFromIPC = SCIPX_ReadFromIPC; IPC_WriteToIPC = SCIPX_WriteToIPC; IPC_Deallocate_Memory = SCIPX_Deallocate_Memory; IPC_ThreadCleanUp = SCIPX_ThreadCleanUp; break;
case DGNB: IPC_Initialize = DGNB_Initialize; IPC_PerClientInit = DGNB_PerClientInit; IPC_Cleanup = DGNB_Cleanup; IPC_Connect_To_Server = DGNB_Connect_To_Server; IPC_Disconnect_From_Server = DGNB_Disconnect_From_Server; IPC_Allocate_Memory = DGNB_Allocate_Memory; IPC_DoHandshake = DGNB_DoHandshake; IPC_ReadFromIPC = DGNB_ReadFromIPC; IPC_WriteToIPC = DGNB_WriteToIPC; IPC_XactIO = DGNB_XactIO; IPC_Deallocate_Memory = DGNB_Deallocate_Memory; IPC_ThreadCleanUp = DGNB_ThreadCleanUp; break;
default : // problem here
printf("Incorrect Xport selection\n"); } } /************************************************************************/
/*++
This routine makes the main thread wait for all the client threads to exit. --*/
NTSTATUS Wait_For_Client_Threads(VOID)
{
NTSTATUS wstatus; // LARGE_INTEGER MTimeout;
DWORD MTimeout;
// printf("Main thread waiting for Client threads ");
printf("..Wait");
// MTimeout.LowPart = 0xFFFFFFFF;
// MTimeout.HighPart = 0x7FFFFFFF; // -1L DIDN't work
MTimeout = INFINITE;
wstatus = WaitForMultipleObjectsEx( NClients, Threads, TRUE, MTimeout, // Default timeout
TRUE); // Alertable
printf("..Over..Results:\n");
if (!NT_SUCCESS(wstatus)) { printf ("Failed on wait err=%lx\n",wstatus); }
return wstatus; }
/************************************************************************/
VOID Cleanup(VOID) { USHORT Cindex = 0; // client index
NTSTATUS exitstatus = 0; NTSTATUS cstatus;
for (Cindex = 0; Cindex < NClients; Cindex++) { // terminate the thread
cstatus = TerminateThread(Clients[Cindex].c_hThHandle, exitstatus); /*
if (!NT_SUCCESS(cstatus)) { printf("Failed to terminate thread no:%d err=%lx\n", Cindex,cstatus); } */ } // printf("Terminated All Threads\n");
} /************************************************************************/
|