Network programming–realize echo server in pure overlapping IO mode

Write in front

The overlapped IO model only introduces the Sender and receiver that perform overlapped IO, but the echo server has not been implemented using this model. Therefore, this article will implement an echo server based on the overlapped IO model on this basis.

ioctlsocket

The ioctlsocket function is used to create a socket in non-blocking mode. ioctlsocket is used to control the IO mode, as shown in the following example:

SOCKET hSock = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, WSA_FLAG_OVERLAPPED);
int mode = 1;
ioctlsocket(hSock, FIONBIO, &mode);

The assignment of the ioctlsocket function called in the above example controls the IO mode of the socket hSock, that is, the IO mode (FIONBIO) of the socket referenced by the hSock handle is changed to the form specified in the traversal mode.

In other words, FIONBIO is an option for changing the socket IO mode. If the variable passed in the third parameter of the function contains 0, it means that the socket is in blocking mode; if there is a non-zero value , the socket has been changed to non-blocking mode.

After changing to non-blocking mode, in addition to performing IO in non-blocking mode, it also has the following characteristics:
① If the accept function is called without a client connection request, it will directly return INVALID_SOCKET instead of blocking and waiting. Calling the WSAGetLastError function returns WSAEWOULDBLOCK.
② The socket created when calling the accept function returns agrees to have non-blocking properties.

Therefore, when the accept function is called for a non-blocking socket and INVALID_SOCKET is returned, the reason for returning INVALUE_SOCKET should be confirmed through the WSAGetLastError function, and then processed accordingly.

Since the interfaces involved are all introduced in the article Overlapping IO Model, details will not be repeated here.

Echo server implemented in pure overlapping IO

// PureOverlappedServer.cpp : This file contains the "main" function. Program execution will start and end here.
//

#include <iostream>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")

#define BUF_SIZE 1024

void CALLBACK ReadCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
void CALLBACK WriteCOmpRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);

typedef struct
{
    SOCKET hCltSock;
    char buf[BUF_SIZE];
    WSABUF wsaBuf;
}PER_IO_DATA, *LPPER_IO_DATA;


int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        puts("argc error!");
        return -1;
    }

    WSADATA wsaData;
    if (0 != WSAStartup(MAKEWORD(2, 2), &wsaData))
    {
        puts("WSAStartup error!");
        return -1;
    }

    SOCKET srvSock = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (srvSock == INVALID_SOCKET)
    {
        puts("WSASocket error!");
        WSACleanup();
        return -1;
    }
    
    int nMode = 1;
    ioctlsocket(srvSock, FIONBIO, (u_long*) & nMode);

    SOCKADDR_IN srvAddr;
    memset( & amp; srvAddr, 0, sizeof(srvAddr));
    srvAddr.sin_family = PF_INET;
    srvAddr.sin_addr.s_addr = htonl(ADDR_ANY);
    srvAddr. sin_port = htons(atoi(argv[1]));

    if (SOCKET_ERROR == bind(srvSock, (sockaddr*) & srvAddr, sizeof(srvAddr)))
    {
        puts("bind error!");
        closesocket(srvSock);
        WSACleanup();
        return -1;
    }

    if (SOCKET_ERROR == listen(srvSock, 5))
    {
        puts("listen error!");
        closesocket(srvSock);
        WSACleanup();
        return -1;
    }

    SOCKADDR_IN cltAddr;
    memset( &cltAddr, 0, sizeof(cltAddr));
    int nCltAddrSize = sizeof(cltAddr);

    int nRecvLen = 0;
    int nFlagInfo = 0;

    while (true)
    {
        SleepEx(100, TRUE); //Enter the warning waiting state to execute the Completion Routine function
        SOCKET cltSock = accept(srvSock, (sockaddr*) &cltAddr, &nCltAddrSize);
        if (cltSock == INVALID_SOCKET)
        {
           //Because the non-blocking mode is set above, it is necessary to judge whether it is an immediate return or an accept error
            if (WSAGetLastError() == WSAEWOULDBLOCK)
            {
                //return immediately
                //puts("wait client connect...");
                continue;
            }
            else
            {
                puts("accept error!");
                break;
            }

            puts("Client connected...");

            //Because multiple clients will want to provide echo services, so each WSARecv here needs to use a different WSAOVERLAPPED variable
            LPPER_IO_DATA lpIOData = new PER_IO_DATA;
            lpIOData->hCltSock = cltSock;
            lpIOData->wsaBuf.buf = lpIOData->buf;
            lpIOData->wsaBuf.len = BUF_SIZE;

            LPWSAOVERLAPPED lpOvlp = new WSAOVERLAPPED;
            memset(lpOvlp, 0, sizeof(WSAOVERLAPPED));

            //Because the Completion Routine is used for IO completion processing, the hEvent member can be borrowed here to save the IO information for later use in the Completion Routine function
            //Because the lpOvlp here will be passed to the third LPWSAOVERLAPPED parameter of the Completion Routine function
            lpOvlp->hEvent = (HANDLE)lpIOData;

            WSARecv(cltSock, & amp;(lpIOData->wsaBuf), 1, (LPDWORD) & amp; nRecvLen, (LPDWORD) & amp;nFlagInfo, lpOvlp, ReadCompRoutine);

        }
    }//end while (true)

    closesocket(srvSock);
    WSACleanup();

    puts("end main");

    return 0;
}
void CALLBACK ReadCompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
    //Get the IO information through the hEvent member of WSAOVERLAPPED
    LPPER_IO_DATA lpIOData = (LPPER_IO_DATA)(lpOverlapped->hEvent);

    SOCKET cltSock = lpIOData->hCltSock;
    LPWSABUF wsaBuf = &(lpIOData->wsaBuf);
    DWORD dwSendBytes = 0;

    if (szRecvBytes == 0)
    {
        //client disconnect
        closesocket(cltSock);

        if (lpIOData != nullptr)
        {
            delete lpIOData;
            lpIOData = nullptr;
        }
        
        if (lpOverlapped != nullptr)
        {
            delete lpOverlapped;
            lpOverlapped = nullptr;
        }
  
    }//end if (szRecvBytes == 0)
    else
    {
        //echo
        lpIOData->wsaBuf.len = szRecvBytes;
        //The same client, read and write can share a WSAOVERLAPPED variable
        WSASend(cltSock, & amp;(lpIOData->wsaBuf), 1, & amp;dwSendBytes, 0, lpOverlapped, WriteCOmpRoutine);
    }
}

