Windows完成端口 IOCP模型(二)

简介:

1详解完成端口基本使用

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绑定侦听 不多说

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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放第一个,其他的随意


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//缓冲区大小
#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函数

1
2
3
4
5
6
7
8
9
10
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操作没完成

投递这个请求

1
2
3
4
5
6
7
8
9
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,他一旦进入,工作线程就会被挂起,知道

完成端口上出现了完成的事件。或网络超时

那么这个线程会被立刻唤醒,执行后续代码

1
2
3
4
5
6
BOOL  WINAPI GetQueuedCompletionStatus(
__in    HANDLE           CompletionPort,     // 这个就是我们建立的那个唯一的完成端口  
__out   LPDWORD          lpNumberOfBytes,    //这个是操作完成后返回的字节数
__out   PULONG_PTR       lpCompletionKey,    // 这个是建立完成端口的时候绑定的那个自定义结构体参
__out  LPOVERLAPPED    *lpOverlapped,      // 这个是在连入Socket的时候一起建立的那个重叠结构
  __in    DWORD            dwMilliseconds      // 等待完成端口的超时时间,WSA_INFINITE是等待有事件才返回


看下这个代码操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//线程函数
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的使用,后面还会补充的。




 本文转自超级极客51CTO博客,原文链接:http://blog.51cto.com/12158490/2058302,如需转载请自行联系原作者



相关文章
|
3月前
|
Windows
windows环境下根据端口号查询进程编号并杀掉此进程
windows环境下根据端口号查询进程编号并杀掉此进程
|
5月前
|
消息中间件 C++ Windows
02 MFC - Windows 编程模型
02 MFC - Windows 编程模型
22 0
|
7月前
|
Windows
Windows下查看某个端口是否被占用结束某个端口的进程
Windows下查看某个端口是否被占用结束某个端口的进程
52 2
|
13天前
|
Linux Windows
Windows、Mac、Linux解决端口被占用的问题
Windows、Mac、Linux解决端口被占用的问题
21 1
|
5月前
|
网络协议 Windows
windows 80端口占用 system pid=4
windows 80端口占用 system pid=4
48 0
|
6月前
|
Windows
windows系统 如何查看端口占用情况并关闭占用的进程?
windows系统 如何查看端口占用情况并关闭占用的进程?
217 0
|
3月前
|
TensorFlow 网络安全 算法框架/工具
tensorflow的模型使用flask制作windows系统服务
tensorflow的模型使用flask制作windows系统服务
|
3月前
|
Windows
windows iocp适配epoll
windows iocp适配epoll
|
4月前
|
Windows
Windows查看所有的端口
Windows查看所有的端口
|
5月前
|
网络协议 前端开发 Java
windows系统下重启springboot项目时,提示端口被占用,却找不到占用端口的程序
windows系统下重启springboot项目时,提示端口被占用,却找不到占用端口的程序