新聞中心
1詳解完成端口基本使用
和碩ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯(lián)建站的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!
1創(chuàng)建完成端口
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
參數(shù)其實就是-1,0,0,0. 最后一個參數(shù)代表的就是
NumberOfConcurrentThreads,就是允許應(yīng)用同時執(zhí)行的線程數(shù)量,
未來避免上下文切換,就是說讓每個CPU只允許一個線程,設(shè)置為0
就是有多少處理器,就有多少工作線程。
原因就是如果一臺機(jī)器有兩個CPU(兩核),如果讓系統(tǒng)同時運(yùn)行的
線程,多于本機(jī)CPU數(shù)量的話,就沒什么意義,會浪費(fèi)CPU寶貴周期,
降低效率,得不償失。
然后會返回一個HANDLE 只要不是NULL就是建立完成端口成功。
2創(chuàng)建Socket綁定偵聽 不多說
SOCKET lo_sock = INVALID_SOCKET; //創(chuàng)建失敗 if (iocp == NULL){ goto failed; } //創(chuàng)建一個線程 把IOCP傳到線程函數(shù)里 h_threadS = CreateThread(NULL, 0, ServerThread, (LPVOID)iocp, 0, 0); // 防止內(nèi)存泄露 CloseHandle(h_threadS); //end //創(chuàng)建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在主線程里面?zhèn)陕燼ccept
struct sockaddr_in c_addr; int len = sizeof(c_addr); //沒有client接入進(jìn)來,線程會掛起 也就是阻塞 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 端口的結(jié)構(gòu)體里面 這個結(jié)構(gòu)體是這樣的: /* 這個結(jié)構(gòu)中定義 struct session{ char c_ip[32]; //ip地址 int c_port; //端口 int c_sock; //socket句柄 int removed;//刪除標(biāo)記 struct session * _next; //鏈表指針 }; */
4然后把獲得的客戶端socket綁定到iocp
這段代碼是在一個while(1)死循環(huán)里進(jìn)行
先介紹下這個函數(shù) 和創(chuàng)建完成端口用的是一個API
HANDLE WINAPI CreateIoCompletionPort( __in HANDLE FileHandle, //這里就是客戶連入的socket __in_opt HANDLE ExistingCompletionPort,//就是前面創(chuàng)建的完成端口, __in ULONG_PRT CompletionKey,//這個參數(shù)可以傳遞一個結(jié)構(gòu)體,自定義的結(jié)構(gòu)體 //你只要把這個結(jié)構(gòu)體傳入,工作線程就可以取出來, // 我使用的是上面我定義的 結(jié)構(gòu)體 _in DWORD DWORD NumberOfConcurrenThreads//上面說了,設(shè)置為0就行 ); //添加到這個完成端口 CreateIoCompletionPort((HANDLE)client_fd, iocp,(DWORD)s, 0); client_fd 就是上面或得的客戶端socket 然后iocp完成端口, s就是帶有客戶端會話信息的結(jié)構(gòu)體
5投遞一個異步recv請求
(就是告訴完成端口,如果我這個客戶端有包過,你要接收完成,然后告訴我)
在這之前就要定義一個結(jié)構(gòu)體作為標(biāo)志,因為啟動的時候投遞了很多的
I/O請求,要用一個標(biāo)志來綁定每一個I/O操作,這樣網(wǎng)絡(luò)操作完成后,
在通過這個標(biāo)志找到這組返回的數(shù)據(jù):
一定要將WSAOVERLAPPED放第一個,其他的隨意
//緩沖區(qū)大小 #define MAX_RECV_SIZE 8092 struct io_package{ WSAOVERLAPPED overlapped; //重疊I/O網(wǎng)絡(luò)操作都要用到這個 重疊結(jié)構(gòu) int opt; //標(biāo)記請求的類型 int pkg_size; //包的長度 WSABUF wsabuffer; //存儲數(shù)據(jù)的緩沖區(qū),用來給重疊操作傳遞數(shù)據(jù)的 char pkg[MAX_RECV_SIZE]; //對應(yīng)WSABUF里的緩沖區(qū) }; //監(jiān)聽事件 用來標(biāo)記請求的類型 enum{ IOCP_ACCEPT = 0, IOCP_RECV, IOCP_WRITE, };
WSARecv函數(shù)
int WSARecv( SOCKET s,//當(dāng)然是投遞這個操作的套接字 LPWSABUF lpBuffers, // 接收緩沖區(qū) DWORD dwBufferCount, // 數(shù)組中WSABUF結(jié)構(gòu)的數(shù)量,設(shè)置為1即可 LPDWORD lpNumberOfBytesRecvd, // 如果接收操作立即完成,這里會返回函數(shù)調(diào)用所接收到的字節(jié)數(shù) LPDWORD lpFlags, // 設(shè)置為0 LPWSAOVERLAPPED lpOverlapped, // 這個Socket對應(yīng)的重疊結(jié)構(gòu) lpCompletionRoutine //這個參數(shù)只有完成例程模式才會用到, ) WSA_IO_PENDING:最常見的返回值,說明WSARecv成功了, 但是I/O操作沒完成
投遞這個請求
struct io_package* io_data = malloc(sizeof(struct io_package)); //只需要清空一次,即可 就是為了 讓重疊結(jié)構(gòu)清空 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; //標(biāo)記請求類型 我們設(shè)置成接收 DWORD dwFlags = 0; //............ WSARecv(client_fd, &io_data->wsabuffer, 1, NULL,&dwFlags, &io_data->overlapped, NULL);
5在工作線程里等待完成事件
GetQueuedCompletionStatus函數(shù)原型,是工作線程里要
用到的API,他一旦進(jìn)入,工作線程就會被掛起,知道
完成端口上出現(xiàn)了完成的事件?;蚓W(wǎng)絡(luò)超時
那么這個線程會被立刻喚醒,執(zhí)行后續(xù)代碼
BOOL WINAPI GetQueuedCompletionStatus( __in HANDLE CompletionPort, // 這個就是我們建立的那個唯一的完成端口 __out LPDWORD lpNumberOfBytes, //這個是操作完成后返回的字節(jié)數(shù) __out PULONG_PTR lpCompletionKey, // 這個是建立完成端口的時候綁定的那個自定義結(jié)構(gòu)體參 __out LPOVERLAPPED *lpOverlapped, // 這個是在連入Socket的時候一起建立的那個重疊結(jié)構(gòu) __in DWORD dwMilliseconds // 等待完成端口的超時時間,WSA_INFINITE是等待有事件才返回
看下這個代碼操作
//線程函數(shù) static DWORD WINAPI ServerThread(LPVOID lParam) { //獲取完成端口 HANDLE iocp = (HANDLE)lParam; //返回的字節(jié)數(shù) DWORD dwTrans; //帶有socket句柄的結(jié)構(gòu)體 因為之前是添加進(jìn)去 這個函數(shù)可以取出 struct session* s; //帶有重疊結(jié)構(gòu)的結(jié)構(gòu)體 struct io_package* io_data; //等待IOCP while (1){ s = NULL; dwTrans = 0; io_data = NULL; //調(diào)用這個API 等待事件 int ret = GetQueuedCompletionStatus(iocp, &dwTrans, (LPDWORD)&s, (LPOVERLAPPED*)&io_data, WSA_INFINITE); if (ret == 0){ printf("iocp error");//IOCP端口發(fā)生錯誤 continue; } //來告訴所有用戶socket的完成事件發(fā)生了 printf("IOCP have event\n"); //接收的字節(jié)==0 表示客戶端斷開連接 if (dwTrans == 0){//socket關(guān)閉了 closesocket(s->c_sock); //釋放內(nèi)存 free(io_data); continue; } //到這里意味著數(shù)據(jù)以及讀取到 //這里就是前面標(biāo)記的事件類型 switch (io_data->opt) { case IOCP_RECV:{ // 接收數(shù)據(jù)以及完成了 io_data->pkg[dwTrans] = 0; printf("IOCP %d: recv %d,%s\n",s->c_port,dwTrans,io_data->pkg); //當(dāng)讀的請求完成后, 必須再投遞一個讀的請求 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的使用,后面還會補(bǔ)充的。
文章名稱:Windows完成端口IOCP模型(二)
分享URL:http://fisionsoft.com.cn/article/giojei.html