1045 lines
31 KiB
C++
1045 lines
31 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) 2002 Microsoft Corporation. All Rights Reserved.
|
|
//
|
|
// Module Name: TestAccept.cpp
|
|
//
|
|
// Description:
|
|
// This file contains the functions for a client that tests
|
|
// the accept implementation.
|
|
|
|
#pragma warning (disable: 4267)
|
|
#pragma warning (disable: 4127)
|
|
|
|
|
|
#include <winsock2.h> // for Winsock API
|
|
#include <windows.h> // for Win32 APIs and types
|
|
#include <ws2tcpip.h> // for IPv6 support
|
|
#include <wspiapi.h> // for IPv6 support
|
|
#include <strsafe.h> // for safe versions of string functions
|
|
#include <stdio.h> // for printing to stdout.
|
|
|
|
|
|
// The following are the default values for the various command-line
|
|
// options.
|
|
#define DEFAULT_ADDRESS_FAMILY AF_UNSPEC
|
|
#define DEFAULT_SERVER "localhost"
|
|
#define DEFAULT_PORT "7243"
|
|
#define DEFAULT_SEND_BUFFER_SIZE 1024
|
|
#define DEFAULT_RECV_BUFFER_SIZE 10240
|
|
#define DEFAULT_LOOP_COUNT 1
|
|
#define DEFAULT_DELAY 5000
|
|
#define DEFAULT_SCENARIO 5
|
|
|
|
|
|
// The following are the different scenarios supported for testing
|
|
// the Accept server implementation. The ideal send receive is the
|
|
// recommended scenario for real world use. The other scenarios
|
|
// are meant to understand the various problems that would be
|
|
// caused in the server if the client behaves (or misbehaves) in a
|
|
// particular way.
|
|
enum
|
|
{
|
|
SC_SEND_THEN_RECV = 1,
|
|
SC_SEND_NO_RECV = 2,
|
|
SC_SEND_WAIT_RECV = 3,
|
|
SC_WAIT_SEND_RECV = 4,
|
|
SC_IDEAL_SEND_RECV = 5,
|
|
SC_NUM_SCENARIOS = 5
|
|
};
|
|
|
|
|
|
// structure that bundles all the global variables needed between different
|
|
// functions into a global context.
|
|
typedef struct _ClientContext
|
|
{
|
|
BYTE addressFamily;
|
|
char *szServer;
|
|
char *szPort;
|
|
LONG sendBufSize;
|
|
LONG recvBufSize;
|
|
LONG loopCount;
|
|
LONG delay;
|
|
BYTE scenario;
|
|
char *pSendBuf;
|
|
char *pRecvBuf;
|
|
int nBytesRemainingToBeSent;
|
|
int nBytesRecd;
|
|
SOCKET sock;
|
|
|
|
} ClientContext;
|
|
|
|
ClientContext g_ClientContext;
|
|
|
|
/*
|
|
This function converts a given address family into its corresponding string
|
|
representation for display purposes.
|
|
*/
|
|
const char *AFImage(BYTE addressFamily)
|
|
{
|
|
char *szRetVal;
|
|
|
|
// return the corresponding string for the given address family.
|
|
switch (addressFamily)
|
|
{
|
|
case AF_UNSPEC : szRetVal = "AF_UNSPEC";
|
|
break;
|
|
case AF_INET : szRetVal = "AF_INET";
|
|
break;
|
|
case AF_INET6 : szRetVal = "AF_INET6";
|
|
break;
|
|
default : szRetVal = "Unrecognized";
|
|
break;
|
|
}
|
|
|
|
return szRetVal;
|
|
}
|
|
|
|
|
|
/*
|
|
This function prints the available command-line options, the arguments
|
|
expected by each of them and the valid input values and the default
|
|
values for each them.
|
|
*/
|
|
void PrintUsage(char *szProgramName)
|
|
{
|
|
printf("\n\n"
|
|
"Usage:\n"
|
|
"------\n"
|
|
" %s <options> \n\n"
|
|
"where <options> is one or more of the following: \n\n"
|
|
" -a <0|4|6> Address Family: 0 for Either\n"
|
|
" 4 for IPv4\n"
|
|
" 6 for IPv6\n"
|
|
" Default: %d\n\n"
|
|
" -n <server> Server Name or IP\n"
|
|
" Default: %s\n\n"
|
|
" -e <endpoint> Port number\n"
|
|
" Default: %s\n\n"
|
|
" -b <bytes> Bytes to send\n"
|
|
" Default: %d\n\n"
|
|
" -l <count> Loop count times \n"
|
|
" Default: %d\n\n"
|
|
" -d <millisecs> Delay after or before send (see -s 3 & -s 4)\n"
|
|
" Default: %d\n\n"
|
|
" -s <1..5> Scenario: \n"
|
|
" 1 : Finish all sends, then recv\n"
|
|
" 2 : Finish all sends, no recv\n"
|
|
" 3 : Finish all sends, wait, then recv\n"
|
|
" 4 : First wait, then send, then recv\n"
|
|
" 5 : Ideal Send and Recv sequence\n"
|
|
" Default: %d\n\n"
|
|
"\n",
|
|
szProgramName,
|
|
DEFAULT_ADDRESS_FAMILY,
|
|
DEFAULT_SERVER,
|
|
DEFAULT_PORT,
|
|
DEFAULT_SEND_BUFFER_SIZE,
|
|
DEFAULT_LOOP_COUNT,
|
|
DEFAULT_DELAY,
|
|
DEFAULT_SCENARIO
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
This function parses the given input arguments and fills up the
|
|
corresponding fields in the g_ClientContext structure.
|
|
*/
|
|
BOOL ParseArguments(int argc, char *argv[])
|
|
{
|
|
// holds the return value from this function.
|
|
// TRUE indicates that all the supplied arguments are valid.
|
|
// FALSE indicates incorrect or insufficient number of arguments.
|
|
BOOL retVal = FALSE;
|
|
|
|
// loop index to go over the command-line arguments one by one.
|
|
int i;
|
|
|
|
printf("Entering ParseArguments()\n");
|
|
|
|
// fill up the default arguments and let the user options override these.
|
|
g_ClientContext.addressFamily = DEFAULT_ADDRESS_FAMILY;
|
|
g_ClientContext.szServer = DEFAULT_SERVER;
|
|
g_ClientContext.szPort = DEFAULT_PORT;
|
|
g_ClientContext.sendBufSize = DEFAULT_SEND_BUFFER_SIZE;
|
|
g_ClientContext.recvBufSize = DEFAULT_RECV_BUFFER_SIZE;
|
|
g_ClientContext.loopCount = DEFAULT_LOOP_COUNT;
|
|
g_ClientContext.delay = DEFAULT_DELAY;
|
|
g_ClientContext.scenario = DEFAULT_SCENARIO;
|
|
|
|
// process each argument in the argv list.
|
|
for (i = 1; i < argc ; i++)
|
|
{
|
|
char firstChar = argv[i][0];
|
|
|
|
// make sure the option begins with a - or /
|
|
if (!(firstChar == '-' || firstChar == '/'))
|
|
{
|
|
printf("ERROR: Option has to begin with - or / : %s\n", argv[i]);
|
|
PrintUsage(argv[0]);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// process the option.
|
|
switch(argv[i][1])
|
|
{
|
|
case 'a' :
|
|
|
|
// Address Family.
|
|
// should be -a 0 or -a 4 or -a 6
|
|
|
|
// first check if there's one more argument.
|
|
if (i + 1 >= argc)
|
|
{
|
|
printf("ERROR: Argument 0/4/6 needed for -a option\n");
|
|
PrintUsage(argv[0]);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// extract and validate the AF number.
|
|
switch(atoi(argv[i+1]))
|
|
{
|
|
// Unspecified.
|
|
case 0:
|
|
g_ClientContext.addressFamily = AF_UNSPEC;
|
|
break;
|
|
|
|
// IPv4.
|
|
case 4:
|
|
g_ClientContext.addressFamily = AF_INET;
|
|
break;
|
|
|
|
// IPv6.
|
|
case 6:
|
|
g_ClientContext.addressFamily = AF_INET6;
|
|
break;
|
|
|
|
// Invalid value.
|
|
default:
|
|
printf("ERROR: Invalid address family. Must be 0/4/6\n");
|
|
PrintUsage(argv[0]);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// indicate that we have processed the next argument as well.
|
|
i++;
|
|
|
|
// AF was fine. continue.
|
|
break;
|
|
|
|
case 'n' :
|
|
|
|
// Interface to listen on.
|
|
// should be -n <server Name>
|
|
|
|
// first check if there's one more argument.
|
|
if (i + 1 >= argc)
|
|
{
|
|
printf("ERROR: Server name needed for -n option\n");
|
|
PrintUsage(argv[0]);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// make sure the input string length is less than
|
|
// the INET6_ADDRSTRLEN, the maximum valid IP address length.
|
|
if (FAILED(StringCchLength(argv[i+1],INET6_ADDRSTRLEN, NULL)))
|
|
{
|
|
printf("ERROR: Server name string too long. "
|
|
"can't exceed %d characters\n",
|
|
INET6_ADDRSTRLEN);
|
|
PrintUsage(argv[0]);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// remember the interface string.
|
|
g_ClientContext.szServer = argv[i+1];
|
|
|
|
// indicate that we have processed the next argument as well.
|
|
i++;
|
|
|
|
// continue.
|
|
break;
|
|
|
|
case 'e' :
|
|
|
|
// Endpoint or Port.
|
|
// should be -e <port number>
|
|
|
|
// first check if there's one more argument.
|
|
if (i + 1 >= argc)
|
|
{
|
|
printf("ERROR: Port number needed for -e option\n");
|
|
PrintUsage(argv[0]);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// make sure the input string length is less than
|
|
// the maximum length for a service name.
|
|
if (FAILED(StringCchLength(argv[i+1], NI_MAXSERV, NULL)))
|
|
{
|
|
printf("ERROR: Port number too long. "
|
|
"can't exceed %d characters\n",
|
|
NI_MAXSERV);
|
|
|
|
PrintUsage(argv[0]);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// remember the port number string.
|
|
g_ClientContext.szPort = argv[i+1];
|
|
|
|
// indicate that we have processed the next argument as well.
|
|
i++;
|
|
|
|
// continue.
|
|
break;
|
|
|
|
case 'b' :
|
|
|
|
// Bytes to Send
|
|
// should be -b <bytes>.
|
|
|
|
// first check if there's one more argument.
|
|
if (i + 1 >= argc)
|
|
{
|
|
printf("ERROR: Number of bytes needed for -b option\n");
|
|
PrintUsage(argv[0]);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// extract the bytes value
|
|
g_ClientContext.sendBufSize = atol(argv[i+1]);
|
|
|
|
// indicate that we have processed the next argument as well.
|
|
i++;
|
|
|
|
// validate the buffer size.
|
|
if (g_ClientContext.sendBufSize < 0)
|
|
{
|
|
printf("ERROR: Number of bytes must be > 0 \n");
|
|
PrintUsage(argv[0]);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// Continue.
|
|
break;
|
|
|
|
case 'l' :
|
|
|
|
// Loop Count
|
|
// should be -l or -l <count>.
|
|
|
|
// if there's one more argument, parse it.
|
|
if (i + 1 < argc)
|
|
{
|
|
g_ClientContext.loopCount = atol(argv[i+1]);
|
|
|
|
// make sure loop count is 1 or more, if specified.
|
|
if (g_ClientContext.loopCount < 1)
|
|
{
|
|
printf("ERROR: Loop Count must be > 0 : %d\n",
|
|
g_ClientContext.loopCount);
|
|
PrintUsage(argv[0]);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// indicate that we have processed the next argument
|
|
// as well.
|
|
i++;
|
|
}
|
|
|
|
|
|
// Continue.
|
|
break;
|
|
|
|
case 'd' :
|
|
|
|
// Delay in milliseconds.
|
|
// should be -d <millisecs>.
|
|
|
|
// first check if there's one more argument.
|
|
if (i + 1 >= argc)
|
|
{
|
|
printf("ERROR: Number of milliseconds needed "
|
|
"for -d option\n");
|
|
PrintUsage(argv[0]);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// extract the delay value
|
|
g_ClientContext.delay = atol(argv[i+1]);
|
|
|
|
// indicate that we have processed the next argument as well.
|
|
i++;
|
|
|
|
// Continue.
|
|
break;
|
|
|
|
case 's' :
|
|
|
|
// Scenario Number.
|
|
// should be -s <scenario number>.
|
|
|
|
// first check if there's one more argument.
|
|
if (i + 1 >= argc)
|
|
{
|
|
printf("ERROR: Scenario Number needed for -s option\n");
|
|
PrintUsage(argv[0]);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// extract the scenario value
|
|
g_ClientContext.scenario = (BYTE) atoi(argv[i+1]);
|
|
|
|
// indicate that we have processed the next argument as well.
|
|
i++;
|
|
|
|
// validate the scenario type.
|
|
if (g_ClientContext.scenario < 1 ||
|
|
g_ClientContext.scenario > SC_NUM_SCENARIOS)
|
|
{
|
|
printf("ERROR: Invalid scenario type: %d. Must be between "
|
|
"1 to %d\n",g_ClientContext.scenario,
|
|
SC_NUM_SCENARIOS);
|
|
PrintUsage(argv[0]);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// Continue.
|
|
break;
|
|
|
|
case 'h' : // help
|
|
case '?' : // help
|
|
PrintUsage(argv[0]);
|
|
goto CLEANUP;
|
|
default :
|
|
printf("ERROR: Unrecognized option: %s\n", argv[i]);
|
|
PrintUsage(argv[0]);
|
|
goto CLEANUP;
|
|
}
|
|
}
|
|
|
|
// echo the final list of values that'll be used.
|
|
// remember, these need not be the same as the input arguments.
|
|
// rather, this is what we'll use inside our program.
|
|
printf("Parsed input arguments. The following values will be used : \n");
|
|
printf("\tAddress Family = %s\n", AFImage(g_ClientContext.addressFamily));
|
|
printf("\tServer = %s\n",g_ClientContext.szServer);
|
|
printf("\tPort = %s\n", g_ClientContext.szPort);
|
|
printf("\tBytes to Send = %ld\n", g_ClientContext.sendBufSize);
|
|
printf("\tReceive buffer size = %ld\n", g_ClientContext.recvBufSize);
|
|
printf("\tIterations = %ld\n", g_ClientContext.loopCount);
|
|
printf("\tDelay = %ld\n", g_ClientContext.delay);
|
|
printf("\tScenario = %d\n", g_ClientContext.scenario);
|
|
|
|
// all went well, signal that we can proceed.
|
|
retVal = TRUE;
|
|
|
|
CLEANUP:
|
|
|
|
printf("Exiting ParseArguments()\n");
|
|
return retVal;
|
|
}
|
|
|
|
|
|
/*
|
|
Prints the given socket address in a printable string format.
|
|
*/
|
|
void PrintAddressString(LPSOCKADDR pSockAddr, DWORD dwSockAddrLen)
|
|
{
|
|
// INET6_ADDRSTRLEN is the maximum size of a valid IPv6 address
|
|
// including port,colons,NULL,etc.
|
|
char buf[INET6_ADDRSTRLEN];
|
|
DWORD dwBufSize = 0;
|
|
|
|
memset(buf,0,sizeof(buf));
|
|
dwBufSize = sizeof(buf);
|
|
|
|
// This function converts the pSockAddr to a printable format into buf.
|
|
if (WSAAddressToString(pSockAddr,
|
|
dwSockAddrLen,
|
|
NULL,
|
|
buf,
|
|
&dwBufSize) == SOCKET_ERROR)
|
|
{
|
|
printf("ERROR: WSAAddressToString failed %d \n", WSAGetLastError());
|
|
goto CLEANUP;
|
|
}
|
|
|
|
printf("%s\n", buf);
|
|
|
|
CLEANUP:
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
This function create a socket for the client and connects it to the
|
|
server.
|
|
*/
|
|
SOCKET CreateClientSocket()
|
|
{
|
|
struct addrinfo hints;
|
|
struct addrinfo *res;
|
|
struct addrinfo *pAddr;
|
|
SOCKET clientSock = INVALID_SOCKET;
|
|
int i;
|
|
int rc;
|
|
|
|
printf("Entering CreateClientSocket()\n");
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
hints.ai_family = g_ClientContext.addressFamily;
|
|
hints.ai_protocol = IPPROTO_TCP;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
|
|
// get the local addresses that are suitable for connecting to the
|
|
// given server address.
|
|
if (getaddrinfo(g_ClientContext.szServer,
|
|
g_ClientContext.szPort,
|
|
&hints,
|
|
&res) != NO_ERROR)
|
|
{
|
|
printf("getaddrinfo failed. Error = %d\n", WSAGetLastError());
|
|
goto CLEANUP;
|
|
}
|
|
|
|
if (res == NULL)
|
|
{
|
|
printf("getaddrinfo returned res = NULL\n");
|
|
goto CLEANUP;
|
|
}
|
|
|
|
printf("getaddrinfo successful.Enumerating the returned addresses ...\n\n");
|
|
|
|
for (pAddr = res, i = 1; pAddr != NULL; pAddr = pAddr->ai_next, i++)
|
|
{
|
|
printf("Processing Address %d returned by getaddrinfo : ", i);
|
|
PrintAddressString(pAddr->ai_addr, pAddr->ai_addrlen);
|
|
clientSock = WSASocket(pAddr->ai_family,
|
|
pAddr->ai_socktype,
|
|
pAddr->ai_protocol,
|
|
NULL,
|
|
NULL,
|
|
0);
|
|
if (clientSock == INVALID_SOCKET)
|
|
{
|
|
printf("WSASocket failed. Error = %d\n", WSAGetLastError());
|
|
printf("Ignoring this address and continuing with the next. \n\n");
|
|
|
|
// anyway, let's continue with other addresses.
|
|
continue;
|
|
}
|
|
|
|
printf("Created client socket with handle = %d\n", clientSock);
|
|
|
|
rc = connect(clientSock, pAddr->ai_addr, pAddr->ai_addrlen);
|
|
if (rc == SOCKET_ERROR)
|
|
{
|
|
printf("connect failed. Error = %d\n", WSAGetLastError());
|
|
closesocket(clientSock);
|
|
clientSock = INVALID_SOCKET;
|
|
continue;
|
|
}
|
|
|
|
printf("Connected successfully\n");
|
|
break;
|
|
}
|
|
|
|
|
|
CLEANUP:
|
|
|
|
// if getaddrinfo succeeded, it would have allocated memory for the
|
|
// res structure which we have to free.
|
|
if (res)
|
|
{
|
|
freeaddrinfo(res);
|
|
printf("Freed the memory allocated for res by getaddrinfo\n");
|
|
}
|
|
|
|
printf("Exiting CreateClientSocket()\n");
|
|
return clientSock;
|
|
}
|
|
|
|
|
|
/*
|
|
Allocate a buffer to use for sending data and initialize the buffer
|
|
with suitable initial values.
|
|
*/
|
|
BOOL PrepareSendBuffer()
|
|
{
|
|
BOOL bSuccess = FALSE;
|
|
|
|
printf("Entering PrepareSendBuffer()\n");
|
|
|
|
g_ClientContext.pSendBuf = (char *) malloc(g_ClientContext.sendBufSize + 1);
|
|
if (g_ClientContext.pSendBuf == NULL)
|
|
{
|
|
printf("malloc failed.\n");
|
|
goto CLEANUP;
|
|
}
|
|
|
|
printf("Allocated Send Buffer: %p\n", g_ClientContext.pSendBuf);
|
|
|
|
// fill up with some info. here we are just sending 1 character.
|
|
memset(g_ClientContext.pSendBuf, 'H', g_ClientContext.sendBufSize);
|
|
g_ClientContext.pSendBuf[g_ClientContext.sendBufSize] = '\0';
|
|
g_ClientContext.nBytesRemainingToBeSent = g_ClientContext.sendBufSize;
|
|
|
|
bSuccess = TRUE;
|
|
|
|
CLEANUP:
|
|
printf("Exiting PrepareSendBuffer()\n");
|
|
return bSuccess;
|
|
}
|
|
|
|
|
|
/*
|
|
Deallocate the buffer that was used for sending.
|
|
*/
|
|
void FreeSendBuffer()
|
|
{
|
|
if (g_ClientContext.pSendBuf != NULL)
|
|
{
|
|
free(g_ClientContext.pSendBuf);
|
|
printf("Freed Send Buffer : %p\n", g_ClientContext.pSendBuf);
|
|
g_ClientContext.pSendBuf = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Allocate a buffer for receiving data from the server.
|
|
Currently since we are not using the received data, we'll just receive
|
|
all the data in a big one-time buffer for all the receives and overwrite
|
|
the same buffer each time. The count of received bytes is what we
|
|
are presently interested in.
|
|
*/
|
|
BOOL PrepareRecvBuffer()
|
|
{
|
|
BOOL bSuccess = FALSE;
|
|
|
|
printf("Entering PrepareRecvBuffer()\n");
|
|
|
|
g_ClientContext.pRecvBuf = (char *) malloc(g_ClientContext.recvBufSize + 1);
|
|
if (g_ClientContext.pRecvBuf == NULL)
|
|
{
|
|
printf("malloc failed.\n");
|
|
goto CLEANUP;
|
|
}
|
|
|
|
printf("Allocated Recv Buffer: %p\n", g_ClientContext.pRecvBuf);
|
|
|
|
memset(g_ClientContext.pRecvBuf, 0, g_ClientContext.recvBufSize + 1);
|
|
g_ClientContext.nBytesRecd = 0;
|
|
|
|
bSuccess = TRUE;
|
|
|
|
CLEANUP:
|
|
printf("Exiting PrepareRecvBuffer()\n");
|
|
return bSuccess;
|
|
}
|
|
|
|
|
|
/*
|
|
Deallocate the buffer used for receiving.
|
|
*/
|
|
void FreeRecvBuffer()
|
|
{
|
|
if (g_ClientContext.pRecvBuf != NULL)
|
|
{
|
|
free(g_ClientContext.pRecvBuf);
|
|
printf("Freed Recv Buffer : %p\n", g_ClientContext.pRecvBuf);
|
|
g_ClientContext.pRecvBuf = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Try sending data to server once. Return the error, if any.
|
|
*/
|
|
int DoSendOnce()
|
|
{
|
|
int nBytesSent;
|
|
int startPosition;
|
|
int err = 0;
|
|
|
|
printf("Entering DoSendOnce()\n");
|
|
|
|
// send from the position where we left off last.
|
|
startPosition = g_ClientContext.sendBufSize -
|
|
g_ClientContext.nBytesRemainingToBeSent;
|
|
nBytesSent = send(g_ClientContext.sock,
|
|
g_ClientContext.pSendBuf + startPosition,
|
|
g_ClientContext.nBytesRemainingToBeSent,
|
|
0);
|
|
|
|
if (nBytesSent == SOCKET_ERROR)
|
|
{
|
|
err = WSAGetLastError();
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// remember where to begin send next time.
|
|
g_ClientContext.nBytesRemainingToBeSent -= nBytesSent;
|
|
printf("Sent %d bytes so far\n", startPosition + nBytesSent);
|
|
|
|
CLEANUP:
|
|
|
|
printf("Exiting DoSendOnce()\n");
|
|
return err;
|
|
}
|
|
|
|
|
|
/*
|
|
Try receiving from the server. This function returns the number of bytes
|
|
received (unlike DoSendOnce which returns the error code).
|
|
*/
|
|
int DoRecvOnce()
|
|
{
|
|
int nBytesRecd;
|
|
|
|
printf("Entering DoRecvOnce()\n");
|
|
|
|
// receive into the same global receive buffer, overwriting the previous
|
|
// contents, as we are only interested in the number of bytes received
|
|
// for verification.
|
|
nBytesRecd = recv(g_ClientContext.sock,
|
|
g_ClientContext.pRecvBuf,
|
|
g_ClientContext.recvBufSize,
|
|
0);
|
|
|
|
if (nBytesRecd == SOCKET_ERROR)
|
|
{
|
|
printf("recv returned: %d\n", WSAGetLastError());
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// update the stats.
|
|
g_ClientContext.nBytesRecd += nBytesRecd;
|
|
printf("Recd %d bytes so far\n", g_ClientContext.nBytesRecd);
|
|
|
|
CLEANUP:
|
|
|
|
printf("Exiting DoRecvOnce()\n");
|
|
return nBytesRecd;
|
|
}
|
|
|
|
|
|
/*
|
|
Repeat sending the data to the server until all the data has been
|
|
completely sent to the server.
|
|
*/
|
|
void DoSendUntilDone()
|
|
{
|
|
int err;
|
|
|
|
printf("Entering DoSendUntilDone()\n");
|
|
|
|
do
|
|
{
|
|
// Get the job done by callnig DoSendOnce.
|
|
err = DoSendOnce();
|
|
|
|
// handle error cases as appropriate.
|
|
switch(err)
|
|
{
|
|
case 0:
|
|
if (g_ClientContext.nBytesRemainingToBeSent == 0)
|
|
{
|
|
printf("Send completed\n");
|
|
goto CLEANUP;
|
|
}
|
|
break;
|
|
|
|
case WSAEWOULDBLOCK:
|
|
printf("Got WSAEWOULDBLOCK from send.\n");
|
|
printf("Waiting for 1 second before retrying send ...\n");
|
|
Sleep(1000);
|
|
break;
|
|
|
|
default:
|
|
// other errors.
|
|
printf("ERROR: send failed: Error = %d\n", err);
|
|
goto CLEANUP;
|
|
}
|
|
} while (TRUE);
|
|
|
|
CLEANUP:
|
|
printf("Exiting DoSendUntilDone()\n");
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
Receive data on the socket until recv returns 0 or error.
|
|
*/
|
|
void DoRecvUntilDone()
|
|
{
|
|
int err;
|
|
|
|
printf("Entering DoRecvUntilDone()\n");
|
|
do
|
|
{
|
|
// get the job done by calling DoRecvOnce.
|
|
switch(DoRecvOnce())
|
|
{
|
|
case 0:
|
|
printf("Recv returned 0. Remote socket must have been "
|
|
"gracefully closed.\n");
|
|
goto CLEANUP;
|
|
|
|
case SOCKET_ERROR:
|
|
err = WSAGetLastError();
|
|
if (err == WSAEWOULDBLOCK)
|
|
{
|
|
printf("Got WSAEWOULDBLOCK from recv.\n");
|
|
printf("Waiting for 1 second before retrying send ...\n");
|
|
Sleep(1000);
|
|
}
|
|
else
|
|
{
|
|
printf("ERROR: recv returned : %d\n", err);
|
|
goto CLEANUP;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// > 0 bytes read. let's continue.
|
|
break;
|
|
}
|
|
} while (1);
|
|
|
|
CLEANUP:
|
|
printf("Exiting DoRecvUntilDone()\n");
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
This functions shuts down the socket before closing it so that
|
|
it would send a FIN packet on the TCP connection to the remote side
|
|
to indicate that there won't be any more data sent on this socket.
|
|
Only if this side shuts down, the remote side will get a 0 for a
|
|
recv and conclude that the client is done with sending data.
|
|
*/
|
|
void DoShutDown()
|
|
{
|
|
if (shutdown(g_ClientContext.sock, SD_SEND) == SOCKET_ERROR)
|
|
printf("shutdown failed. err = %d\n", WSAGetLastError());
|
|
else
|
|
printf("shutdown successful\n");
|
|
}
|
|
|
|
/*
|
|
This function implements the scenario where all the sends are done first
|
|
and all the recvs next.
|
|
*/
|
|
void DoSendThenRecv()
|
|
{
|
|
DoSendUntilDone();
|
|
DoShutDown();
|
|
DoRecvUntilDone();
|
|
}
|
|
|
|
/*
|
|
This function implements the scenario where the client only sends but
|
|
doesn't recv the echoed data back. This helps demonstrate possible
|
|
deadlocks in the server side for huge sized data buffer.
|
|
*/
|
|
void DoSendNoRecv()
|
|
{
|
|
DoSendUntilDone();
|
|
DoShutDown();
|
|
}
|
|
|
|
/*
|
|
This function implements the scenario where the client waits for a certain
|
|
time before doing the recv thereby allowing the server code to get
|
|
a WSAEWOULDBLOCK on a send, especially when the data buffer is huge.
|
|
*/
|
|
void DoSendWaitRecv()
|
|
{
|
|
DoSendUntilDone();
|
|
DoShutDown();
|
|
printf("Waiting before doing recv ...\n");
|
|
Sleep(g_ClientContext.delay);
|
|
DoRecvUntilDone();
|
|
}
|
|
|
|
/*
|
|
This function implements the scenario to test the server to get
|
|
a FD_WRITE event before an FD_READ event.
|
|
*/
|
|
void DoWaitSendRecv()
|
|
{
|
|
printf("Waiting before doing initial send ...\n");
|
|
Sleep(g_ClientContext.delay);
|
|
DoSendUntilDone();
|
|
DoShutDown();
|
|
DoRecvUntilDone();
|
|
}
|
|
|
|
/*
|
|
This function is supplied as the call back function for CreateThread.
|
|
Although it is doing nothing other than calling DoRecvUntilDone, it
|
|
needs to have this particular signature to be passed as a valid
|
|
call back function for CreateThread.
|
|
*/
|
|
DWORD WINAPI ReceiverThread(LPVOID pv)
|
|
{
|
|
pv = NULL;
|
|
|
|
DoRecvUntilDone();
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
This function illustrates the ideal send and recv scenario where
|
|
one thread does the recv independently until it gets a 0 or an error
|
|
and the other thread (in this case, the current thread) does sends
|
|
until all data is sent. This is the truly asynchronous implementation
|
|
of the client unlike the earlier scenarios which were mostly meant
|
|
for testing specific sequence of events in the server and understanding
|
|
the need for different parts of the server code.
|
|
*/
|
|
void DoIdealSendRecv()
|
|
{
|
|
HANDLE hThread;
|
|
|
|
// start the receiver thread.
|
|
hThread = CreateThread(NULL, 0, ReceiverThread, NULL, 0, NULL);
|
|
if (hThread == NULL)
|
|
{
|
|
printf("ERROR: CreateThread failed. Error = %d\n", GetLastError());
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// In this thread, send all data until done.
|
|
DoSendUntilDone();
|
|
DoShutDown();
|
|
|
|
// wait till recv thread is done.
|
|
if (WaitForSingleObject(hThread, INFINITE) != WAIT_OBJECT_0)
|
|
{
|
|
printf("ERROR: WaitForSingleObject failed: %d\n", GetLastError());
|
|
goto CLEANUP;
|
|
}
|
|
printf("Recv Thread done.\n");
|
|
|
|
CLEANUP:
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
This function is the entry point for this program.
|
|
Based on the command-line arguments, it invokes the suitable functions.
|
|
*/
|
|
int _cdecl main(int argc, char *argv[])
|
|
{
|
|
// holds the return value from this function.
|
|
// 0 indicates success, non-zero indicates failure.
|
|
int retVal;
|
|
int i;
|
|
BOOL bStartupSuccessful = FALSE;
|
|
|
|
WSADATA wsaData;
|
|
|
|
printf("Entering main()\n");
|
|
|
|
// parse and validate the given arguments and determine if we should
|
|
// continue the execution or return error.
|
|
if (ParseArguments(argc, argv) == FALSE)
|
|
{
|
|
// error input. return a non-zero error code.
|
|
retVal = 1;
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// call WSAStartup to initialize Winsock before calling any of its APIs.
|
|
retVal = WSAStartup(MAKEWORD(2,2), &wsaData);
|
|
if (retVal != 0)
|
|
{
|
|
printf("WSAStartup failed. Error = %d\n", retVal);
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// remember that the WSAStartup was successful so as to call WSACleanup
|
|
// once, correspondingly.
|
|
bStartupSuccessful = TRUE;
|
|
|
|
for(i = 1; i <= g_ClientContext.loopCount; i++)
|
|
{
|
|
printf("\n\nPerforming Iteration : %d\n"
|
|
"-------------------------\n",i);
|
|
|
|
// create a socket that's connected to the server.
|
|
g_ClientContext.sock = CreateClientSocket();
|
|
if (g_ClientContext.sock == INVALID_SOCKET)
|
|
goto CLEANUP;
|
|
|
|
// allocate and initialize the buffer to be sent.
|
|
if (PrepareSendBuffer() == FALSE)
|
|
goto CLOSE_SOCKET;
|
|
|
|
// allocate and initialize a buffer for receiving.
|
|
if (PrepareRecvBuffer() == FALSE)
|
|
goto CLOSE_SOCKET;
|
|
|
|
// perform the sends/recv according to the requested scenario.
|
|
switch(g_ClientContext.scenario)
|
|
{
|
|
case SC_SEND_THEN_RECV : DoSendThenRecv();
|
|
break;
|
|
case SC_SEND_NO_RECV : DoSendNoRecv();
|
|
break;
|
|
case SC_SEND_WAIT_RECV : DoSendWaitRecv();
|
|
break;
|
|
case SC_WAIT_SEND_RECV : DoWaitSendRecv();
|
|
break;
|
|
case SC_IDEAL_SEND_RECV: DoIdealSendRecv();
|
|
break;
|
|
default: printf("Unrecognized scenario\n");
|
|
break;
|
|
}
|
|
|
|
CLOSE_SOCKET:
|
|
|
|
// free the send and recv buffers.
|
|
FreeSendBuffer();
|
|
FreeRecvBuffer();
|
|
|
|
// close the client socket.
|
|
closesocket(g_ClientContext.sock);
|
|
printf("Closed socket %d. "
|
|
"Total Bytes Recd = %d, "
|
|
"Total Bytes Sent = %d\n",
|
|
g_ClientContext.sock,
|
|
g_ClientContext.nBytesRecd,
|
|
g_ClientContext.sendBufSize -
|
|
g_ClientContext.nBytesRemainingToBeSent);
|
|
|
|
}
|
|
|
|
|
|
CLEANUP:
|
|
|
|
// call WSACleanup only if WSAStartup was successful.
|
|
if (bStartupSuccessful)
|
|
{
|
|
// Inform Winsock that we are done with all the APIs.
|
|
WSACleanup();
|
|
}
|
|
|
|
printf("Exiting main()\n");
|
|
return retVal;
|
|
}
|
|
|
|
|