Visual C++网络编程学习(3)-就绪通告I/O模型

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/weixin_43165699/article/details/100151495

Visual C++网络编程学习(3)-就绪通告I/O模型(select)

       在此之前,我们搭建了TCP的阻塞模型与UDP的阻塞模型。做完之后觉得暗自窃喜了一下,但事后发现还是有几个问题:
       1.之前我们搭建的是阻塞I/O模型,该模型在使用accept,Receive函数时,如果没有数据达到,当前线程将会保持阻塞,此时对进行其他操作会产生一定的影响。可是如果将该模型内的套接字设置为非阻塞时,对accept,Receive函数的调用时机没有一个好的把握。
       2.对于服务器程序,如何支持多个客户端的连接。
       对于上述问题,select模型可以很好的解决。
      首先,select可以帮助我们决定我们检测系统状态,当得知响应的I/O操作的条件已经满足时,会通知线程调用I/O函数,这时一般情况能够确保成功而不发生阻塞。其次,select可以同时检测多个套接字的多种操作,所以任何一个套接字操作就绪,select都会发出通知,这也就是说select模型具有能够同时等待多个套接字上的多种操作的能力。
      现将服务器的代码公布如下,客户端的代码直接采用https://blog.csdn.net/weixin_43165699/article/details/100066402的客户端代码即可。
#include<iostream>
#include<WINSOCK2.h>
#include <vector>
#include <string>//引用头文件
#pragma comment(lib,"Ws2_32.lib")
using namespace std;
#define SERVEPORT 10000
#define nBuffersize 10000
#define CLIENTNUMBERMAX 64 
#define nBuffsize 1000
struct Connection
{
	int numberClient;
	SOCKET hsocket;
	char Buffer[nBuffersize];
	int nBytes;
	Connection(int numberClient,SOCKET socket) :numberClient(numberClient),hsocket(socket), nBytes(0) {}
};
typedef vector<Connection *> Connectionlist;
SOCKET BindListen();           
int DoWork();                 
void ResetFDSet(fd_set& fdRead, fd_set& fdWrite, fd_set& fdExcept, SOCKET SocketServe, Connectionlist &conns);
int CheckAccept(fd_set& fdRead, fd_set& fdWrite, fd_set& fdExcept, SOCKET SocketServe, Connectionlist &conns);
void CheckClientConnection(fd_set& fdRead, fd_set& fdWrite, fd_set& fdExcept, Connectionlist &conns);
bool Read(Connection *PConn);
void GetTime();              
bool SendClientMes(Connectionlist &conns);
void ThreadFunc();           
static Connectionlist conns;
static int ClientNumber;  

