测试Winsock的select

说明

实现了一个回显一行字符串的服务器:客户端发送一行字符串,一'\n'结尾,服务器接受完一行后就原封不动地发回给客户端。

windows下对select的能监控的Socket数量是有限制的,若超过,一种方案是再开一个线程。

cpp 复制代码
#ifndef FD_SETSIZE
#define FD_SETSIZE      64
#endif /* FD_SETSIZE */

代码

cpp 复制代码
#include <iostream>
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#include <vector>
#include <memory>

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


struct ClientSocketItem
{
	ClientSocketItem()
	{
		hSocket = NULL;
		memset(szRecv, 0, sizeof(szRecv));
		nRecvSize = 0;
		bNeedWrite = false;
		nWriteOffset = 0;
	}

	SOCKET hSocket;
	char szRecv[1024];
	unsigned int nRecvSize;
	std::string strIp;

	bool bNeedWrite;//接受完成,可以发送
	unsigned int nWriteOffset = 0;
};

std::vector<std::shared_ptr<ClientSocketItem>> g_Clients;


void do_accept(SOCKET hListenSocket)
{
	sockaddr_in mPeerAddr = { 0 };
	int nAddrLen = sizeof(sockaddr);
	SOCKET hClientSocket = accept(hListenSocket, (sockaddr*)(&mPeerAddr), &nAddrLen);
	if (INVALID_SOCKET == hClientSocket)
	{
		std::cout << "accept failed with error "
			<< WSAGetLastError() << std::endl;
	}
	else
	{
		unsigned long nNoBlock = 0;
		ioctlsocket(hClientSocket, FIONBIO, &nNoBlock);

		std::string strIpAddr = inet_ntoa(mPeerAddr.sin_addr);
		std::cout << "accept success, peer ip is " << strIpAddr.c_str() << std::endl;

		auto pClient = std::make_shared<ClientSocketItem>();
		pClient->hSocket = hClientSocket;
		pClient->strIp = strIpAddr;
		g_Clients.push_back(pClient);
	}
}

bool do_read(const std::shared_ptr<ClientSocketItem>& pClient )
{
	if (!pClient)
	{
		return false;
	}

	char c = 0; //测试用,每次只读一个字符
	int nRecvValue = recv(pClient->hSocket, &c, 1, 0);
	if (nRecvValue > 0)
	{
		pClient->szRecv[pClient->nRecvSize] = c;
		pClient->nRecvSize += 1;
		std::cout << "read one char: " << c << std::endl;
		if (c == '\n')
		{
			std::cout << "read finished" << std::endl;
			pClient->bNeedWrite = true;
		}

		return true;
	}
	else if (0 == nRecvValue)
	{
		std::cout << "peer client closed" << std::endl;
		closesocket(pClient->hSocket);
		return false;
	}
	else
	{
		int nError = WSAGetLastError();
		if (WSAEWOULDBLOCK != nError)
		{
			std::cerr << "recv failed with error " << nError << std::endl;
			closesocket(pClient->hSocket);
			return false;
		}

		std::cout << "next recv" << std::endl;
		return true;
	}
}


bool do_write(const std::shared_ptr<ClientSocketItem>& pClient)
{
	if (!pClient)
	{
		return false;
	}

	if (!pClient->bNeedWrite)
	{
		return true;
	}

	//测试用,每次只发送一个字符
	int nRet = send(pClient->hSocket, pClient->szRecv + pClient->nWriteOffset, 1, 0);
	if (nRet >= 0)
	{
		std::cout << "send one char: " << pClient->szRecv[pClient->nWriteOffset] << std::endl;

		pClient->nWriteOffset += 1;
		if (pClient->nWriteOffset == pClient->nRecvSize)
		{
			std::cout << "send finished, close client(" << pClient->strIp.c_str()
				<< ")" << std::endl;
			pClient->bNeedWrite = false;
			closesocket(pClient->hSocket);
			return false;
		}

		return true;
	}
	else
	{
		int nError = WSAGetLastError();
		if (WSAEWOULDBLOCK != nError)
		{
			std::cerr << "send failed with error " << nError << std::endl;
			closesocket(pClient->hSocket);
			return false;
		}

		std::cout << "next send" << std::endl;
		return true;
	}
}



