1详解完成端口基本使用

1创建完成端口

HANDLEiocp=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);

参数其实就是-1,0,0,0. 最后一个参数代表的就是

NumberOfConcurrentThreads,就是允许应用同时执行的线程数量,

未来避免上下文切换,就是说让每个CPU只允许一个线程,设置为0

就是有多少处理器,就有多少工作线程。

原因就是如果一台机器有两个CPU(两核),如果让系统同时运行的

线程,多于本机CPU数量的话,就没什么意义,会浪费CPU宝贵周期,

降低效率,得不偿失。

然后会返回一个HANDLE 只要不是NULL就是建立完成端口成功。



2创建Socket绑定侦听 不多说

SOCKETlo_sock=INVALID_SOCKET;//创建失败if(iocp==NULL){gotofailed;}//创建一个线程把IOCP传到线程函数里h_threadS=CreateThread(NULL,0,ServerThread,(LPVOID)iocp,0,0);//防止内存泄露CloseHandle(h_threadS);//end//创建socketlo_sock=socket(AF_INET,SOCK_STREAM,0);if(lo_sock==INVALID_SOCKET){gotofailed;}structsockaddr_inaddr;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;intret=bind(lo_sock,(conststructsockaddr*)&addr,sizeof(addr));if(ret!=0){printf("bind%s:%derror\n","127.0.0.1",port);gotofailed;}printf("bind%s:%dsuccess\n","127.0.0.1",port);printf("startinglisteneron%d\n",port);//SOMAXCONN通过listen指定最大队列长度ret=listen(lo_sock,SOMAXCONN);if(ret!=0){printf("listeningonportfailed\n");gotofailed;}printf("listeningonsuccess\n");




3在主线程里面侦听accept

structsockaddr_inc_addr;intlen=sizeof(c_addr);//没有client接入进来,线程会挂起也就是阻塞intclient_fd=accept(lo_sock,(structsockaddr*)&c_addr,&len);if(client_fd!=INVALID_SOCKET){//这里就是有新的socket连接了printf("newclient%s:%dcoming\n",inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));}else{continue;}//保存会话信息structsession*s=save_session(client_fd,inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));将信息保存在一个存用户ipport端口的结构体里面这个结构体是这样的:/*这个结构中定义structsession{charc_ip[32];//ip地址intc_port;//端口intc_sock;//socket句柄intremoved;//删除标记structsession*_next;//链表指针};*/



4然后把获得的客户端socket绑定到iocp

这段代码是在一个while(1)死循环里进行

先介绍下这个函数 和创建完成端口用的是一个API

