网络编程--纯重叠IO方式实现回声服务器

写在前面

重叠IO模型一文中只介绍了执行重叠IO的Sender和receiver,但还未利用该模型实现过回声服务器,因此,本文将在此基础上实现基于重叠IO模型的回声服务器。

ioctlsocket

ioctlsocket函数用于创建非阻塞模式的套接字。ioctlsocket用于控制IO方式,如下示例:

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

上述示例中调用的ioctlsocket函数赋值控制套接字hSock的IO方式,即将hSock句柄引用的套接字的IO模式(FIONBIO)改为遍历mode中指定的形式。

也就是说,FIONBIO是用于更改套接字IO模式的选项,该函数的第三个参数中传入的变量若存有0,则说明套接字是阻塞模式的;如果存有非0值,则说明已将套接字改为非阻塞模式。

改为非阻塞模式后,除了以非阻塞模式进行IO外,还具有如下特点:
①如果在没有客户端连接请求的状态下调用accept函数,将直接返回INVALID_SOCKET而不是阻塞等待。调用WSAGetLastError函数是返回WSAEWOULDBLOCK。
②调用accept函数返回时创建的套接字同意具有非阻塞属性。

因此,针对非阻塞套接字调用accept函数并返回INVALID_SOCKET时,应该通过WSAGetLastError函数确认返回INVALUE_SOCKET的理由,再进行相应处理。

因涉及接口均在重叠IO模型一文中介绍,这里不再赘述。

纯重叠IO方式实现回声服务器

// PureOverlappedServer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#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(&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); //进入可警告等待状态以执行Completion Routine函数
        SOCKET cltSock = accept(srvSock, (sockaddr*)&cltAddr, &nCltAddrSize);
        if (cltSock == INVALID_SOCKET)
        {
           //因为上面设置了非阻塞模式, 因此这里需判断是否是立即返回还是accept错误
            if (WSAGetLastError() == WSAEWOULDBLOCK)
            {
                //立即返回
                //puts("wait client connect...");
                continue;
            }
            else
            {
                puts("accept error!");
                break;
            }

            puts("Client connected...");

            //因会想多个客户端提供回声服务,因此这里每次WSARecv都需要使用不同的WSAOVERLAPPED变量
            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));

            //因为使用Completion Routine进行IO完成处理,因此这里可以借用hEvent成员保存IO信息,以便后面Completion Routine函数中使用
            //因为这里的lpOvlp会传递到Completion Routine函数的第三个LPWSAOVERLAPPED参数
            lpOvlp->hEvent = (HANDLE)lpIOData;

            WSARecv(cltSock, &(lpIOData->wsaBuf), 1, (LPDWORD) & nRecvLen, (LPDWORD)&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)
{
    //通过WSAOVERLAPPED的hEvent成员得到IO信息
    LPPER_IO_DATA lpIOData = (LPPER_IO_DATA)(lpOverlapped->hEvent);

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

    if (szRecvBytes == 0)
    {
        //客户端断开连接
        closesocket(cltSock);

        if (lpIOData != nullptr)
        {
            delete lpIOData;
            lpIOData = nullptr;
        }
        
        if (lpOverlapped != nullptr)
        {
            delete lpOverlapped;
            lpOverlapped = nullptr;
        }
  
    }//end if (szRecvBytes == 0)
    else
    {
        //回声
        lpIOData->wsaBuf.len = szRecvBytes;
        //同一客户端,读写可共用一个WSAOVERLAPPED变量
        WSASend(cltSock, &(lpIOData->wsaBuf), 1, &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, &(lpIOData->wsaBuf), 1, &dwReadBytes, &dwFlags, lpOverlapped, ReadCompRoutine);
}

客户端

// StableEchoClient.cpp : 定义控制台应用程序的入口点。
//

#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(&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\n") || !strcmp(msg, "Q\n"))
		{
			break;
		}

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

		nRecvLen = 0;
		while (true)
		{
			nRecvLen += recv(cltSock, msg, BUF_SIZE - 1 - nRecvLen, 0);
			//确保接收长度>=发送长度
			if (nRecvLen >= nSendLen)
			{
				break;
			}
		}

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

	}

	closesocket(cltSock);
	WSACleanup();

	return 0;
}

总结

基于重叠IO模型,实现了一个简单的重叠IO回声服务器/客户端,以加深使用WSASend和WSARecv函数中LPWSAOVERLAPPED参数和Competion Routine方式处理IO完成的理解。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
重叠IO模型之OverLapped完成例程模型WSACompletionRoutineServer VS2010 基础入门 客户端服务器客户端服务器端发送数据 可接收多个客户端 #include #include #pragma comment (lib, "ws2_32.lib") #define PORT 8088 #define MSG_SIZE 1024 SOCKET g_sConnect; bool g_bConnect = false; typedef struct { WSAOVERLAPPED overLap; WSABUF wsaBuf; char chMsg[MSG_SIZE]; DWORD nRecvNum; DWORD nFlags; SOCKET sClient; }PRE_IO_OPERATION_DATA, *LP_PER_IO_OPERATION_DATA; void CALLBACK CompletionRoutine(DWORD dwError, DWORD dwTrans, LPWSAOVERLAPPED lpOverlap, DWORD nFlags); DWORD WINAPI workThread(LPVOID lp) { LP_PER_IO_OPERATION_DATA lpData; while(TRUE) { if (g_bConnect) // 有新的连接 { // 为lpData分配空间并初始化 lpData = (LP_PER_IO_OPERATION_DATA)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PRE_IO_OPERATION_DATA)); lpData->wsaBuf.len = MSG_SIZE; lpData->wsaBuf.buf = lpData->chMsg; lpData->sClient = g_sConnect; WSARecv(lpData->sClient, &lpData->wsaBuf, 1, &lpData->nRecvNum, &lpData->nFlags, &lpData->overLap, CompletionRoutine); g_bConnect = false; // 处理完毕 } SleepEx(1000, TRUE); } return 0; } // 系统在WSARecv收到信息后,自动调用此函数,并传入参数--回调函数 void CALLBACK CompletionRoutine(DWORD dwError, DWORD dwTrans, LPWSAOVERLAPPED lpOverlap, DWORD nFlags) { LP_PER_IO_OPERATION_DATA lpData = (LP_PER_IO_OPERATION_DATA)lpOverlap; if (0 != dwError) // 接收失败 { printf("Socket %d Close!\n", lpData->sClient); closesocket(lpData->sClient); HeapFree(GetProcessHeap(), 0, lpData); } else // 接收成功 { lpData->chMsg[dwTrans] = '\0'; send(lpData->sClient, lpData->chMsg, dwTrans, 0); printf("Socket:%d MSG: %s \n", lpData->sClient, lpData->chMsg); memset(&lpData->overLap, 0, sizeof(WSAOVERLAPPED)); lpData->wsaBuf.len = MSG_SIZE; lpData->wsaBuf.buf = lpData->chMsg; // 继续接收来自客户端的数据 实现 WSARecv与CompletionRoutine循环 WSARecv(lpData->sClient, &lpData->wsaBuf,1, &lpData->nRecvNum, &lpData->nFlags, &lpData->overLap, CompletionRoutine); } } int main() { WSADATA wsaData; WSAStartup(0x0202, &wsaData); SOCKET sListen; sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in addrListen; addrListen.sin_family = AF_INET; addrListen.sin_port = htons(PORT); addrListen.sin_addr.S_un.S_addr = htonl(ADDR_ANY); int nErrorCode = 0; nErrorCode = bind(sListen, (sockaddr*)&addrListen, sizeof(sockaddr)); nErrorCode = listen(sListen, 5); DWORD nThreadID; CreateThread(NULL, 0, workThread, NULL, 0, &nThreadID); sockaddr_in addrConnect; int nAddrLen = sizeof(sockaddr_in); printf("Server Started!\n"); while(TRUE) { g_sConnect= accept(sListen, (sockaddr*)&addrConnect, &nAddrLen); if (INVALID_SOCKET == g_sConnect) { return -1; } g_bConnect = true; // 连接成功 printf("Accept Client :%s -- PORT:%d\n", inet_ntoa(addrConnect.sin_addr), htons(addrConnect.sin_port)); } return 0; }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值