设为首页 收藏本站
查看: 539|回复: 0

[经验分享] Windows完成端口 IOCP模型(二)

[复制链接]

尚未签到

发表于 2018-6-9 10:36:45 | 显示全部楼层 |阅读模式
  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的使用,后面还会补充的。

  

  

  

  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-521229-1-1.html 上篇帖子: SCCM 2012 SP1系列(十三)部署Windows7系统 下篇帖子: 如何让win2008R2 SP1 KMS服务器支持Windows8.1和Server2012R2的激活
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表