C++網路程式設計伺服器select模型(參考)
阿新 • • 發佈:2019-01-10
#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; }