Windows完成端口 IOCP模型(二)

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的使用,後面還會補充的。







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章