您的位置:首页 > 其它

阻塞模式下使用IOCP

2018-01-02 17:59 162 查看
     刚开始想学习IOCP(后面也会叫完成端口)的时候,在网上找到的最多的是小猪的IOCP资源,里面用的AcceptEx+IOCP来实现的,把代码下载下来看了半天都没看明白,然后想如果从最简单的阻塞模式入手,会不会更容易理解IOCP的运用呢?所以就花了了几天时间自己写了一个,总算大概弄懂了IOCP在socket里面的用法了,具体流程如下:

1.网络库的初始化+IOCP初始化和线程的创建+服务端socket的初始化

  a.网络库的初始化

#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
WSADATA wsa;
int nErr = WSAStartup(MAKEWORD(2, 2), &wsa);
if(nErr != 0)
{
nErr = GetLastError();
std::cout<<"WSAStartup error, num ="<<nErr<<std::endl;
return -1;
}
if(HIBYTE(wsa.wVersion) != 2 || LOBYTE(wsa.wVersion) != 2)
{
return -1;
}

  b.IOCP初始化和线程的创建

completion_port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);//创建完成端口
if(completion_port == NULL)
{
nErr = GetLastError();
std::cout<<"CreateIoCompletionPort err, number="<<nErr<<std::endl;
return -1;
}
SYSTEM_INFO sys_info;
GetSystemInfo(&sys_info);
int thread_cnt = sys_info.dwNumberOfProcessors * 2 - 1;
HANDLE* pthread_handle = new HANDLE[thread_cnt];
DWORD* pthread_id = new DWORD[thread_cnt];

for(int index = 0; index < thread_cnt; ++index) //创建处理完成端口上的I/0事件的线程
{
pthread_handle[index] = CreateThread(NULL, 0, ThreadFunc, completion_port, NULL, &pthread_id[index]);
}


   c.服务端socket的初始化  

SOCKET soct = socket(AF_INET, SOCK_STREAM, 0);
if(soct == INVALID_SOCKET)
{
nErr = GetLastError();
std::cout<<"socket err, number="<<nErr<<std::endl;
return -1;
}
sockaddr_in svr_addr_in;
svr_addr_in.sin_family = AF_INET;
svr_addr_in.sin_port = htons(10001);
svr_addr_in.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int nResult = bind(soct, (sockaddr*)&svr_addr_in, sizeof(sockaddr));
if(nResult == SOCKET_ERROR)
{
nErr = GetLastError();
std::cout<<"bind err, number="<<nErr<<std::endl;
return -1;
}
nResult = listen(soct, 1000);
if(nResult == SOCKET_ERROR)
{
nErr = GetLastError();
std::cout<<"listen err, number="<<nErr<<std::endl;
return -1;
}


2.accept开始接收客户端连接了(关键在这一步)

   a.accept等待客户端的连接

SOCKET clt_soct; /***客户端连接所产生socket***/
sockaddr_in clt_addr_in ={0};
int clt_addr_len = 16;
clt_soct = accept(soct, (sockaddr*)&clt_addr_in, &clt_addr_len);
if(clt_soct == INVALID_SOCKET)
{
closesocket(clt_soct);
nErr = GetLastError();
std::cout<<"accept err, number="<<nErr<<std::endl;
continue;
}


        在这一步产生了一个clt_soct,这个是客户端连接说产生的socket套接字,后续完成端口的发送接收数据都是通过这个socket来完成,所以下一步就是把这个套接字和完成端口完成绑定;

LPSOCK_ADDR_IN psock_addr_in = (LPSOCK_ADDR_IN)GlobalAlloc(GPTR, sizeof(SOCK_ADDR_IN));//必须用指针,因为这个对象还要到其他的线程使用
psock_addr_in->soct = clt_soct;
psock_addr_in->addrin = clt_addr_in;
CreateIoCompletionPort((HANDLE)clt_soct, completion_port, (ULONG_PTR)(psock_addr_in), 0); //clt_soct绑定到完成端口是上,以后clt_soct上的接收就由完成端口来完成
     请注意CreateIoCompletionPort的第三个参数psock_addr_in,因为这个参数在后面作用很大,这里要解释一下这个SOCK_ADDR_IN这个结构和GetQueuedCompletionStatus这个函数,因为挺重要的

          
typedef struct
{
SOCKET soct;          /*客户端连接所产生的socket*/
sockaddr_in addrin;   /*客户端连接所产生的sockaddrin*/
} SOCK_ADDR_IN, *LPSOCK_ADDR_IN;
BOOL GetQueuedCompletionStatus( //函数功能:捕捉完成端口上的任何操作
HANDLE CompletionPort,
LPDWORD lpNumberOfBytes,
PULONG_PTR lpCompletionKey,//这个参数就是CreateIoCompltetionPort绑定socket的时候传递进去的psock_addr_in,里面有客户端的socket和地址信息
LPOVERLAPPED *lpOverlapped,//这个参数也是由WSASend或者WSARecv提交操作的时候传参的
DWORD dwMilliseconds);




     b.把socket的IO事件提交给完成端口,由完成端口来监听

