<一>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_CONNECTEXm_lpfnConnectEx = NULL;
DWORDdwBytes = 0;
GUIDGuidConnectEx = 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());
returnFALSE;
}
MYOVERLAPPED *pmyoverlapped= new MYOVERLAPPED;// socket和I/O通讯的载体
pmyoverlapped->operateType = OP_CONNECT; // 设置请求类型,得到I/O结果时根据此 // //来识别请求类型
pmyoverlapped->hEvent = NULL; // 非事件模型
// 设置连接目标地址
sockaddr_inaddrPeer;
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 );
intnLen = sizeof(addrPeer);
PVOID lpSendBuffer = NULL;
DWORDdwSendDataLength = 0;
DWORD dwBytesSent =0;
// 重点
BOOLbResult = 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("ConnextExerror: %d/n"),WSAGetLastError());
returnFALSE;
}
else;//操作未决(正在进行中…)
{
TRACE0("WSAGetLastError() == ERROR_IO_PENDING/n");// 操作正在进行中
}
}
returnTRUE;
}
// 在这个函数中重点是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-〉监听-〉接受连接。