📄 iocpserver.cpp
字号:
//
// Sample: I/O Completion Port IPv4/IPv6 Server
//
// Files:
// iocpserver.cpp - this file
// resolve.cpp - Common name resolution routines
// resolve.h - Header file for name resolution routines
//
// Description:
// This sample illustrates overlapped IO with a completion port for
// TCP and UDP over both IPv4 and IPv6. This sample uses the
// getaddrinfo/getnameinfo APIs which allows this application to be
// IP version independent. That is the desired address family
// (AF_INET or AF_INET6) can be determined simply from the string
// address passed via the -l command.
//
// For TCP, a listening socket is created for each IP address family
// available. Each socket is associated with a completion port and
// worker threads are spawned (one for each CPU available). For each
// listening thread, a number of AcceptEx are posted. The worker threads
// then wait for one of these to complete. Upon completion, the client
// socket is associated with the completion port and several receives
// are posted. The AcceptEx is reposted as well. Once data is received
// on a client socket, it is echoed back.
//
// For UDP, an echo socket is creatd for each IP address family available.
// For each socket, several receives are posted. Once these receives
// complete, the data is sent back to the receiver.
//
// The important thing to remember with IOCP is that the completion events
// may occur out of order; however, the buffers are guaranteed to be filled
// in the order posted. For our echo server this can cause problems as
// receive N+1 may complete before receive N. We can't echo back N+1 before
// echoing N. There are two approaches possible. First, we could surmise
// that since receive N+1 has completed then we can safely echo back receive
// N and N+1 at that time (to maintain the data ordering). To do this properly
// you'll have to call WSAGetOverlappedResult on receive N in order to find
// out how many bytes were received to echo it back. The second approach
// (which is implemented in this sample) is to keep a list of receive
// buffers that completed out of order. This list is maintained in the
// per-socket data structure. When receive N+1 completes, it will notice that
// receive N has not completed. The buffer is then queued in the out of
// order send list. Once receive N completes, its buffer is queued -- the
// queue is ordered in the same order that the receive operations are.
// Another routine (DoSends) goes through this list and sends those buffers
// that are available and in order. If any gaps are detected no further buffers
// are sent (as we will wait for that receive to complete and insert its
// buffer into the list so that the next call to DoSends will correctly
// send the buffers in the right order).
//
// For example:
// If this sample is called with the following command lines:
// iocpserver.exe -l fe80::2efe:1234 -e 5150
// iocpserver.exe -l ::
// Then the server creates an IPv6 socket as an IPv6 address was
// provided.
//
// On the other hand, with the following command line:
// iocpserver.exe -l 7.7.7.1 -e 5150
// iocpserver.exe -l 0.0.0.0
// Then the server creates an IPv4 socket.
//
// Calling the server with no parameters will create a server that
// listens both IPv4 and IPv6 (if installed).
//
// Compile:
// cl -o iocpserver.exe iocpserver.cpp resolve.cpp ws2_32.lib
//
// Usage:
// iocpserver.exe [options]
// -a 4|6 Address family, 4 = IPv4, 6 = IPv6 [default = IPv4]
// -b size Buffer size for send/recv
// -e port Port number
// -l addr Local address to bind to [default INADDR_ANY for IPv4 or INADDR6_ANY for IPv6]
// -p proto Which protocol to use [default = TCP]
// tcp Use TCP
// udp Use UDP
//
#include <winsock2.h>
#include <ws2tcpip.h>
#include <mswsock.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include "resolve.h"
#define DEFAULT_BUFFER_SIZE 4096 // default buffer size
#define DEFAULT_OVERLAPPED_COUNT 5 // Number of overlapped recv per socket
#define MAX_COMPLETION_THREAD_COUNT 32 // Maximum number of completion threads allowed
int gAddressFamily = AF_UNSPEC, // default to unspecified
gSocketType = SOCK_STREAM, // default to TCP socket type
gProtocol = IPPROTO_TCP, // default to TCP protocol
gBufferSize = DEFAULT_BUFFER_SIZE,
gOverlappedCount = DEFAULT_OVERLAPPED_COUNT;
char *gBindAddr = NULL, // local interface to bind to
*gBindPort = "5150"; // local port to bind to
//
// This is our per I/O buffer. It contains a WSAOVERLAPPED structure as well
// as other necessary information for handling an IO operation on a socket.
//
typedef struct _BUFFER_OBJ
{
WSAOVERLAPPED ol;
SOCKET sclient; // Used for AcceptEx client socket
char *buf; // Buffer for recv/send/AcceptEx
int buflen; // Length of the buffer
int operation; // Type of operation issued
#define OP_ACCEPT 0 // AcceptEx
#define OP_READ 1 // WSARecv/WSARecvFrom
#define OP_WRITE 2 // WSASend/WSASendTo
SOCKADDR_STORAGE addr;
int addrlen;
ULONG IoOrder; // Order in which this I/O was posted
struct _BUFFER_OBJ *next;
} BUFFER_OBJ;
//
// This is our per socket buffer. It contains information about the socket handle
// which is returned from each GetQueuedCompletionStatus call.
//
typedef struct _SOCKET_OBJ
{
SOCKET s; // Socket handle
int af, // Address family of socket (AF_INET, AF_INET6)
bClosing; // Is the socket closing?
volatile LONG OutstandingOps; // Number of outstanding overlapped ops on
// socket
BUFFER_OBJ **PendingAccepts; // Pending AcceptEx buffers
// (used for listening sockets only)
ULONG LastSendIssued, // Last sequence number sent
IoCountIssued; // Next sequence number assigned to receives
BUFFER_OBJ *OutOfOrderSends;// List of send buffers that completed out of order
// Pointers to Microsoft specific extensions. These are used by listening
// sockets only
LPFN_ACCEPTEX lpfnAcceptEx;
LPFN_GETACCEPTEXSOCKADDRS lpfnGetAcceptExSockaddrs;
CRITICAL_SECTION SockCritSec; // Protect access to this structure
struct _SOCKET_OBJ *next;
} SOCKET_OBJ;
//
// Statistics counters
//
volatile LONG gBytesRead=0,
gBytesSent=0,
gStartTime=0,
gBytesReadLast=0,
gBytesSentLast=0,
gStartTimeLast=0;
//
// Function: usage
//
// Description:
// Prints usage information and exits the process.
//
void usage(char *progname)
{
fprintf(stderr, "usage: %s [-a 4|6] [-e port] [-l local-addr] [-p udp|tcp]\n",
progname);
fprintf(stderr, " -a 4|6 Address family, 4 = IPv4, 6 = IPv6 [default = IPv4]\n"
" -b size Buffer size for send/recv [default = %d]\n"
" -e port Port number [default = %s]\n"
" -l addr Local address to bind to [default INADDR_ANY for IPv4 or INADDR6_ANY for IPv6]\n"
" -p tcp|udp Which protocol to use [default = TCP]\n",
gBufferSize,
gBindPort
);
ExitProcess(-1);
}
void dbgprint(char *format,...)
{
#ifdef DEBUG
va_list vl;
char dbgbuf[2048];
if (pid == 0)
{
pid = GetCurrentProcessId();
}
va_start(vl, format);
wvsprintf(dbgbuf, format, vl);
va_end(vl);
OutputDebugString(dbgbuf);
#endif
}
//
// Function: GetBufferObj
//
// Description:
// Allocate a BUFFER_OBJ. Each receive posted allocates one of these.
// After the recv is successful, the BUFFER_OBJ is queued for
// sending by the send thread. To increase performance, a lookaside lists
// should be used to cache free BUFFER_OBJ.
//
BUFFER_OBJ *GetBufferObj(SOCKET_OBJ *sock, int buflen)
{
BUFFER_OBJ *newobj=NULL;
// Allocate the object
newobj = (BUFFER_OBJ *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(BUFFER_OBJ));
if (newobj == NULL)
{
fprintf(stderr, "GetBufferObj: HeapAlloc failed: %d\n", GetLastError());
ExitProcess(-1);
}
// Allocate the buffer
newobj->buf = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(BYTE) *buflen);
if (newobj->buf == NULL)
{
fprintf(stderr, "GetBufferObj: HeapAlloc failed: %d\n", GetLastError());
ExitProcess(-1);
}
newobj->buflen = buflen;
newobj->addrlen = sizeof(newobj->addr);
return newobj;
}
//
// Function: FreeBufferObj
//
// Description:
// Free the buffer object. To increase performance, a lookaside list should be
// implemented to cache BUFFER_OBJ when freed.
//
void FreeBufferObj(BUFFER_OBJ *obj)
{
HeapFree(GetProcessHeap(), 0, obj->buf);
HeapFree(GetProcessHeap(), 0, obj);
}
//
// Function: GetSocketObj
//
// Description:
// Allocate a socket object and initialize its members. A socket object is
// allocated for each socket created (either by socket or accept).
// Again, a lookaside list can be implemented to cache freed SOCKET_OBJ to
// improve performance.
//
SOCKET_OBJ *GetSocketObj(SOCKET s, int af)
{
SOCKET_OBJ *sockobj=NULL;
sockobj = (SOCKET_OBJ *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SOCKET_OBJ));
if (sockobj == NULL)
{
fprintf(stderr, "GetSocketObj: HeapAlloc failed: %d\n", GetLastError());
ExitProcess(-1);
}
// Initialize the members
sockobj->s = s;
sockobj->af = af;
// For TCP we initialize the IO count to one since the AcceptEx is posted
// to receive data
sockobj->IoCountIssued = ((gProtocol == IPPROTO_TCP) ? 1 : 0);
InitializeCriticalSection(&sockobj->SockCritSec);
return sockobj;
}
//
// Function: FreeSocketObj
//
// Description:
// Frees a socket object. If there are outstanding operations, the object
// is not freed.
//
void FreeSocketObj(SOCKET_OBJ *obj)
{
BUFFER_OBJ *ptr=NULL,
*tmp=NULL;
if (obj->OutstandingOps != 0)
{
// Still outstanding operations so just return
return;
}
// Close the socket if it hasn't already been closed
if (obj->s != INVALID_SOCKET)
{
closesocket(obj->s);
obj->s = INVALID_SOCKET;
}
DeleteCriticalSection(&obj->SockCritSec);
HeapFree(GetProcessHeap(), 0, obj);
}
//
// Function: ValidateArgs
//
// Description:
// Parses the command line arguments and sets up some global
// variables.
//
void ValidateArgs(int argc, char **argv)
{
int i;
for(i=1; i < argc ;i++)
{
if (((argv[i][0] != '/') && (argv[i][0] != '-')) || (strlen(argv[i]) < 2))
usage(argv[0]);
else
{
switch (tolower(argv[i][1]))
{
case 'a': // address family - IPv4 or IPv6
if (i+1 >= argc)
usage(argv[0]);
if (argv[i+1][0] == '4')
gAddressFamily = AF_INET;
else if (argv[i+1][0] == '6')
gAddressFamily = AF_INET6;
else
usage(argv[0]);
i++;
break;
case 'b': // buffer size for send/recv
if (i+1 >= argc)
usage(argv[0]);
gBufferSize = atol(argv[++i]);
break;
case 'e': // endpoint - port number
if (i+1 >= argc)
usage(argv[0]);
gBindPort = argv[++i];
break;
case 'l': // local address for binding
if (i+1 >= argc)
usage(argv[0]);
gBindAddr = argv[++i];
break;
case 'o': // overlapped count
if (i+1 >= argc)
usage(argv[0]);
gOverlappedCount = atol(argv[++i]);
break;
case 'p': // protocol - TCP or UDP
if (i+1 >= argc)
usage(argv[0]);
if (_strnicmp(argv[i+1], "tcp", 3) == 0)
{
gProtocol = IPPROTO_TCP;
gSocketType = SOCK_STREAM;
}
else if (_strnicmp(argv[i+1], "udp", 3) == 0)
{
gProtocol = IPPROTO_UDP;
gSocketType = SOCK_DGRAM;
}
else
usage(argv[0]);
i++;
break;
default:
usage(argv[0]);
break;
}
}
}
}
//
// Function: PrintStatistics
//
// Description:
// Print the send/recv statistics for the server
//
void PrintStatistics()
{
ULONG bps, tick, elapsed;
tick = GetTickCount();
elapsed = (tick - gStartTime) / 1000;
if (elapsed == 0)
return;
printf("\n");
// Calculate average bytes per second
bps = gBytesSent / elapsed;
printf("Average BPS sent: %lu [%lu]\n", bps, gBytesSent);
bps = gBytesRead / elapsed;
printf("Average BPS read: %lu [%lu]\n", bps, gBytesRead);
elapsed = (tick - gStartTimeLast) / 1000;
if (elapsed == 0)
return;
// Calculate bytes per second over the last X seconds
bps = gBytesSentLast / elapsed;
printf("Current BPS sent: %lu\n", bps);
bps = gBytesReadLast / elapsed;
printf("Current BPS read: %lu\n", bps);
InterlockedExchange(&gBytesSentLast, 0);
InterlockedExchange(&gBytesReadLast, 0);
gStartTimeLast = tick;
}
//
// Function: PostRecv
//
// Description:
// Post an overlapped receive operation on the socket.
//
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -