標籤:err 未來 success 釋放記憶體 null eof wmi 一起 監聽事件
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//建立socketlo_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 8092struct 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;//等待IOCPwhile (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的使用,後面還會補充的。
Windows完成連接埠 IOCP模型(二)