重拾基础,今天要来说一下关于网络的那些事,特意找了一些资料来学习笔记。iocp是Windows中比较经典的一个模型,这个模型堪称全异步通信,所以找了实例强悍的一位大牛来学习分析。先看主要的API
HANDLE CreateIoCompletionPort(HANDLE fileHandle, //连入的套接字句柄,没有置空
HANDLE ExistingCompletionPort, //已经创建的完成端口,没有置空
ULONG_PTR CompletionKey, //类似于线程参数,绑定的时候传入自定义的结构体指针,此时收到事件时,可以知道是那个文件句柄上的事件。
DWORD NumberOfConcurrentThreads //用于监听的线程数
);
其应用如下:
typedef struct _IO_CONTEXT
{
OVERLAPPED m_Overlapped; //重叠结构
SOCKET mSockClient; //接收的客户端套接字
WSABUF m_wsaBuf; //缓存变量
char m_szBuffer[1024]; //默认缓存地址,默认大小为1024
int m_type; //操作类型,是发送接收还是收到连接请求等
}IO_CONTEXT,*PIO_CONTEXT;
typedef struct _CLIENT_CONTEXT
{
SOCKET m_socket; //套接字
sockaddr_in m_addr; //套接字地址
std::vector<PIO_CONTEXT> mIOContexts; //套接字的缓存结构
}CLIENT_CONTEXT,PCLIENT_CONTEXT;
HANDLE m_hIOCompletionPort = 0; // 完成端口的句柄
CLIENT_CONTEXT *m_pListenSocket;
m_pListenSocket = new CLIENT_CONTEXT;
m_pListenSocket->m_socket = WSASocket(AF_INET,SOCK_STREAM,0,NULL,WSA_FLAG_OVERLAPPED);
m_hIOCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0 );
if(m_hIOCompletionPort != 0)
{
CreateIoCompletionPort((HANDLE)m_pListenSocket->m_socket,m_hIOCompletionPort,(ULONG_PTR)m_pListenSocket,0);
}
....
当创建了监听之后,可以开启完成端口监听事件,开始获取事件,下面简要说以下。
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort,
LPDWORD lpNumberOfBytesTransferred,
PULONG_PTR lpCompletionKey,
LPOVERLAPPED * lpOverlapped,
DWORD dwMilliseconds
);
BOOL PostQueueCompletionStatus(
DWORD dwNumberOfBytesTransferred,
ULONG_PTR dwCompletionKey,
LPOVERLAPPED lpOverlapped
);
其应用如下:
OVERLAPPED *pOverlapped = NULL;
CLIENT_CONTEXT *pSocketContext = NULL;
DWORD dwBytesTransfered = 0;
BOOL bReturn = GetQueuedCompletionStatus(
m_hIOCompletionPort,
&dwBytesTransfered,
(PULONG_PTR)&pSocketContext,
&pOverlapped,
INFINITE);
///空闲没有事件
if(!bReturn)
{
}
有io完成事件
else
{
IO_CONTEXT* pIoContext = CONTAINING_RECORD(pOverlapped, IO_CONTEXT, m_Overlapped);
//通过传输的字节长度以及事件判断连接是否断开了,即某一个客户端断开连接
if(dwBytesTransfered==0 &&(pIOContext->mtype == 1 || pIOContext->mtype == 2))
{
//移除出远程监控上下文列表,即远程连接的上下文去掉。
}
else
{
//根据事件类型处理完成的事件
switch(pIOContext->m_type)
{
case 0: //有新的远程连接请求到来
Deal_AcceptEvent(pSocketContext,pIOContext);
break;
case 1: //有新的读完成事件到来
Deal_ReadEvent(pSocketContext,pIOContext);
break;
case 2: //有新的写完成事件到来
Deal_WriteEvent(pSocketContext,pIOContext);
break;
}
}
....
如果接收远程连接也要异步的话,可以使用系统定义的异步指针,下面讲一下:
BOOL (*LPFN_ACCEPTEX)(SOCKET sListenSocket,
SOCKET sAcceptSocket,
PVOID lpOutputBuffer,
DWORD dwReceiveDataLength,
DWORD dwLocalAddressLength,
DWORD dwRemoteAddressLength,
LPDWORD lpdwBytesReceived,
LPOVERLAPPED lpOverlapped)
#define WSAID_ACCEPTEX \
{0xb5367df1,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}}
void (* LPFN_GETACCEPTEXSOCKADDRS)(
PVOID lpOutputBuffer,
DWORD dwReceiveDataLength,
DWORD dwLocalAddressLength,
DWORD dwRemoteAddressLength,
struct sockaddr **LocalSockaddr,
LPINT LocalSockaddrLength,
struct sockaddr **RemoteSockaddr,
LPINT RemoteSockaddrLength
);
#define WSAID_GETACCEPTEXSOCKADDRS \
{0xb5367df2,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}}
以上是异步接收连接的函数指针,程序中可以这样用
LPFN_ACCEPTEX m_lpfnAcceptEx = nullptr;
LPFN_GETACCEPTEXSOCKADDRS m_lpfnGetAcceptExSockAddrs=nullptr;
GUID GuidAcceptEx = WSAID_ACCEPTEX;
GUID GuidGetAcceptExSockAddrs = WSAID_GETACCEPTEXSOCKADDRS;
DWORD dwBytes = 0;
WSAIoctl(m_pListenSocket->m_socket,SIO_GET_EXTENSION_FUNCTION_POINTER,&GuidAcceptEx,
sizeof(GuidAcceptEx),&m_lpfnAcceptEx,sizeof(m_lpfnAcceptEx),&dwBytes,nullptr,nullptr);
WSAIoctl(m_pListenSocket->m_socket,SIO_GET_EXTENSION_FUNCTION_POINTER,&GuidGetAcceptExSockAddrs, sizeof(GuidGetAcceptExSockAddrs),&m_lpfnGetAcceptExSockAddrs,sizeof(m_lpfnGetAcceptExSockAddrs),&dwBytes,nullptr,nullptr);
IO_CONTEXT *pIOContext = new IO_CONTEXT;
m_pListenSocket->mIOContexts.push_back(pIOContext);
pIOContext->m_type = 0; //accept_posted;
WSABUF *p_wbuf = &pIOContext->m_wsaBuf;
OVERLAPPED *p_ol = &pIOContext->m_Overlapped;
m_lpfnAcceptEx(m_pListenSocket->m_socket,pAcceptIoContext->m_socket,p_wbuf->buf,p_wbuf->len - ((sizeof(sockaddr_in)+16)*2),sizeof(sockaddr_in) + 16,sizeof(sockaddr_in) + 16,&dwBytes,p_ol);
//当收到远程连接请求时,需要将收到的远端加入到完成端口监控列表中,远端的数据从发送的参数中通过GetQueuedCompletionStatus取回,通过重叠io获取io上下文
//PER_SOCKET_CONTEXT *pSocketContext = NULL;
//PER_IO_CONTEXT* pIoContext = CONTAINING_RECORD(pOverlapped, PER_IO_CONTEXT, m_Overlapped);
CLIENT_CONTEXT *pNewClientContext = new CLIENT_CONTEXT;
pNewClientContext->m_socket = pIoContext->mSockClient;
SOCKADDR_IN* ClientAddr = NULL;
SOCKADDR_IN* LocalAddr = NULL;
int remoteLen = sizeof(SOCKADDR_IN), localLen = sizeof(SOCKADDR_IN);
///
// 1. 首先取得连入客户端的地址信息
// 这个 m_lpfnGetAcceptExSockAddrs 不得了啊~~~~~~
// 不但可以取得客户端和本地端的地址信息,还能顺便取出客户端发来的第一组数据,老强大了...
m_lpfnGetAcceptExSockAddrs(pIoContext->m_wsaBuf.buf, pIoContext->m_wsaBuf.len - ((sizeof(SOCKADDR_IN)+16)*2),
sizeof(SOCKADDR_IN)+16, sizeof(SOCKADDR_IN)+16, (LPSOCKADDR*)&LocalAddr, &localLen, (LPSOCKADDR*)&ClientAddr, &remoteLen);
memcpy(&(pNewClientContext->m_addr),ClientAddr,sizeof(sockaddr_in));
//需要将收到的远程上下文加入完成端口监听消息。
CreateIoCompletionPort((HANDLE)pNewClientContext->m_Socket, m_hIOCompletionPort, (DWORD)pNewClientContext, 0);
//此时监听远程消息,发送异步读事件。
读写不分家,此时共同阐述读和写
int WSARecv(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd,
LPDWORD lpFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
int WSASend(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
异步读操作的话,提前发出WSARecv,然后等待有数据到来,读完成之后就可以对数据进行处理,异步写操作的话,可以一次写多块内容,写完成事件到来之后,可以确定是否写成功了,写了多少字节,据此进行后续操作。看应用:
回看上文,当收到远程连接之后,此时需要发送一个读操作,接收远程发过来的消息,或者直接给远程发送异步写。
IO_CONTEXT* pNewIoContext = pNewSocketContext->GetNewIoContext(); //此处在上下文中申请直接加入vector
pNewIoContext->m_type = 1;
pNewIoContext->m_sockClient = pNewSocketContext->m_socket;
DWORD dwFlags = 0;
DWORD dwBytes = 0;
WSABUF *p_wbuf = &pNewIoContext->m_wsaBuf;
OVERLAPPED *p_ol = &pNewIoContext->m_Overlapped;
int nBytesRecv = WSARecv( pNewIoContext->m_sockAccept, p_wbuf, 1, &dwBytes, &dwFlags, p_ol, NULL );
//当收到读完成事件的时候,除了处理收到的数据之外,还要根据内存使用情况,为下次接收发送读操作
异步写的实例暂时先不发了,这就是我理解的完成端口的一些操作。参考了网上一些大牛的代码,欢迎各位it大牛品评,并做出修正意见。