📄 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 how to write a scalable, high-performance
// Winsock server. This is implemented as a TCP (IPv4/IPv6) server
// designed to handle many connections simultaneously. The purpose
// of this server is an echo server. For each accepted connection,
// data is read and then sent back to the client. Several limitations
// are present to ensure that as many concurrent connections can
// be handled. First, each connection has only a single overlapped
// receive posted at any given time. Second, each connection may only
// have a maximum of five outstanding overlapped sends. This prevents
// a malicious client from connecting and only sending data -- this
// would cause an unlimited number of data to be buffered since the
// sends would block as the client is not receiving data (and the TCP
// window size goes to zero).
//
// This sample illustrates overlapped IO with a completion port for
// TCP 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.
//
// 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]
// -os count Maximum number of overlapped send operations to allow simultaneously (per socket)
// -oa count Maximum number of overlapped accepts to allow simultaneously
// -o count Number of initial overlapped accepts to post
//
#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_OVERLAPPED_ACCEPTS 500
#define MAX_OVERLAPPED_SENDS 200
#define MAX_OVERLAPPED_RECVS 200
#define MAX_COMPLETION_THREAD_COUNT 32 // Maximum number of completion threads allowed
#define BURST_ACCEPT_COUNT 100
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,
gInitialAccepts= DEFAULT_OVERLAPPED_COUNT,
gMaxAccepts = MAX_OVERLAPPED_ACCEPTS,
gMaxReceives = MAX_OVERLAPPED_RECVS,
gMaxSends = MAX_OVERLAPPED_SENDS;
char *gBindAddr = NULL, // local interface to bind to
*gBindPort = "5150"; // local port to bind to
//
// Statistics counters
//
volatile LONG gBytesRead=0,
gBytesSent=0,
gStartTime=0,
gBytesReadLast=0,
gBytesSentLast=0,
gStartTimeLast=0,
gConnections=0,
gConnectionsLast=0,
gOutstandingSends=0;
//
// 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
HANDLE PostAccept;
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;
struct _SOCKET_OBJ *sock;
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 OutstandingRecv, // Number of outstanding overlapped ops on
OutstandingSend,
PendingSend;
CRITICAL_SECTION SockCritSec; // Protect access to this structure
struct _SOCKET_OBJ *next;
} SOCKET_OBJ;
//
//
//
typedef struct _LISTEN_OBJ
{
SOCKET s;
int AddressFamily;
BUFFER_OBJ *PendingAccepts; // Pending AcceptEx buffers
volatile long PendingAcceptCount;
int HiWaterMark,
LoWaterMark;
HANDLE AcceptEvent;
HANDLE RepostAccept;
volatile long RepostCount;
// Pointers to Microsoft specific extensions.
LPFN_ACCEPTEX lpfnAcceptEx;
LPFN_GETACCEPTEXSOCKADDRS lpfnGetAcceptExSockaddrs;
CRITICAL_SECTION ListenCritSec;
struct _LISTEN_OBJ *next;
} LISTEN_OBJ;
// Serialize access to the free lists below
CRITICAL_SECTION gBufferListCs,
gSocketListCs,
gPendingCritSec;
// Lookaside lists for free buffers and socket objects
BUFFER_OBJ *gFreeBufferList=NULL;
SOCKET_OBJ *gFreeSocketList=NULL;
BUFFER_OBJ *gPendingSendList=NULL,
*gPendingSendListEnd=NULL;
int PostSend(SOCKET_OBJ *sock, BUFFER_OBJ *sendobj);
int PostRecv(SOCKET_OBJ *sock, BUFFER_OBJ *recvobj);
void FreeBufferObj(BUFFER_OBJ *obj);
//
// 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"
" -oa count Maximum overlapped accepts to allow\n"
" -os count Maximum overlapped sends to allow\n"
" -or count Maximum overlapped receives to allow\n"
" -o count Initial number of overlapped accepts to post\n",
gBufferSize,
gBindPort
);
ExitProcess(-1);
}
//
// Function: dbgprint
//
// Description:
// Prints a message if compiled with the DEBUG flag.
//
void dbgprint(char *format,...)
{
#ifdef DEBUG
va_list vl;
char dbgbuf[2048];
va_start(vl, format);
wvsprintf(dbgbuf, format, vl);
va_end(vl);
printf(dbgbuf);
OutputDebugString(dbgbuf);
#endif
}
//
// Function: EnqueuePendingOperation
//
// Description:
// Enqueues a buffer object into a list (at the end).
//
void EnqueuePendingOperation(BUFFER_OBJ **head, BUFFER_OBJ **end, BUFFER_OBJ *obj, int op)
{
EnterCriticalSection(&gPendingCritSec);
if (op == OP_READ)
;
else if (op == OP_WRITE)
InterlockedIncrement(&obj->sock->PendingSend);
obj->next = NULL;
if (*end)
{
(*end)->next = obj;
(*end) = obj;
}
else
{
(*head) = (*end) = obj;
}
LeaveCriticalSection(&gPendingCritSec);
return;
}
//
// Function: DequeuePendingOperation
//
// Description:
// Dequeues the first entry in the list.
//
BUFFER_OBJ *DequeuePendingOperation(BUFFER_OBJ **head, BUFFER_OBJ **end, int op)
{
BUFFER_OBJ *obj=NULL;
EnterCriticalSection(&gPendingCritSec);
if (*head)
{
obj = *head;
(*head) = obj->next;
// If next is NULL, no more objects are in the queue
if (obj->next == NULL)
{
(*end) = NULL;
}
if (op == OP_READ)
;
else if (op == OP_WRITE)
InterlockedDecrement(&obj->sock->PendingSend);
}
LeaveCriticalSection(&gPendingCritSec);
return obj;
}
//
// Function: ProcessPendingOperations
//
// Description:
// This function goes through the list of pending send operations and posts them
// as long as the maximum number of ouststanding sends is not exceeded.
//
void ProcessPendingOperations()
{
BUFFER_OBJ *sendobj=NULL;
while(gOutstandingSends < gMaxSends)
{
sendobj = DequeuePendingOperation(&gPendingSendList, &gPendingSendListEnd, OP_WRITE);
if (sendobj)
{
if (PostSend(sendobj->sock, sendobj) == SOCKET_ERROR)
{
// Cleanup
printf("ProcessPendingOperations: PostSend failed!\n");
FreeBufferObj(sendobj);
break;
}
}
else
{
break;
}
}
return;
}
//
// Function: InsertPendingAccept
//
// Description:
// Inserts a pending accept operation into the listening object.
//
void InsertPendingAccept(LISTEN_OBJ *listenobj, BUFFER_OBJ *obj)
{
obj->next = NULL;
EnterCriticalSection(&listenobj->ListenCritSec);
if (listenobj->PendingAccepts == NULL)
{
listenobj->PendingAccepts = obj;
}
else
{
// Insert at head - order doesn't really matter
obj->next = listenobj->PendingAccepts;
listenobj->PendingAccepts = obj;
}
LeaveCriticalSection(&listenobj->ListenCritSec);
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -