写在前面
重叠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完成的理解。