/*******************************把recv操作交给完成端口操作*********************************/
LPIO_OPERATION_DATA operator_data = NULL;
operator_data = (LPIO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof(IO_OPERATION_DATA));
operator_data->buff_len = 1024;                 //要接收的字节数
operator_data->wsabuf.buf = operator_data->buff;
operator_data->wsabuf.len = operator_data->buff_len;
ZeroMemory(&operator_data->overlapped, sizeof(OVERLAPPED));
operator_data->operation_type = IO_OPERATION_READ; //读操作

DWORD recv_cnt = 0, flag = 0;
WSARecv(clt_soct, &(operator_data->wsabuf), 1, &recv_cnt, &flag, &(operator_data->overlapped), NULL);//投递给完成端口来异步接收
//这里这个overlapped就是GetQueuedCompletionStatus说得到的第4个参数,CONTAINING_RECORD可以通过这个重叠结构overlapped得到整个LPIO_OPERATION_DATA对象


3.所有的事情都准备完了,下面都事情都交给完成端口去做了

DWORD WINAPI ThreadFunc(LPVOID lParam)
{
int nerr = 0;
BOOL result = FALSE;
DWORD recv_cnt = 0, flag = 0;
LPSOCK_ADDR_IN psock_addr_in = NULL; //用于GetQueuedCompletionStatus第三个参数,在CreateIoCompletionPort传参的时候ULONG_PTR==LPSOCK_ADDR_IN,所以PULONG_PTR必须是LPSOCK_ADDR_IN对象的地址
LPOVERLAPPED poverlapped = NULL;
while(true)
{
//这里的第三个和第四个参数就是绑定完成端口和提交IO操作给完成端口里传进来的,具体可以看第2步
result = GetQueuedCompletionStatus(completion_port, &recv_cnt, (PULONG_PTR)(&psock_addr_in), &poverlapped, INFINITE);
if(result == FALSE)
{
if(NULL == poverlapped)
continue;
//if(sock_addr_in == NULL)
//continue;
nerr = GetLastError();
//错误处理
continue;
}
LPIO_DATA pio_data = (LPIO_DATA)CONTAINING_RECORD(poverlapped, IO_DATA, overlapped);//通过overlapped获取到整个LPIO_DATA对象
if(recv_cnt == 0 && (pio_data->operation_type == IO_OPERATION_READ || pio_data->operation_type == IO_OPERATION_WRITE))
{
std::cout<<inet_ntoa(psock_addr_in->addrin.sin_addr)<<"连接断开"<<std::endl;
closesocket(psock_addr_in->soct);
GlobalFree(psock_addr_in); //如果是GlobalAlloc就用GlobalFree释放
//delete psock_addr_in;     //如果是new就用delete释放
GlobalFree(pio_data);
continue;
}
else
{
//std::cout<<inet_ntoa(pso
4000
ck_addr_in->addrin.sin_addr)<<std::endl;
switch(pio_data->operation_type)
{
case IO_OPERATION_READ:
{
std::cout<<"接收了"<<recv_cnt<<"个字节,内容是:"<<pio_data->wsabuf.buf<<std::endl;//接收到了客户端发送的数据
sprintf(pio_data->buff, "OK");
pio_data->buff_len = strlen("OK");
pio_data->wsabuf.buf = pio_data->buff;
pio_data->wsabuf.len = pio_data->buff_len;
pio_data->operation_type = IO_OPERATION_WRITE;
WSASend(psock_addr_in->soct, &pio_data->wsabuf, 1, &recv_cnt, flag, &(pio_data->overlapped), NULL);//把发送“OK”的操作提交给完成端口去做
}
break;
case IO_OPERATION_WRITE:
std::cout<<"发送了"<<recv_cnt<<"个字节,内容是:"<<pio_data->wsabuf.buf<<"\r\n"<<std::endl;//完成了发送“OK”的操作
pio_data->buff_len = 1024;
memset(pio_data->buff, 0, pio_data->buff_len);
memset(pio_data->wsabuf.buf, 0, pio_data->buff_len);
pio_data->wsabuf.buf = pio_data->buff;
pio_data->wsabuf.len = pio_data->buff_len;
ZeroMemory(&(pio_data->overlapped), sizeof(OVERLAPPED));
pio_data->operation_type = IO_OPERATION_READ;
WSARecv(psock_addr_in->soct, &(pio_data->wsabuf), 1, &recv_cnt, &flag, &(pio_data->overlapped), NULL);//继续接受客户端的下一个数据
break;
default:
break;
}
}
}
return 0;
}


源代码地址:点击打开链接






















                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  IOCP 阻塞
相关文章推荐