採用Windows完成端口寫的服務器

//完成端口例子
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>

#define PORT 8090
#define DATA_BUFSIZE 8192

#pragma comment(lib, "Ws2_32")

typedef struct
//這個玩意就是灌數據,取數據的一個自定義數據結構
//和那個wm_data差不了多少,不過就是老要塞一個OverLapped結構
{
	OVERLAPPED Overlapped;
	WSABUF DataBuf;
	CHAR Buffer[DATA_BUFSIZE];
	//發送字節數
	DWORD BytesSEND;                                 
	DWORD BytesRECV;
} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;


typedef struct
{
	SOCKET Socket;
} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;


DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID);

void main(void)
{
	SOCKADDR_IN InternetAddr;
	SOCKET Listen;
	SOCKET Accept;
	//完成端口
	HANDLE CompletionPort;
	//系統信息,用於獲取當前系統的CPU核數
	SYSTEM_INFO SystemInfo;
	LPPER_HANDLE_DATA PerHandleData;
	LPPER_IO_OPERATION_DATA PerIoData;
	int i;
	DWORD RecvBytes;
	DWORD Flags;
	DWORD ThreadID;
	WSADATA wsaData;
	DWORD Ret;

	if ((Ret = WSAStartup(0x0202, &wsaData)) != 0)
	{
		printf("WSAStartup failed with error %d\n", Ret);
		return;
	}

	//創建完成端口
	if ((CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) == NULL)
	{
		printf("CreateIoCompletionPort failed with error: %d\n", GetLastError());
		return;
	}
	//獲取系統信息
	GetSystemInfo(&SystemInfo);

	//雙倍CPU運行
	for (i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++)
	{
		//創建線程句柄
		HANDLE ThreadHandle;

		//完成端口掛到線程上面來了,就像管子把灌數據的和讀數據的兩頭都連上了,           
		if ((ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, CompletionPort,
			0, &ThreadID)) == NULL)
		{
			printf("CreateThread() failed with error %d\n", GetLastError());
			return;
		}
		CloseHandle(ThreadHandle);
	}

	//啓動一個監聽socket ,監聽設置爲重疊(OVERLAPPED),只有在服務端纔有必要設置
	if ((Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0,
		WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
	{
		printf("WSASocket() failed with error %d\n", WSAGetLastError());
		return;
	}

	InternetAddr.sin_family = AF_INET;
	//監聽所有IP發送來的消息
	InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	//設定監聽端口
	InternetAddr.sin_port = htons(PORT);

	//將Socket設置爲監聽
	if (bind(Listen, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)
	{
		printf("bind() failed with error %d\n", WSAGetLastError());
		return;
	}

	//開始監聽
	//SOMAXCONN : Maximum queue length specifiable by listen
	if (listen(Listen, SOMAXCONN) == SOCKET_ERROR)
	{
		printf("listen() failed with error %d\n", WSAGetLastError());
		return;
	}

	// 監聽端口打開,就開始在這裏循環,一有socket連上,WSAAccept就創建一個socket,
	// 這個socket 又和完成端口聯上,
	
	// 嘿嘿,完成端口第二次調用那個createxxx函數,爲什麼,留給人思考思考可能更深刻,
	// 反正這套路得來2次,
	// 完成端口completionport和accept socket掛起來了,
	while (TRUE)
	{
		//主線程跑到這裏就等啊等啊,但是線程卻開工了,
		if ((Accept = WSAAccept(Listen, NULL, NULL, NULL, 0)) == SOCKET_ERROR)
		{
			printf("WSAAccept() failed with error %d\n", WSAGetLastError());
			return;
		}

		if ((PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA))) == NULL)
		{
			printf("GlobalAlloc() failed with error %d\n", GetLastError());
			return;
		}
		PerHandleData->Socket = Accept;

	    //把這頭和完成端口completionPort連起來
	    //就像你把漏斗接到管子口上,開始要灌數據了
		//雖然是同一個方法,但是入參不同功能也不同
		if (CreateIoCompletionPort((HANDLE)Accept, CompletionPort, (DWORD)PerHandleData,
			0) == NULL)
		{
			printf("CreateIoCompletionPort failed with error %d\n", GetLastError());
			return;
		}

		//清管子的數據結構,準備往裏面灌數據
		if ((PerIoData = (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_OPERATION_DATA))) == NULL)
		{
			printf("GlobalAlloc() failed with error %d\n", GetLastError());
			return;
		}

		ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
		PerIoData->BytesSEND = 0;
		PerIoData->BytesRECV = 0;
		PerIoData->DataBuf.len = DATA_BUFSIZE;
		PerIoData->DataBuf.buf = PerIoData->Buffer;

		Flags = 0;

		//  accept接到了數據,就放到PerIoData中,而perIoData又通過線程中的函數取出,
		if (WSARecv(Accept, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,
			&(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
		{
			if (WSAGetLastError() != ERROR_IO_PENDING)
			{
				printf("WSARecv() failed with error %d\n", WSAGetLastError());
				return;
			}
		}
	}
	//不會執行到這裏
	printf("Main Thread End\n");
}

//線程一但調用,就老在裏面循環,
//注意,傳入的可是完成端口啊,就是靠它去取出管子中的數據
DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID)
{
	HANDLE CompletionPort = (HANDLE)CompletionPortID;

	DWORD BytesTransferred;
	LPOVERLAPPED Overlapped;
	LPPER_HANDLE_DATA PerHandleData;
	LPPER_IO_OPERATION_DATA PerIoData;
	DWORD SendBytes, RecvBytes;
	DWORD Flags;
	

	while (TRUE)
	{
		//在這裏檢查完成端口部分的數據buf區,數據來了嗎?
		// 這個函數參數要看說明,
		// PerIoData 就是從管子流出來的數據,
		//PerHandleData 也是從管子裏取出的,是何時塞進來的,
	    //就是在建立第2次createIocompletionPort時
		if (GetQueuedCompletionStatus(CompletionPort, &BytesTransferred,
			(LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE) == 0)
		{
			printf("GetQueuedCompletionStatus failed with error %d\n", GetLastError());
			return 0;
		}

		// 檢查數據傳送完了嗎
		if (BytesTransferred == 0)
		{
			printf("Closing socket %d\n", PerHandleData->Socket);
			printf("Receive Message%s\n", PerIoData->Buffer);

			if (closesocket(PerHandleData->Socket) == SOCKET_ERROR)
			{
				printf("closesocket() failed with error %d\n", WSAGetLastError());
				return 0;
			}
			GlobalFree(PerHandleData);
			GlobalFree(PerIoData);
			continue;
		}

	   //看看管子裏面有數據來了嗎?= 0,那是剛收到數據
		if (PerIoData->BytesRECV == 0)
		{
			PerIoData->BytesRECV = BytesTransferred;
		}

		Flags = 0;
		ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));

		PerIoData->DataBuf.len = DATA_BUFSIZE;
		PerIoData->DataBuf.buf = PerIoData->Buffer;

		if (WSARecv(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,
			&(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
		{
			if (WSAGetLastError() != ERROR_IO_PENDING)
			{
				//如果已經讀完了
				printf("WSARecv() failed with error %d\n", WSAGetLastError());
				return 0;
			}
		}
	}
}

我用的VS2019,解決方案配置是Win32。

 

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