1. 程式人生 > >基於select非阻塞模型的服務端程式示例(Winsock2實現)

基於select非阻塞模型的服務端程式示例(Winsock2實現)

/*
總結:
	①無論阻塞還是非阻塞,select都不會立即返回,select就是用於非阻塞模型中的。
	②將SOCKET置於非阻塞模式下時,處理連線或處理收發資料的Socket API都會立即返回。
	③select會監視fd_set中的所有套接字,一旦有套接字發生IO事件(包括客戶端的連線請求),select會立即返回,
	  並將fd_set中沒有發生IO事件的套接字移除。由此可見,如果想讓程式監視所有套接字的IO事件,應用程式應該儲存所有的
	  套接字控制代碼,並在呼叫select之前將所有的套接字新增到fd_set中。
	④阻塞模式下的accept呼叫返回的是阻塞模式的SOCKET,非阻塞模式返回的是非阻塞模式的SOCKET。
	⑤對於連線請求和recv的套接字,應將其置於readSet中,即select中的第二個引數。
*/

#define _CRT_SECURE_NO_WARNINGS

#ifdef UNICODE
#undef UNICODE
#endif

#ifdef _UNICODE
#undef _UNICODE
#endif

#include <stdio.h>
#include <tchar.h>
#include <WinSock2.h>

#pragma comment(lib,"ws2_32.lib")

void ShowSystemError(DWORD dwError)
{
	HLOCAL hlocal = NULL;
	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
		NULL, dwError, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), (PTSTR)&hlocal, 0, NULL);
	_tprintf(_T("%s\n"), (TCHAR*)LocalLock(hlocal));
	LocalFree(hlocal);
}

struct Node
{
	SOCKET sc;
	SOCKADDR_IN addr;
	Node* next;
	Node* prev;
};

int _tmain(int argc,TCHAR* argv[])
{
	int nRet = 0;
	WSADATA wsadata;
	nRet = WSAStartup(MAKEWORD(2, 2), &wsadata);
	if (nRet != 0)
	{
		printf("WSAStartup呼叫失敗,錯誤資訊:");
		ShowSystemError(nRet);
		getchar();
		return -1;
	}

	SOCKET serverSocket = INVALID_SOCKET;
	serverSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (serverSocket == INVALID_SOCKET)
	{
		printf("socket呼叫失敗,錯誤資訊:");
		ShowSystemError(WSAGetLastError());
		goto END;
	}

	SOCKADDR_IN serverAddr;
	ZeroMemory(&serverAddr, sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_addr.S_un.S_addr = INADDR_ANY;
	serverAddr.sin_port = htons(8888);
	if (bind(serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) != 0)
	{
		printf("bind呼叫失敗,錯誤資訊:");
		ShowSystemError(WSAGetLastError());
		goto END;
	}

	if (listen(serverSocket, 5) != 0)
	{
		printf("listen呼叫失敗,錯誤資訊:");
		ShowSystemError(WSAGetLastError());
		goto END;
	}

	Node* pHead = new Node;
	pHead->prev = pHead->next = NULL;

	Node* pNewNode = new Node;
	pNewNode->sc = serverSocket;
	memcpy(&pNewNode->addr,&serverAddr,sizeof(SOCKADDR_IN));
	pNewNode->prev = pHead;
	pNewNode->next=pHead->next;
	pHead->next = pNewNode;

	FD_SET readSet;

	SOCKET clientSocket;
	SOCKADDR_IN clientAddr;
	int nAddrLen;
	Node* p;
	Node* pTemp;
	char recvBuf[1024];
	while (true)
	{
		FD_ZERO(&readSet);
		for (p = pHead->next; p != NULL; p = p->next)
			FD_SET(p->sc,&readSet);
		nRet = select(0,&readSet,NULL,NULL,NULL);
		if (nRet < 0)
		{
			printf("select呼叫失敗,錯誤資訊:");
			ShowSystemError(WSAGetLastError());
			continue;
		}
		for (p = pHead->next; p != NULL; p = p->next)
		{
			if (FD_ISSET(p->sc, &readSet))
			{
				if (p->sc == serverSocket)
				{
					clientSocket = INVALID_SOCKET;
					ZeroMemory(&clientAddr,sizeof(SOCKADDR_IN));
					nAddrLen = sizeof(SOCKADDR_IN);
					clientSocket = accept(serverSocket,(SOCKADDR*)&clientAddr,&nAddrLen);
					if (clientSocket == INVALID_SOCKET)
					{
						printf("accept呼叫失敗,錯誤資訊:");
						ShowSystemError(WSAGetLastError());
						continue;
					}
					printf("%s:%d的客戶成功連線到伺服器。\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
					pNewNode = new Node;
					pNewNode->sc = clientSocket;
					memcpy(&pNewNode->addr, &clientAddr, sizeof(SOCKADDR_IN));
					pNewNode->prev = pHead;
					pNewNode->next = pHead->next;
					pHead->next->prev = pNewNode;
					pHead->next = pNewNode;
				}
				else
				{
					ZeroMemory(recvBuf,sizeof(recvBuf));
					nRet = recv(p->sc,recvBuf,sizeof(recvBuf),0);
					if (nRet < 0)
					{
						printf("recv呼叫失敗,錯誤資訊:");
						ShowSystemError(WSAGetLastError());
						if (closesocket(p->sc) != 0)
						{
							printf("closesocket呼叫失敗,錯誤資訊:");
							ShowSystemError(WSAGetLastError());
						}
						pTemp=p->prev;
						p->prev->next = p->next;
						p->next->prev = p->prev;
						delete p;
						p = pTemp;
					}
					else if (nRet == 0)
					{
						printf("%s:%d的客戶主動關閉連線。\n", inet_ntoa(p->addr.sin_addr), ntohs(p->addr.sin_port));
						if (closesocket(p->sc) != 0)
						{
							printf("closesocket呼叫失敗,錯誤資訊:");
							ShowSystemError(WSAGetLastError());
						}
						pTemp = p->prev;
						p->prev->next = p->next;
						p->next->prev = p->prev;
						delete p;
						p = pTemp;
					}
					else
					{
						//解析資料
						printf("%s:%d的客戶傳送:%s\n", inet_ntoa(p->addr.sin_addr), ntohs(p->addr.sin_port),recvBuf);
					}
				}
			}
		}
	}

END:
	if (serverSocket != INVALID_SOCKET)
	{
		if (closesocket(serverSocket) != 0)
		{
			printf("closesocket呼叫失敗,錯誤資訊:");
			ShowSystemError(WSAGetLastError());
		}
	}
	if (WSACleanup() != 0)
	{
		printf("WSACleanup呼叫失敗,錯誤資訊:");
		ShowSystemError(WSAGetLastError());
	}
	getchar();
	return 0;
}