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,如需转载请自行联系原作者