HANDLEWINAPICreateIoCompletionPort(__inHANDLEFileHandle,//这里就是客户连入的socket__in_optHANDLEExistingCompletionPort,//就是前面创建的完成端口,__inULONG_PRTCompletionKey,//这个参数可以传递一个结构体,自定义的结构体//你只要把这个结构体传入,工作线程就可以取出来,//我使用的是上面我定义的结构体_inDWORDDWORDNumberOfConcurrenThreads//上面说了,设置为0就行);//添加到这个完成端口CreateIoCompletionPort((HANDLE)client_fd,iocp,(DWORD)s,0);client_fd就是上面或得的客户端socket然后iocp完成端口,s就是带有客户端会话信息的结构体


5投递一个异步recv请求

(就是告诉完成端口,如果我这个客户端有包过,你要接收完成,然后告诉我)

在这之前就要定义一个结构体作为标志,因为启动的时候投递了很多的

I/O请求,要用一个标志来绑定每一个I/O操作,这样网络操作完成后,

在通过这个标志找到这组返回的数据:

一定要将WSAOVERLAPPED放第一个,其他的随意


//缓冲区大小#defineMAX_RECV_SIZE8092structio_package{WSAOVERLAPPEDoverlapped;//重叠I/O网络操作都要用到这个重叠结构intopt;//标记请求的类型intpkg_size;//包的长度WSABUFwsabuffer;//存储数据的缓冲区,用来给重叠操作传递数据的charpkg[MAX_RECV_SIZE];//对应WSABUF里的缓冲区};//监听事件用来标记请求的类型enum{IOCP_ACCEPT=0,IOCP_RECV,IOCP_WRITE,};

WSARecv函数

intWSARecv(SOCKETs,//当然是投递这个操作的套接字LPWSABUFlpBuffers,//接收缓冲区DWORDdwBufferCount,//数组中WSABUF结构的数量,设置为1即可LPDWORDlpNumberOfBytesRecvd,//如果接收操作立即完成,这里会返回函数调用所接收到的字节数LPDWORDlpFlags,//设置为0LPWSAOVERLAPPEDlpOverlapped,//这个Socket对应的重叠结构lpCompletionRoutine//这个参数只有完成例程模式才会用到,)WSA_IO_PENDING:最常见的返回值,说明WSARecv成功了,但是I/O操作没完成

投递这个请求

structio_package*io_data=malloc(sizeof(structio_package));//只需要清空一次,即可就是为了让重叠结构清空memset(io_data,0,sizeof(structio_package));io_data->wsabuffer.buf=io_data->pkg;io_data->wsabuffer.len=MAX_RECV_SIZE-1;io_data->opt=IOCP_RECV;//标记请求类型我们设置成接收DWORDdwFlags=0;//............WSARecv(client_fd,&io_data->wsabuffer,1,NULL,&dwFlags,&io_data->overlapped,NULL);


5在工作线程里等待完成事件

GetQueuedCompletionStatus函数原型,是工作线程里要

用到的API,他一旦进入,工作线程就会被挂起,知道

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

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

BOOLWINAPIGetQueuedCompletionStatus(__inHANDLECompletionPort,//这个就是我们建立的那个唯一的完成端口__outLPDWORDlpNumberOfBytes,//这个是操作完成后返回的字节数__outPULONG_PTRlpCompletionKey,//这个是建立完成端口的时候绑定的那个自定义结构体参__outLPOVERLAPPED*lpOverlapped,//这个是在连入Socket的时候一起建立的那个重叠结构__inDWORDdwMilliseconds//等待完成端口的超时时间,WSA_INFINITE是等待有事件才返回


看下这个代码操作

//线程函数staticDWORDWINAPIServerThread(LPVOIDlParam){//获取完成端口HANDLEiocp=(HANDLE)lParam;//返回的字节数DWORDdwTrans;//带有socket句柄的结构体因为之前是添加进去这个函数可以取出structsession*s;//带有重叠结构的结构体structio_package*io_data;//等待IOCPwhile(1){s=NULL;dwTrans=0;io_data=NULL;//调用这个API等待事件intret=GetQueuedCompletionStatus(iocp,&dwTrans,(LPDWORD)&s,(LPOVERLAPPED*)&io_data,WSA_INFINITE);if(ret==0){printf("iocperror");//IOCP端口发生错误continue;}//来告诉所有用户socket的完成事件发生了printf("IOCPhaveevent\n");//接收的字节==0表示客户端断开连接if(dwTrans==0){//socket关闭了closesocket(s->c_sock);//释放内存free(io_data);continue;}//到这里意味着数据以及读取到//这里就是前面标记的事件类型switch(io_data->opt){caseIOCP_RECV:{//接收数据以及完成了io_data->pkg[dwTrans]=0;printf("IOCP%d:recv%d,%s\n",s->c_port,dwTrans,io_data->pkg);//当读的请求完成后,必须再投递一个读的请求DWORDdwFlags=0;intret=WSARecv(s->c_sock,&io_data->wsabuffer,1,NULL,&dwFlags,&io_data->overlapped,NULL);}break;caseIOCP_WRITE:{}break;caseIOCP_ACCEPT:{}break;default:break;}}return0;}

到这里其实就完成了这个IOCP的使用,后面还会补充的。