<一> IOCP 实现步骤
如果懂得了 IOCP 的工作原理,它实现起来是很简单的。
它的实现步骤如下:
1. 创建好 IOCP
2. 创建 Socket ( socket 可以是由 Accept 得到)
3. 将 Socket 关联到 IOCP
4. socket 向 IOCP 提交各种所需请求
5. IOCP 操作完成之后将结果返回给 socket
6. 重复步骤 3 和 4 ,直到 socket 关闭
它就那么几个步骤,但实现起来需要不少的代码。以下就以创建一个客户端的 socket 为例,先做部分的讲解。这里主要讲解原理,函数的参数和返回值先忽略。
1 . // 创建 IOCP
// 利用函数 CreateIoCompletionPort 创建IOCP
// 注意参数设置
m_hIocp = CreateIoCompletionPort (INVALID_HANDLE_VALUE ,NULL ,(ULONG_PTR )0,0);
// m_hIocp 就代表一个完成端口了
2 .// 创建连接型的 Socket
// 利用 利用函数WSASocket 创建 socket ,必须指定 WSA_FLAG_OVERLAPPED
m_sockClient = ::WSASocket (AF_INET ,
SOCK_STREAM , IPPROTO_TCP , NULL , NULL , WSA_FLAG_OVERLAPPED );
// m_sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 这样也可以
// 默认为WSA_FLAG_OVERLAPPED
// 以下的绑定很重要,也是容易漏掉的。(如果少了绑定,在 ConnextEx 时将得到错误代码:10022 — 提供了一个无效的参数
sockaddr_in local_addr ;
ZeroMemory (&local_addr , sizeof (sockaddr_in ));
local_addr .sin_family = AF_INET ;
int irt = ::bind (m_sockClient , (sockaddr *)(&local_addr ), sizeof (sockaddr_in ));
// m_sockClient 是创建好的socket
3 .// 将 Socket 关联到 IOCP
// 又利用上了CreateIoCompletionPort ,它将socket 关联到IOCP
CreateIoCompletionPort ((HANDLE )m_sockClient ,m_hIocp , (ULONG_PTR )m_sockClient ,0);
// 已将sockClient 关联到m_hIocp
// (ULONG_PTR)m_sockClient 为以后识别是那个socket 的操作
4.// socket 向 IOCP 提交各种所需请求 , 这里提交的是连接请求
// 代码比较长,将整个函数都拷贝了过来
BOOL CIOCP_ClientDlg ::ConnectToServer ()
{
//----------------------
LPFN_CONNECTEX m_lpfnConnectEx = NULL ;
DWORD dwBytes = 0;
GUID GuidConnectEx = WSAID_CONNECTEX ;
// 重点,获得ConnectEx 函数的指针
if (SOCKET_ERROR == WSAIoctl (m_sockClient , SIO_GET_EXTENSION_FUNCTION_POINTER ,
&GuidConnectEx , sizeof (GuidConnectEx ),
&m_lpfnConnectEx , sizeof (m_lpfnConnectEx ), &dwBytes , 0, 0))
{
TRACE ( "WSAIoctl is failed. Error code = %d" , WSAGetLastError ());
return FALSE ;
}
MYOVERLAPPED *pmyoverlapped = new MYOVERLAPPED ; // socket 和I/O 通讯的载体
pmyoverlapped ->operateType = OP_CONNECT ; // 设置请求类型,得到I/O 结果时根据此 // // 来识别请求类型
pmyoverlapped ->hEvent = NULL ; // 非事件模型
// 设置连接目标地址
sockaddr_in addrPeer ;
ZeroMemory (&addrPeer , sizeof (sockaddr_in ));
addrPeer .sin_family = AF_INET ;
addrPeer .sin_addr .s_addr = inet_addr ( "192.168.0.15" );
addrPeer .sin_port = htons ( 5400 );
int nLen = sizeof (addrPeer );
PVOID lpSendBuffer = NULL;
DWORD dwSendDataLength = 0;
DWORD dwBytesSent = 0;
// 重点
BOOL bResult = m_lpfnConnectEx (m_sockClient ,
(sockaddr *)&addrPeer , // [in] 对方地址
nLen , // [in] 对方地址长度
lpSendBuffer , // [in] 连接后要发送的内容,这里不用
dwSendDataLength , // [in] 发送内容的字节数 ,这里不用
&dwBytesSent , // [out] 发送了多少个字节,这里不用
(OVERLAPPED *)pmyoverlapped ); // [in] 这东西复杂,下一篇有详解
if (!bResult ) // 返回值处理
{
if ( WSAGetLastError () != ERROR_IO_PENDING ) // 调用失败
{
TRACE (TEXT ("ConnextEx error: %d/n" ),WSAGetLastError ());
return FALSE ;
}
else ;// 操作未决(正在进行中 … )
{
TRACE0 ("WSAGetLastError() == ERROR_IO_PENDING/n" );// 操作正在进行中
}
}
return TRUE ;
}
// 在这个函数中重点是 WSAIoctl 函数(得到ConnectEx 函数地址)和m_lpfnConnectEx 函数指针(进行ConnectEx 调用,也就是向I/O 提交连接对方的请求)。
// 这样,实现了向I/O 提交Connect 请求。
5 . // IOCP 操作完成之后将结果返回给 socket
// 提交请求之后, sokcet 是如何得到结果的呢? 靠的是主动要到 I/O 的出口那里等 // 待,直到拿到数据。一般都有专门的一条或多条线程在等候结果。重点在 // GetQueuedCompletionStatus 函数。
void CIOCP_ClientDlg ::IocpWorkerThread ()
{
MYOVERLAPPED *lpOverlapped = NULL ;
DWORD dwByteTransfered = 0;
ULONG_PTR *PerHandleKey = NULL ;
while (1)
{
lpOverlapped = NULL ;
if (m_hIocp == NULL )
{
break ;
}
// 下面的函数调用就是去I/O 出口那里等待,并获得I/O 操作结果
BOOL bResult = GetQueuedCompletionStatus (
m_hIocp , // 指定从哪个IOCP 那里或地数据
&dwByteTransfered , // 或得或是发送了多少字节的数据
(PULONG_PTR )&PerHandleKey , // socket 关联到IOCP 时指定的一个关联值
(LPWSAOVERLAPPED *)&lpOverlapped , // 或得ConnectEx 传进来的结构
INFINITE ); // 一直等待,直到有结果
......// 处理从I/O 获得的数据,代码很多,省略!
}
}
// 如果ConnecEx 是成功的,那么到GetQueuedCompletionStatus 调用结束,m_sockClient 则是可以用来发送和接收数据的socket 了。
6 . // 重复步骤 3 和 4 ,直到 socket 关闭
// 现在可以用 m_sockClient 来发送和接收数据了。
// 下面提交接收请求
WSARecv (socket , &lpOverlapped ->wsabuf , 1,
&lpOverlapped ->dwByteRecvSend , &Flags ,
( LPWSAOVERLAPPED )lpOverlapped , NULL );
// 下面提交发送请求
int nResult = WSASend (
socket ,
&pmyoverlapped ->wsabuf , // WSABUF 指针,发送数据在这里
1, // WSABUF 指针指向的数组大小
&pmyoverlapped ->dwByteRecvSend , // 实际发送字节数
0,
(LPWSAOVERLAPPED )pmyoverlapped , //OVERLAPPED 结构
0);
IOCP 的客户端实现步骤也就上面的六点。一个socket 能够连接对方,主要就在ConnectEx ,GetQueuedCompletionStatus 得到的是 ConnectEx 的结果是否成功。
上面的例子展示的是如何实现一个客户端,服务器端的实现也差不多,就是创建socket- 〉绑定socket- 〉监听- 〉接受连接。