Windows socket I/0 model – IOCP completion port model

In Windows network programming, IOCP (Input/Output Completion Port) is a high-performance I/O model that enables applications to handle a large number of concurrent I/O operations. The IOCP model mainly handles asynchronous I/O operations through event notification and callback functions. Compared with the previously introduced models such as select, WSAAsyncSelect and WSAEventSelect, the advantage of the IOCP model is that it can achieve higher concurrent processing capabilities, reduce the CPU overhead of I/O operations, and improve the overall performance of the system.

Using the IOCP model requires the following steps:

Create an I/O Completion Port
First the I/O completion port needs to be created, which can be done by calling the CreateIoCompletionPort() function. This function returns an I/O completion port handle, and all subsequent I/O operations need to be associated with this port.

Associate a socket with an I/O completion port
Next, the socket needs to be associated with an I/O completion port, which can be done by calling the CreateIoCompletionPort() function. After associating a socket with an I/O completion port, asynchronous I/O operations can begin.

Initiate an asynchronous I/O operation
Asynchronous I/O operations can be initiated using Windows system functions such as WSASend() and WSARecv(). When calling these functions, you need to pass a pointer to an OVERLAPPED structure as a parameter to the function, and you also need to pass the I/O completion port handle to the function as a parameter.

Wait for asynchronous I/O operations to complete
After an asynchronous I/O operation is initiated, the program needs to wait for the operation to complete and process the result of the operation. This can be achieved with the GetQueuedCompletionStatus() function, which blocks waiting for a completion event on the I/O completion port. When an I/O operation is completed, information such as the relevant socket handle, the number of completed bytes, and a pointer to the OVERLAPPED structure will be returned.

Handle results of asynchronous I/O operations
After obtaining the result of the asynchronous I/O operation, the program needs to process the result according to the specific situation. For example, after completing a data receiving operation, it is necessary to write the received data into the buffer, and continue to initiate the next data receiving operation.
case procedure
#include
#include
?
// Initialize the Winsock library
CInitSock theSock;
?
#define BUFFER_SIZE 1024
?
typedef struct _PER_HANDLE_DATA // per-handle data
{
SOCKET s; // corresponding socket handle
sockaddr_in addr; // client address
} PER_HANDLE_DATA, *PPER_HANDLE_DATA;
?
?
typedef struct _PER_IO_DATA // per-I/O data
{
OVERLAPPED ol; // overlapping structure
char buf[BUFFER_SIZE]; // data buffer
int nOperationType; // operation type
#define OP_READ 1
#define OP_WRITE 2
#define OP_ACCEPT 3
} PER_IO_DATA, PPER_IO_DATA;
?
?
DWORD WINAPI ServerThread(LPVOID lpParam)
{
// Get the completion port object handle
HANDLE hCompletion = (HANDLE) lpParam;
?
DWORD dwTrans;
PPER_HANDLE_DATA pPerHandle;
PPER_IO_DATA pPerIO;
while(TRUE)
{
// Wait for I/O to complete on all sockets associated to this completion port
BOOL bOK = ::GetQueuedCompletionStatus(hCompletion,
&dwTrans, (LPDWORD) &pPerHandle, (LPOVERLAPPED
) &pPerIO, WSA_INFINITE);
if(!bOK) // an error occurred on this socket
{
::closesocket(pPerHandle->s);
::GlobalFree(pPerHandle);
::GlobalFree(pPerIO);
continue;
}

if(dwTrans == 0 & amp; & amp; // The socket is closed by the other party
  (pPerIO->nOperationType == OP_READ || pPerIO->nOperationType == OP_WRITE))
  
{
  ::closesocket(pPerHandle->s);
  ::GlobalFree(pPerHandle);
  ::GlobalFree(pPerIO);
  continue;
}

?
switch(pPerIO->nOperationType) // Check what I/O request is completed through the nOperationType field in the per-I/O data
{
case OP_READ: // Complete a receive request
{
pPerIO->buf[dwTrans] = ‘\0’;
printf(pPerIO -> buf);

 // Continue to deliver and receive I/O requests
    WSABUF buf;
    buf.buf = pPerIO->buf;
    buf.len = BUFFER_SIZE;
    pPerIO->nOperationType = OP_READ;

?
DWORD nFlags = 0;
::WSARecv(pPerHandle->s, & amp;buf, 1, & amp;dwTrans, & amp;nFlags, & amp;pPerIO->ol, NULL);
}
break;
case OP_WRITE: // These types of I/O requests are not delivered in this example
case OP_ACCEPT:
break;
}
}
return 0;
}
?
?
void main()
{
int nPort = 4567;
// Create a completion port object, create a worker thread to process events in the completion port object
HANDLE hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
::CreateThread(NULL, 0, ServerThread, (LPVOID)hCompletion, 0, 0);
?
// Create a listening socket, bind it to a local address, and start listening
SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN si;
si.sin_family = AF_INET;
si.sin_port = ::ntohs(nPort);
si.sin_addr.S_un.S_addr = INADDR_ANY;
::bind(sListen, (sockaddr*) &si, sizeof(si));
::listen(sListen, 5);
?
// loop through incoming connections
while(TRUE)
{
// Wait for pending connection requests to be accepted
SOCKADDR_IN saRemote;
int nRemoteLen = sizeof(saRemote);
SOCKET sNew = ::accept(sListen, (sockaddr*) &saRemote, &nRemoteLen);
?
// After accepting a new connection, create a per-handle data for it and associate them with the completion port object.
PPER_HANDLE_DATA pPerHandle =
(PPER_HANDLE_DATA)::GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));
pPerHandle->s = sNew;
memcpy( &pPerHandle->addr, &saRemote, nRemoteLen);
::CreateIoCompletionPort((HANDLE)pPerHandle->s, hCompletion, (DWORD)pPerHandle, 0);

// Post a receive request
PPER_IO_DATA pPerIO = (PPER_IO_DATA)::GlobalAlloc(GPTR, sizeof(PER_IO_DATA));
pPerIO->nOperationType = OP_READ;
WSABUF buf;
buf.buf = pPerIO->buf;
buf.len = BUFFER_SIZE;
DWORD dwRecv;
DWORD dwFlags = 0;
::WSARecv(pPerHandle->s, &buf, 1, &dwRecv, &dwFlags, &pPerIO->ol, NULL);

}
}
?
Official Account: Self-Cultivation of Safety Dogs

Douyin: haidragon

bibi: haidragonx