VS2015实现套接字完成端口模型(4-4)

代码下载地址

3.7 CClient

接下来为项目添加CClient类用于在套接字上实现数据发送和接收。

3.7.1 创建CClient

VS2015左侧“解决方案资源管理器”中选中“IOCP_Server”项目,之后在右键菜单中选择“添加->类”,如图3所示。

 

3 为项目添加类

之后,在添加类的对话框中选择添加C++类,之后点击“添加”按键,如图4所示。

 

4 添加C++

接下来将类名设置为CClient”,其它选项自动生成即可,如图5所示。

 

5 添加CClient

最后,点击完成按键。这样就在项目中添加了一个名为CClient的类,该类对应的文件为Client.hClient.cpp

3.7.2 构造函数

Client类中需要包含套接字变量以及客户端网络信息变量,因此在Client类的构造函数中需要对这两个变量进行初始化。

CClient::CClient(SOCKET s, SOCKADDR_IN addr)

{

m_s = s;

m_addr = addr;

}

其中m_s表示与客户端通信的套接字;m_addr表示客户端的网络信息。以上两个变量均为CClient类的private成员变量。

SOCKET m_s;

SOCKADDR_IN m_addr;

3.7.3 析构函数

CClient类的析构函数中,关闭与客户端通信的套接字。

CClient::~CClient()

{

closesocket(m_s);

}

3.7.4 自定义数据结构

CClient类中自定义数据结构

typedef struct _io_operation_data

{

OVERLAPPED overlapped;

WSABUF dataBuf;

BYTE type;

}IO_OPERATION_DATA, *PIO_OPERATION_DATA;

该结构的成员包含了重叠I/O结构OVERAPPED、数据缓冲区结构WSABUF、用于保存发送/接收数据的缓冲区和长度、以及I/O操作类型标志等。

(1)OVERAPPED对象

OVERAPPED对象overlapped主要用在服务端发出重叠I/O请求,例如接收数据或者发送数据时使用。线程池中的服务线程在被激活时,即“2 相关API函数”中提到的GetQueuedCompletionStatus()函数返回时,该函数的第四个参数就保存了该OVERAPPED对象。在服务线程中,通过GetQueuedCompletionStatus()函数获取到的OVERAPPED对象,就可以得到自定义结构IO_OPERATION_DATA的指针,进而在服务线程中获取重叠I/O操作的类型。

2)重叠I/O操作类型

1.2 完成端口模型基本原理”中提到,线程池中的服务线程获取消息队列中的完成通知,并在服务线程中处理套接字数据。但是服务线程只是知道有重叠I/O操作完成,但是无法知道完成的这个重叠I/O操作到底是读操作还是写操作。自定义结构IO_OPERATION_DATA中的成员变量type的作用就是指定重叠I/O操作到底是读操作还是写操作。

3)缓冲区

重叠I/O操作的函数WSAWRecv()WSASend()都需要WSABUF结构为缓冲区。因此,自定义结构IO_OPERATION_DATA中的WSABUF结构的对象dataBuf,用于指定重叠I/O操作的缓冲区。

3.7.5 发送数据

CClient类添加public权限的成员函数Send(),该函数的作用是发出发送数据的重叠I/O请求。

bool CClient::Send(char* sendBuf, int length)

{

DWORD flags = 0;

DWORD sendBytes = 0;

m_IO.type = WRITE;

m_IO.dataBuf.buf = sendBuf;

m_IO.dataBuf.len = length;

 

if (WSASend(m_s, &m_IO.dataBuf, 1, &sendBytes, flags, (LPOVERLAPPED)&(m_IO.overlapped), NULL) == SOCKET_ERROR)

{

return false;

}

return true;

}

在以上代码中,m_IO是定义结构IO_OPERATION_DATA的对象,将type定义为WRITE,即写操作,WRITE为自定义常量。

#define WRITE 1

之后,通过WSASend()函数发出发送数据的重叠I/O请求,m_IO.dataBuf中保存了要发送数据的缓冲区及缓冲区大小。

3.7.6 接收数据

CClient类添加public权限的成员函数Recv(),该函数的作用是发出接收数据的重叠I/O请求。

bool CClient::Recv()

{

DWORD flags = 0;

DWORD recvBytes = 0;

ZeroMemory(&m_IO, sizeof(IO_OPERATION_DATA));

m_IO.type = READ;

 

if (WSARecv(

m_s

, &m_IO.dataBuf

, 1

, &recvBytes

, &flags

, &m_IO.overlapped

, NULL

) == SOCKET_ERROR)

{

if (ERROR_IO_PENDING != WSAGetLastError())

{

return false;

}

}

return true;

}

在以上代码中,READ是自定义常量,表示重叠I/O操作是读操作,该变量定义如下

#define READ 0

当接收数据的重叠I/O操作完成之后,接收到的数据保存在m_IO.dataBuf中。在线程池中的服务线程此时会通过GetQueuedCompletionStatus()函数获取到重叠I/O结构的指针,即在WSARecv()函数中指定的&m_IO.overlapped。通过该指针,线程池中的服务线程会得到完成的重叠I/O操作的类型,即m_IO.type

3.7.7 处理数据

在通过重叠I/O操作接收到数据之后,数据保存在m_IO.dataBuf中。之后可以对接收到的数据进行操作。

CClient定义public权限的成员变量Handle(),在该函数中,对m_IO.dataBuf进行处理。

3.8 完成端口与套接字关联

通过“2 相关API函数”中提到的CreateIoCompletionPort()函数将完成端口与套接字关联。

3.6 接收客户端的连接”中提到,在while语句中,服务端调用WSAAccept()函数接收客户端的连接。如果没有客户端连接,WSAAccept()函数不会返回,主程序将会挂起;当有客户端连接,WSAAccept()函数返回一个新建的套接字sAccept,服务端使用这个新建的套接字sAccept与客户端进行通信。接下来,仍然在while语句中,使用CreateIoCompletionPort()函数将完成端口与套接字关联。

CClient* pClient = new CClient(sAccept, servAddr);

if (CreateIoCompletionPort((HANDLE)sAccept, hComPort, (DWORD)pClient, 0) == NULL)

{

return -1;

}

其中,pClient是“3.7 CClient类”中提到的CClient类的指针,该指针作为CreateIoCompletionPort()函数的第三个参数,即完成键使用。在线程池的服务线程中,只知道有重叠I/O操作完成,但是并不知道该重叠I/O操作是发生在哪个套接字上的。因此,将CClient类的指针pClient作为完成键,在线程池的服务线程中,可以通过GetQueuedCompletionStatus()函数获取这个完成键,即获取到了Client类的指针,也就是知道了完成重叠I/O操作的套接字的信息了。

猜你喜欢

转载自blog.csdn.net/hou09tian/article/details/80912158