void CALLBACK WriteCOmpRoutine(DWORD dwError, DWORD szSendBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
    LPPER_IO_DATA lpIOData = (LPPER_IO_DATA)lpOverlapped->hEvent;

    SOCKET cltSock = lpIOData->hCltSock;

    WSABUF wsaBuf = lpIOData->wsaBuf;

    DWORD dwReadBytes = 0;
    DWORD dwFlags = 0;

    WSARecv(cltSock, & amp;(lpIOData->wsaBuf), 1, & amp;dwReadBytes, & amp;dwFlags, lpOverlapped, ReadCompRoutine);
}

client

// StableEchoClient.cpp : Defines the entry point of the console application.
//

#include "stdafx.h"
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")

#define BUF_SIZE 1024


int _tmain(int argc, _TCHAR* argv[])
{
if (argc != 3)
{
return -1;
}

WSADATA wsaData;
if (0 != WSAStartup(MAKEWORD(2, 2), &wsaData))
{
return -1;
}

SOCKET cltSock = socket(PF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == cltSock)
{
return -1;
}

SOCKADDR_IN srvAddr;
memset( & amp; srvAddr, 0, sizeof(srvAddr));
srvAddr.sin_family = PF_INET;
srvAddr. sin_addr. s_addr = inet_addr(argv[1]);
srvAddr. sin_port = htons(_ttoi(argv[2]));

if (SOCKET_ERROR == connect(cltSock, (sockaddr*) & srvAddr, sizeof(srvAddr)))
{
return -1;
}

char msg[BUF_SIZE] = {};

int nSendLen = 0, nRecvLen = 0;
while (true)
{
fputs("Input msg(Q to quit): ", stdout);
fgets(msg, BUF_SIZE, stdin);
if (!strcmp(msg, "q\\
") || !strcmp(msg, "Q\\
"))
{
break;
}

nSendLen = strlen(msg);
send(cltSock, msg, nSendLen, 0);

nRecvLen = 0;
while (true)
{
nRecvLen + = recv(cltSock, msg, BUF_SIZE - 1 - nRecvLen, 0);
//Make sure the receive length >= send length
if (nRecvLen >= nSendLen)
{
break;
}
}

msg[nRecvLen] = 0;
printf("msg from server: %s\\
", msg);

}

closesocket(cltSock);
WSACleanup();

return 0;
}

Summary

Based on the overlapping IO model, a simple overlapping IO echo server/client is implemented to deepen the understanding of using the LPWSAOVERLAPPED parameter and Competion Routine in the WSASend and WSARecv functions to process IO completion.