SOCKET BindListen()
{
	WSADATA wsaData = { 0 };
	WORD wVer = MAKEWORD(2, 2);
    if (0 != WSAStartup(wVer, &wsaData))
	{
	GetTime();
	 cout << "初始化错误,错误代码:" << WSAStartup(wVer, &wsaData) << endl;
	 return 0;
	}
	SOCKET SocketServe=NULL;
	if (NULL != SocketServe)	
	{
	closesocket(SocketServe);
	}
	SocketServe = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == SocketServe)
	{
	GetTime();
	cout << "创建套接字错误,错误代码:" << WSAGetLastError() << endl;
	return 0;
      }
	GetTime();
	cout << "套接字创建成功!" << endl;
	sockaddr_in soServeAddr;         //服务器地址

	soServeAddr.sin_family = AF_INET;
	soServeAddr.sin_port = htons(SERVEPORT);
	soServeAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
		
	if (bind(SocketServe, (sockaddr*)&soServeAddr, sizeof(soServeAddr)))
	{
	GetTime();
	cout << "套接字绑定错误,错误代码" << WSAGetLastError() << endl;
	return 0;
	}
	if (listen(SocketServe, CLIENTNUMBERMAX) == SOCKET_ERROR)
	{
	GetTime();
	cout << "监听失败" << WSAGetLastError() << endl;
	return 0;
		}
	GetTime();
	cout << "服务器端已启动等待连接..." << endl;
	return SocketServe;
}
int  DoWork()
{
	SOCKET SocketServe = BindListen();
    u_long nNoBlock = 1;
	if (ioctlsocket(SocketServe,FIONBIO,&nNoBlock)==SOCKET_ERROR)
	{
	GetTime();
	 cout << "套接字模式设置失败,错误代码:" << WSAGetLastError() << endl;
	 return 0;
		}
	fd_set fdRead, fdWrite, fdExcept;
	ClientNumber = 0;
	while (true)
	{
		ResetFDSet(fdRead, fdWrite, fdExcept,SocketServe,conns);
		int nRet = select(0,&fdRead,&fdWrite,&fdExcept,NULL);
		if (nRet<=0)
		{
			GetTime();
			cout << "select error" << WSAGetLastError() << endl;
			break;
		}
		nRet = CheckAccept(fdRead, fdWrite, fdExcept,SocketServe,conns);
		if (nRet==SOCKET_ERROR)
		{
			break;
		}
		CheckClientConnection(fdRead, fdWrite, fdExcept,conns);
	}
	Connectionlist::iterator it = conns.begin();
	for (; it!=conns.end(); ++it)
	{
		closesocket((*it)->hsocket);
		delete *it;
	}
	ClientNumber = 0;
	if (SocketServe!=INVALID_SOCKET)
	{
		closesocket(SocketServe);
	}
	return 0;
}
void ResetFDSet(fd_set& fdRead, fd_set& fdWrite, fd_set& fdExcept, SOCKET SocketServe, Connectionlist &conns)
{
    FD_ZERO(&fdRead);
	FD_ZERO(&fdWrite);
	FD_ZERO(&fdExcept);
	FD_SET(SocketServe, &fdRead);
	FD_SET(SocketServe, &fdExcept);

	Connectionlist::iterator it = conns.begin();
	for (; it!= conns.end();++it)
	{
		Connection *pConn = *it;
	    FD_SET(pConn->hsocket, &fdRead);
		FD_SET(pConn->hsocket, &fdExcept);
	}
}
int CheckAccept( fd_set & fdRead,  fd_set & fdWrite,  fd_set & fdExcept, SOCKET SocketServe, Connectionlist &conns)
{

	int lastErr = 0;
	if (FD_ISSET(SocketServe,&fdExcept))
	{
		int errlen = sizeof(lastErr);
		getsockopt(SocketServe,SOL_SOCKET,SO_ERROR,(char*)&lastErr,&errlen);
		GetTime();
		cout << "I/O error" << lastErr << endl;
		return SOCKET_ERROR;
	}
	if (FD_ISSET(SocketServe,&fdRead))
	{
		sockaddr_in soClientAddr;
		int nSize = sizeof(soClientAddr);
		SOCKET sd = accept(SocketServe,(sockaddr*)&soClientAddr, &nSize);
		lastErr = WSAGetLastError();
		if (sd==INVALID_SOCKET&&lastErr!=WSAEWOULDBLOCK)
		{
			cout << "accept error" << lastErr << endl;
			return SOCKET_ERROR;
		}
		if (sd!=INVALID_SOCKET)
		{
			u_long nNoBlock = 1;
			if (ioctlsocket(sd, FIONBIO, &nNoBlock) == SOCKET_ERROR)
			{
				GetTime();
				cout << "套接字模式设置失败,错误代码:" << WSAGetLastError() << endl;
				return SOCKET_ERROR;
			}
			ClientNumber++;
			conns.push_back(new Connection(ClientNumber,sd));
			GetTime();
			cout <<"第【"<< ClientNumber<< "】个套接字连接成功"<< endl;
		}
	}
	return 0;
}
void CheckClientConnection(fd_set & fdRead, fd_set & fdWrite, fd_set & fdExcept, Connectionlist &conns)
{
	Connectionlist::iterator it = conns.begin();
	while (it!=conns.end())
	{
		Connection * pConn = *it;
		bool BOK = true;
		if (FD_ISSET(pConn->hsocket, &fdExcept))
		{
			BOK = false;
			int lastErr;
			int errlen = sizeof(lastErr);
			getsockopt(pConn->hsocket, SOL_SOCKET, SO_ERROR, (char *)&lastErr, &errlen);
			GetTime();
			cout << "I/O 错误" << lastErr << endl;
		}
		else
		{
			if (FD_ISSET(pConn->hsocket, &fdRead))
			{
				BOK = Read(pConn);
			}
			else if (FD_ISSET(pConn->hsocket, &fdWrite))
			{
				//BOK = TryWrite(pConn);
			}
		}
		if (BOK==false)
		{
			ClientNumber--;                        //客户端数量减少
			closesocket(pConn->hsocket);
			delete pConn;
			it = conns.erase(it);
		}
		else
		{
			++it;
		}
	}
}
bool Read(Connection * PConn)
{
	int nRet = recv(PConn->hsocket, PConn->Buffer, nBuffersize,0);
	if (nRet>0)
	{
		GetTime();
		cout <<"第【"<< PConn->numberClient<< "】个客户端接收到数据:";
		for (int  i = 0; i < nRet; i++)
		{
			cout << *(PConn->Buffer + i);
		}
		cout << " "<<endl;
		return true;
	}
	else if (nRet == 0)
	{
		GetTime();
		cout << "第【" << PConn->numberClient << "】个客户端连接已经断开" << endl;
		return false;
	}
	else
	{
		int lastErr = WSAGetLastError();
		if (lastErr==WSAEWOULDBLOCK)
		{
			return true;
		}
		GetTime();
		cout << "第【" << PConn->numberClient << "】个客户端连接已经断开" << endl;
		return false;
	}
	return false;
}
void GetTime()
{
	SYSTEMTIME st;
	GetLocalTime(&st);
	cout << st.wYear << ":" << st.wMonth << ":" << st.wDay << ":" << st.wHour << ":" << st.wMinute << ":" << st.wSecond<<"  ";
}
bool SendClientMes(Connectionlist &conns)
{
	int number;
	GetTime();
	cout << "请输入客户端号:" ;
	cin >> number;
	cout <<" "<<endl;
	if (number>ClientNumber)
	{
		GetTime();
		cout << "该客户端当前未连接" << endl;
		return false;
	}
	else
	{
		Connectionlist::iterator it = conns.begin();
	
		while ((*it)->numberClient<= number)
		{
			if ((*it)->numberClient==number)
			{
				GetTime();
				cout << "请输入你要发送的数据:";
				string s1;
				cin >> s1;
				int buffsize = nBuffersize;
				char *revBuffer = (char*)malloc(sizeof(char)*buffsize);
				int i = 0;
				for (i = 0; i < s1.size(); i++)
				{
					*(revBuffer + i) = s1[i];
				}
				int nSent = send((*it)->hsocket, revBuffer, i, 0);

				if (nSent>0)
				{
					if (nSent == i)
					{
						GetTime();
						cout << "第【" << (*it)->numberClient << "】个客户端数据已经发送,发送正常。" << endl;
					}
					else
					{
						GetTime();
						cout << "第【" << (*it)->numberClient << "】个客户端数据未发完。" << endl;
					}
					return true;
				}
				else if (nSent == 0)
				{
					GetTime();
					cout << "第【" << (*it)->numberClient << "】个客户端连接已经断开" << endl;
					return false;
				}
				else
				{
					int lastErr = WSAGetLastError();
					if (lastErr == WSAEWOULDBLOCK)
					{
						return true;
					}
					GetTime();
					cout << "第【" << (*it)->numberClient << "】个客户端连接已经断开" << endl;
					return false;
				}
			}
			it++;
		}

	}
	return true;
}
void ThreadFunc()
{
	DoWork();
}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
void main()
{
	cout << "************************服务器测试程序************************"<<endl;
	HANDLE hThread;
	DWORD ThreadID;
	hThread = CreateThread(NULL, 0,(LPTHREAD_START_ROUTINE)ThreadFunc,NULL,0,&ThreadID);
	while (true)
	{
		int t;
		cin >> t;
		switch (t)
		{
		case 1:
		{
			SendClientMes(conns);
			break;
		}
		default:
		{
			break;
		}
			
		}
		
	}
}

下面是和客户端交互的结果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43165699/article/details/100151495