版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
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;
}
}
}
}
下面是和客户端交互的结果: