zoukankan      html  css  js  c++  java
  • C++网络编程服务器select模型(参考)

    #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;
    }

  • 相关阅读:
    [King.yue]Ext.net 页面布局Flex
    [King.yue]Ext.net 弹出Windows窗体注意的事项
    [Irving]SqlServer 标量函数 详解【转】
    [Andrew]Ext.net前台弹框
    [King.yue]Ext中Grid得到选择行数据的方法总结
    [zouxianghui] 清空GridPanel的checkbox选中行
    [BILL WEI]SQL 存储过程学习
    [Tommas] 测试场景 VS 测试用例 哪个更好?(转)
    winform 打印小票
    html5 input 标签
  • 原文地址:https://www.cnblogs.com/zztong/p/6695269.html
Copyright © 2011-2022 走看看