阻塞模式下使用IOCP
2018-01-02 17:59
218 查看
刚开始想学习IOCP(后面也会叫完成端口)的时候,在网上找到的最多的是小猪的IOCP资源,里面用的AcceptEx+IOCP来实现的,把代码下载下来看了半天都没看明白,然后想如果从最简单的阻塞模式入手,会不会更容易理解IOCP的运用呢?所以就花了了几天时间自己写了一个,总算大概弄懂了IOCP在socket里面的用法了,具体流程如下:
1.网络库的初始化+IOCP初始化和线程的创建+服务端socket的初始化
a.网络库的初始化
b.IOCP初始化和线程的创建
c.服务端socket的初始化
2.accept开始接收客户端连接了(关键在这一步)
a.accept等待客户端的连接
在这一步产生了一个clt_soct,这个是客户端连接说产生的socket套接字,后续完成端口的发送接收数据都是通过这个socket来完成,所以下一步就是把这个套接字和完成端口完成绑定;
b.把socket的IO事件提交给完成端口,由完成端口来监听
3.所有的事情都准备完了,下面都事情都交给完成端口去做了
源代码地址:点击打开链接
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
- 阻塞模式下使用IOCP
- 阻塞模式下使用IOCP
- 阻塞模式下使用IOCP
- 阻塞模式下使用IOCP
- 阻塞模式下使用IOCP
- 阻塞模式下使用IOCP
- 阻塞模式下使用IOCP
- libcurl使用easy模式阻塞卡死等问题的完美解决
- 使用 erlang OTP 模式编写非阻塞的 tcp 服务器(来自erlang wiki)
- 非阻塞socket与epoll的ET模式结合使用注意事项
- libcurl使用easy模式阻塞卡死等问题的完美解决---超时设置
- 非阻塞socket与epoll的ET模式结合使用注意事项
- IO模式设置网络编程常见问题总结—IO模式设置,阻塞与非阻塞的比较,recv参数对性能的影响—O_NONBLOCK(open使用)、IPC_NOWAIT(msgrcv)、MSG_DONTWAIT(re
- libcurl使用easy模式阻塞卡死等问题的完美解决---超时设置
- IO模式设置网络编程常见问题总结—IO模式设置,阻塞与非阻塞的比较,recv参数对性能的影响—O_NONBLOCK(open使用)、IPC_NOWAIT(msgrcv)、MSG_DONTWAIT(re
- IO模式设置网络编程常见问题总结—IO模式设置,阻塞与非阻塞的比较,recv参数对性能的影响—O_NONBLOCK(open使用)、IPC_NOWAIT(msgrcv)、MSG_DONTWAIT(re
- 使用阻塞队列实现生产者-消费者模式——Java实现
- IO模式设置网络编程常见问题总结—IO模式设置,阻塞与非阻塞的比较,recv参数对性能的影响—O_NONBLOCK(open使用)、IPC_NOWAIT(msgrcv)、MSG_DONTWAIT(re
- IO模式设置网络编程常见问题总结—IO模式设置,阻塞与非阻塞的比较,recv参数对性能的影响—O_NONBLOCK(open使用)、IPC_NOWAIT(msgrcv)、MSG_DONTWAIT(re