#include<iostream> #include<vector> #include<WinSock2.h> using namespace std; #pragma comment(lib,"Ws2_32.lib") const int nPort=10000; const int buf_len=1024; struct Connection{ SOCKET hSocket; char Buffer[buf_len]; int nBytes; Connection(SOCKET socket):hSocket(socket),nBytes(0)//结构体构造函数,hSocket的初始值为socket,nBytes的初始值为0; {} }; //注意:要加分号 typedef vector<Connection*> ConnectionList; SOCKET BindListen() { SOCKET HSListen=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);//创建套接字 sockaddr_in sdListen; sdListen.sin_family=AF_INET; sdListen.sin_port=htons(nPort); sdListen.sin_addr.s_addr=htonl(INADDR_ANY); if(bind(HSListen,(sockaddr*)&sdListen,sizeof(sockaddr_in))==SOCKET_ERROR) { cout<<"绑定失败"<<endl; return INVALID_SOCKET; } else return HSListen; } //分析conns中的客户连接,根据分析结果填充各个fd_set void ResetFDset(fd_set &fdRead,fd_set &fdWrite,fd_set &fdExcept,SOCKET sdListen,const ConnectionList &conns) { //首先清空各个fd_set FD_ZERO(&fdRead); FD_ZERO(&fdWrite); FD_ZERO(&fdExcept); //监听套接字每次都被放入fdRead,也就是说每次调用select都会去检测是否有新的连接请求 FD_SET(sdListen,&fdRead); FD_SET(sdListen,&fdExcept); ConnectionList::const_iterator it=conns.begin();//获取vector中首个元素的地址,vector中的元素是连续存储的,类似数组 //不同于链 for(;it!=conns.end();++it) { Connection *pConn=*it;//取出地址为it的内容,其内容为 Connection *类型 //客户连接的缓冲区还有空间,可以继续接收数据,就需要把其对应的套接字句柄放入fdRead中 if(pConn->nBytes<buf_len) { FD_SET(pConn->hSocket,&fdRead); } //客户连接的缓冲区还有数据需要发送,就需要把其对应的套接字句柄放入到fdWrite中 if(pConn->nBytes>0) { FD_SET(pConn->hSocket,&fdWrite); } //每个连接的套接字句柄都需要放到fd_Except中,以便select 能够检测其I/O错误 FD_SET(pConn->hSocket,&fdExcept); } } //检查是否有新的客户连接 int CheckAccept(const fd_set &fdRead,const fd_set &fdExcept,SOCKET sdListen,ConnectionList &conns) { int lastErr=0; //首先检查是否发生错误 if(FD_ISSET(sdListen,&fdExcept)) { int errlen=sizeof(lastErr); getsockopt(sdListen,SOL_SOCKET,SO_ERROR,(char *)&lastErr,&errlen); cout<<"I/O error"<<lastErr<<endl; return SOCKET_ERROR; } //检查fdRead中是否包含了监听套接字,如果是,则证明有新的连接请求,可以用accept if(FD_ISSET(sdListen,&fdRead)) { //由于fd_set有大小限制(最多为FD_SETSIZE),所以当已有客户连接到达这个限制时,不再接受连接请求 if(conns.size()>=FD_SETSIZE-1) { return 0; } //调用accept来接受连接请求 sockaddr_in saRemote; int nSize=sizeof(sockaddr_in); SOCKET sd=accept(sdListen,(sockaddr *)&saRemote,&nSize); lastErr=WSAGetLastError(); //为了程序的健壮性,检查WSAEWOULDBLOCK错误 if(sd==INVALID_SOCKET&&lastErr!=WSAEWOULDBLOCK) { cout<<"接收错误"<<lastErr<<endl; return SOCKET_ERROR; } if(sd!=INVALID_SOCKET) { //获取了新的客户连接套接字句柄,需要把它设为非阻塞模式,以便对其使用select函数 u_long nNoBlock=1; if(ioctlsocket(sd,FIONBIO,&nNoBlock)==SOCKET_ERROR) { cout<<"ioctlsocket error"<<WSAGetLastError()<<endl; return SOCKET_ERROR; } //为新的客户连接创建一个Connection对象,并且加入到conns中去 conns.push_back(new Connection(sd)); } } return 0; } //被动安全关闭连接 //该函数被调用的原因是recv返回0,代表对方关闭了连接(即对方停止了数据发送) //如果本地还有数据没有发送出去,则需要继续调用send来把剩余的数据发送出去,之后 //再调用shutdown来停止发送数据 bool PassiveShutdown(SOCKET sd,const char *buff,int len) { if(buff!=NULL && len>0) { //使用阻塞I/O模型把剩余数据发送出去 u_long nNoBlock=0; if(ioctlsocket(sd,FIONBIO,&nNoBlock)==SOCKET_ERROR) { cout<<"ioctlsocket error"<<WSAGetLastError()<<endl; return false; } int nSent=0; //把剩余数据发送出去 while(nSent<len) { int nTemp=send(sd,&buff[nSent],len=nSent,0); if(nTemp>0) { nSent+=nTemp; } else if(nTemp==SOCKET_ERROR) {cout<<"发送失败"<<WSAGetLastError()<<endl; return false; } else { cout<<"对方关闭连接"<<endl; break; } } } if(shutdown(sd,SD_SEND)==SOCKET_ERROR) { cout<<"关闭失败"<<WSAGetLastError()<<endl; return false; } return true; } //select成功返回并标明recv就绪,调用recv接收数据 bool TryRead(Connection *pConn) { //pConn->buffer+pConn->nBytes表示缓冲区Buffer可用空间的开始位置 //buf_len-pConn->nBytes表示缓冲区Buffer可用空间的大小 int nRet=recv(pConn->hSocket ,pConn->Buffer+pConn->nBytes,buf_len-pConn->nBytes,0); if(nRet>0) { pConn->nBytes+=nRet; return true; } //对方关闭了连接,调用被动安全关闭连接PassiveShutdown函数 else if(nRet==0) { cout<<"对方关闭连接"<<endl; PassiveShutdown(pConn->hSocket,pConn->Buffer,pConn->nBytes); return false; } //发生了错误。为了程序的健壮性,检查WSAEWOULDBLOCK错误 else { int lastErr=WSAGetLastError(); if(lastErr==WSAEWOULDBLOCK) { return true; } cout<<"接收失败"<<lastErr<<endl; return false; } } //select成功返回并表明send就绪,调用send发送数据 bool TryWrite(Connection *pConn) { int nSent=send(pConn->hSocket,pConn->Buffer,pConn->nBytes,0); if(nSent>0) { pConn->nBytes-=nSent; //Buffer中还有数据尚未发送出去 if(pConn->nBytes>0) { //把尚未发送的数据从Buffer的尾部移动到Buffer头部 memmove(pConn->Buffer,pConn->Buffer+nSent,pConn->nBytes); } return true; } //对方关闭了连接,调用了被动安全关闭连接PassiveShutdown函数 else if(nSent==0) { cout<<"对方关闭连接"<<endl; PassiveShutdown(pConn->hSocket,pConn->Buffer,pConn->nBytes); return false; } //发生了错误,为了程序的健壮性,检查WSAEWOULDBLOCK错误 else { int lastErr=WSAGetLastError(); if(lastErr==WSAEWOULDBLOCK) { return true; } cout<<"发送失败"<<lastErr<<endl; return false; } } //检查当前所有客户连接,看看它们是否可读(recv),可写(send),还是I/O错误 void CheckConn(const fd_set &fdRead,const fd_set &fdWrite,const fd_set &fdExcept,ConnectionList &conns) { ConnectionList::iterator it=conns.begin(); while(it!=conns.end()) { Connection *pConn=*it; bool bOk=true; //检查当前连接是否发生I/O错误 if(FD_ISSET(pConn->hSocket,&fdExcept)) { bOk=false; int lastErr; int errlen=sizeof(lastErr); getsockopt(pConn->hSocket,SOL_SOCKET,SO_ERROR,(char*)&lastErr,&errlen); cout<<"I/O error"<<lastErr<<endl; } else { //检查当前连接是否可读 if(FD_ISSET(pConn->hSocket,&fdRead)) { bOk=TryRead(pConn); } } //发生了错误,关闭套接字并把其对应的Connection对象从conns中移除 if(bOk==false) { closesocket(pConn->hSocket); delete pConn; it=conns.erase(it); } else { ++it; } } } //select就绪通告I/O模型服务器的主体函数 void DoWork() { //获取监听套接字 SOCKET sdListen=BindListen(); if(sdListen==INVALID_SOCKET) {} //定义conns,用于保存当前所有客户连接 ConnectionList conns; //把监听套接字设为非阻塞模式 u_long nNoBlock=1; if(ioctlsocket(sdListen,FIONBIO,&nNoBlock)==SOCKET_ERROR) { cout<<"ioctlsocket 错误"<<WSAGetLastError()<<endl; } fd_set fdRead,fdWrite,fdExcept; while(true) { //分析conns中所有客户连接并把它们放到合适的fd_set中 ResetFDset(fdRead,fdWrite,fdExcept,sdListen,conns); //调用select,等待就绪的套接字I/O操作 int nRet=select(0,&fdRead,&fdWrite,&fdExcept,NULL); if(nRet<=0) { cout<<"select error"<<WSAGetLastError()<<endl; break; } //检查是否有新的客户连接请求 nRet=CheckAccept(fdRead,fdExcept,sdListen,conns); if(nRet==SOCKET_ERROR) { break; } //检查客户连接 CheckConn(fdRead,fdWrite,fdExcept,conns); } //释放资源 ConnectionList::iterator it=conns.begin(); for(;it!=conns.end();++it) { closesocket((*it)->hSocket); delete *it; } if(sdListen!=INVALID_SOCKET) closesocket(sdListen); } int main() { WSAData wsaData; int nCode; if((nCode=WSAStartup(MAKEWORD(2,2),&wsaData))!=0) { cout<<"初始化失败"<<nCode<<endl; return -1; } DoWork(); return 0; }