int main(int argc, char* argv)
{
	WORD wVersionRequested = MAKEWORD(2, 2);
	WSADATA wsaData = { 0 };
	int err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		return -1;
	}

	if (LOBYTE(wsaData.wVersion) != 2 ||
		HIBYTE(wsaData.wVersion) != 2)
	{
		WSACleanup();
		return -1;
	}

	SOCKET hListenSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == hListenSocket)
	{
		std::cerr << "create socket failed with error " << WSAGetLastError()
			<< std::endl;
		return -1;
	}

	sockaddr_in mSockAddrIn = { 0 };
	mSockAddrIn.sin_family = AF_INET;
	mSockAddrIn.sin_port = htons((u_short)8878);
	mSockAddrIn.sin_addr.S_un.S_addr = inet_addr("0.0.0.0");
	if (SOCKET_ERROR == bind(hListenSocket, (sockaddr*)(&mSockAddrIn),
		sizeof(sockaddr)))
	{
		std::cerr << "bind failed with error " << WSAGetLastError() << std::endl;
		return -1;
	}

	if (SOCKET_ERROR == listen(hListenSocket, SOMAXCONN))
	{
		std::cerr << "listen failed with error " << WSAGetLastError() << std::endl;
		return -1;
	}


	std::vector<SOCKET> mAllClients;
	while (true)
	{
		fd_set readfds;
		fd_set writefds;
		fd_set exceptfds;
		FD_ZERO(&readfds);
		FD_ZERO(&writefds);
		FD_ZERO(&exceptfds);

		FD_SET(hListenSocket, &readfds);
		//FD_SET(hListenSocket, &writefds);
		//FD_SET(hListenSocket, &exceptfds);

		for (auto it = g_Clients.begin(); it != g_Clients.end(); ++it)
		{
			std::shared_ptr<ClientSocketItem> pClientItem = *it;
			if (!pClientItem) continue;
			FD_SET(pClientItem->hSocket, &readfds);

			if (pClientItem->bNeedWrite)//否则select会一直有事件
			{
				FD_SET(pClientItem->hSocket, &writefds);
			}
			
			FD_SET(pClientItem->hSocket, &exceptfds);
		}

		int nRet = select(0, &readfds, &writefds, &exceptfds, nullptr);
		std::cout << "select return with " << nRet << std::endl;
		if (nRet > 0)
		{
			//read
			if (FD_ISSET(hListenSocket, &readfds))
			{
				do_accept(hListenSocket);
			}
			for (auto it = g_Clients.begin(); it != g_Clients.end();)
			{
				std::shared_ptr<ClientSocketItem> pClientItem = *it;
				if (pClientItem && FD_ISSET(pClientItem->hSocket, &readfds))
				{
					if (!do_read(pClientItem))
					{
						it = g_Clients.erase(it);
						continue;
					}
				}

				++it;
			}

			//write
			for (auto it = g_Clients.begin(); it != g_Clients.end();)
			{
				std::shared_ptr<ClientSocketItem> pClientItem = *it;
				if (pClientItem && FD_ISSET(pClientItem->hSocket, &writefds))
				{
					if (!do_write(pClientItem))
					{
						it = g_Clients.erase(it);
						continue;
					}
				}

				++it;
			}//end of for

			//error 
			for (auto it = g_Clients.begin(); it != g_Clients.end();)
			{
				std::shared_ptr<ClientSocketItem> pClientItem = *it;
				if (pClientItem && FD_ISSET(pClientItem->hSocket, &exceptfds))
				{
					std::cerr << "client socket except, close and remove it" << std::endl;
					closesocket(pClientItem->hSocket);
					it = g_Clients.erase(it);
					continue;
				}

				++it;
			}//end of for
		}
		else
		{
			std::cerr << "select failed with error " << WSAGetLastError() << std::endl;
		}

	}


	return 0;
}
相关推荐
hjjdebug11 小时前
select 函数详解
select·c 语言
曲幽7 天前
告别重复劳动:SQL Server存储过程实战手册,从入门到高效协作
sql·select·cursor·declare·trigger·procedure
源代码•宸8 天前
Golang原理剖析(channel面试与分析)
开发语言·经验分享·后端·面试·golang·select·channel
源代码•宸9 天前
Golang原理剖析(channel源码分析)
开发语言·后端·golang·select·channel·hchan·sudog
源代码•宸16 天前
Golang语法进阶(Sync、Select)
开发语言·经验分享·后端·算法·golang·select·pool
Ronin3051 个月前
【Linux网络】多路转接select
linux·网络·select·多路转接
AI2中文网2 个月前
AppInventor2 使用 SQLite(三)带条件过滤查询表数据
数据库·sql·sqlite·select·app inventor 2·appinventor·tableview
无聊的小坏坏3 个月前
Select 服务器实战教学:从 Socket 封装到多客户端并发
服务器·select·io多路复用
Wy_编程4 个月前
高并发服务器-多路IO转接-select
服务器·select·高并发
眰恦ゞLYF5 个月前
服务器类型与TCP并发服务器构建(SELECT)
服务器·select·io多路复用