strong nor 完成后 over tran event 进行 才会 n)
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, (conststruct 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 函数
- intWSARecv(
- 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;
- }
- }
- return0;
- }
到这里其实就完成了这个 IOCP 的使用, 后面还会补充的。
来源: http://www.bubuko.com/infodetail-2452961.html