1详解完成端口基本使用
1创建完成端口
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
参数其实就是-1,0,0,0. 最后一个参数代表的就是
NumberOfConcurrentThreads,就是允许应用同时执行的线程数量,
未来避免上下文切换,就是说让每个CPU只允许一个线程,设置为0
就是有多少处理器,就有多少工作线程。
原因就是如果一台机器有两个CPU(两核),如果让系统同时运行的
线程,多于本机CPU数量的话,就没什么意义,会浪费CPU宝贵周期,
降低效率,得不偿失。
然后会返回一个HANDLE 只要不是NULL就是建立完成端口成功。
2创建Socket绑定侦听 不多说
SOCKET lo_sock = INVALID_SOCKET; //创建失败 if (iocp == NULL){ goto failed; } //创建一个线程 把IOCP传到线程函数里 h_threadS = CreateThread(NULL, 0, ServerThread, (LPVOID)iocp, 0, 0); // 防止内存泄露 CloseHandle(h_threadS); //end //创建socket lo_sock = socket(AF_INET,SOCK_STREAM,0); if (lo_sock == INVALID_SOCKET){ goto failed; } struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); addr.sin_port = htons(port); addr.sin_family = AF_INET; int ret = bind(lo_sock, (const struct sockaddr*)&addr, sizeof(addr)); if (ret != 0){ printf("bind %s:%d error \n", "127.0.0.1", port); goto failed; } printf("bind %s:%d success \n", "127.0.0.1", port); printf("starting listener on %d\n", port); // SOMAXCONN 通过listen指定最大队列长度 ret = listen(lo_sock, SOMAXCONN); if (ret != 0){ printf("listening on port failed\n"); goto failed; } printf("listening on success\n");
3在主线程里面侦听accept
struct sockaddr_in c_addr; int len = sizeof(c_addr); //没有client接入进来,线程会挂起 也就是阻塞 int client_fd = accept(lo_sock, (struct sockaddr*)&c_addr, &len); if (client_fd != INVALID_SOCKET){ //这里就是有新的socket连接了 printf("new client %s:%d coming\n", inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port)); } else{ continue; } //保存会话信息 struct session* s = save_session(client_fd, inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port)); 将信息保存在一个存用户ip port 端口的结构体里面 这个结构体是这样的: /* 这个结构中定义 struct session{ char c_ip[32]; //ip地址 int c_port; //端口 int c_sock; //socket句柄 int removed;//删除标记 struct session * _next; //链表指针 }; */
4然后把获得的客户端socket绑定到iocp
这段代码是在一个while(1)死循环里进行
先介绍下这个函数 和创建完成端口用的是一个API
HANDLE WINAPI CreateIoCompletionPort( __in HANDLE FileHandle, //这里就是客户连入的socket __in_opt HANDLE ExistingCompletionPort,//就是前面创建的完成端口, __in ULONG_PRT CompletionKey,//这个参数可以传递一个结构体,自定义的结构体 //你只要把这个结构体传入,工作线程就可以取出来, // 我使用的是上面我定义的 结构体 _in DWORD DWORD NumberOfConcurrenThreads//上面说了,设置为0就行 ); //添加到这个完成端口 CreateIoCompletionPort((HANDLE)client_fd, iocp,(DWORD)s, 0); client_fd 就是上面或得的客户端socket 然后iocp完成端口, s就是带有客户端会话信息的结构体
5投递一个异步recv请求
(就是告诉完成端口,如果我这个客户端有包过,你要接收完成,然后告诉我)
在这之前就要定义一个结构体作为标志,因为启动的时候投递了很多的
I/O请求,要用一个标志来绑定每一个I/O操作,这样网络操作完成后,
在通过这个标志找到这组返回的数据:
一定要将WSAOVERLAPPED放第一个,其他的随意
//缓冲区大小 #define MAX_RECV_SIZE 8092 struct io_package{ WSAOVERLAPPED overlapped; //重叠I/O网络操作都要用到这个 重叠结构 int opt; //标记请求的类型 int pkg_size; //包的长度 WSABUF wsabuffer; //存储数据的缓冲区,用来给重叠操作传递数据的 char pkg[MAX_RECV_SIZE]; //对应WSABUF里的缓冲区 }; //监听事件 用来标记请求的类型 enum{ IOCP_ACCEPT = 0, IOCP_RECV, IOCP_WRITE, };
WSARecv函数
int WSARecv( SOCKET s,//当然是投递这个操作的套接字 LPWSABUF lpBuffers, // 接收缓冲区 DWORD dwBufferCount, // 数组中WSABUF结构的数量,设置为1即可 LPDWORD lpNumberOfBytesRecvd, // 如果接收操作立即完成,这里会返回函数调用所接收到的字节数 LPDWORD lpFlags, // 设置为0 LPWSAOVERLAPPED lpOverlapped, // 这个Socket对应的重叠结构 lpCompletionRoutine //这个参数只有完成例程模式才会用到, ) WSA_IO_PENDING:最常见的返回值,说明WSARecv成功了, 但是I/O操作没完成
投递这个请求
struct io_package* io_data = malloc(sizeof(struct io_package)); //只需要清空一次,即可 就是为了 让重叠结构清空 memset(io_data, 0, sizeof(struct io_package)); io_data->wsabuffer.buf = io_data->pkg; io_data->wsabuffer.len = MAX_RECV_SIZE - 1; io_data->opt = IOCP_RECV; //标记请求类型 我们设置成接收 DWORD dwFlags = 0; //............ WSARecv(client_fd, &io_data->wsabuffer, 1, NULL,&dwFlags, &io_data->overlapped, NULL);
5在工作线程里等待完成事件
GetQueuedCompletionStatus函数原型,是工作线程里要
用到的API,他一旦进入,工作线程就会被挂起,知道
完成端口上出现了完成的事件。或网络超时
那么这个线程会被立刻唤醒,执行后续代码
BOOL WINAPI GetQueuedCompletionStatus( __in HANDLE CompletionPort, // 这个就是我们建立的那个唯一的完成端口 __out LPDWORD lpNumberOfBytes, //这个是操作完成后返回的字节数 __out PULONG_PTR lpCompletionKey, // 这个是建立完成端口的时候绑定的那个自定义结构体参 __out LPOVERLAPPED *lpOverlapped, // 这个是在连入Socket的时候一起建立的那个重叠结构 __in DWORD dwMilliseconds // 等待完成端口的超时时间,WSA_INFINITE是等待有事件才返回
看下这个代码操作
//线程函数 static DWORD WINAPI ServerThread(LPVOID lParam) { //获取完成端口 HANDLE iocp = (HANDLE)lParam; //返回的字节数 DWORD dwTrans; //带有socket句柄的结构体 因为之前是添加进去 这个函数可以取出 struct session* s; //带有重叠结构的结构体 struct io_package* io_data; //等待IOCP while (1){ s = NULL; dwTrans = 0; io_data = NULL; //调用这个API 等待事件 int ret = GetQueuedCompletionStatus(iocp, &dwTrans, (LPDWORD)&s, (LPOVERLAPPED*)&io_data, WSA_INFINITE); if (ret == 0){ printf("iocp error");//IOCP端口发生错误 continue; } //来告诉所有用户socket的完成事件发生了 printf("IOCP have event\n"); //接收的字节==0 表示客户端断开连接 if (dwTrans == 0){//socket关闭了 closesocket(s->c_sock); //释放内存 free(io_data); continue; } //到这里意味着数据以及读取到 //这里就是前面标记的事件类型 switch (io_data->opt) { case IOCP_RECV:{ // 接收数据以及完成了 io_data->pkg[dwTrans] = 0; printf("IOCP %d: recv %d,%s\n",s->c_port,dwTrans,io_data->pkg); //当读的请求完成后, 必须再投递一个读的请求 DWORD dwFlags = 0; int ret = WSARecv(s->c_sock, &io_data->wsabuffer, 1, NULL, &dwFlags, &io_data->overlapped, NULL); } break; case IOCP_WRITE:{ } break; case IOCP_ACCEPT:{ } break; default: break; } } return 0; }
到这里其实就完成了这个IOCP的使用,后面还会补充的。