socket 五种模型理解之一---------select模型

一、选择模型,即select模型;

Echo服务器

#include<winsock.h>
#include<stdio.h> 
#definePORT       5150
#defineMSGSIZE    1024 
#pragmacomment(lib, "ws2_32.lib") 
int    g_iTotalConn= 0;
SOCKETg_CliSocketArr[FD_SETSIZE]; 
DWORD WINAPIWorkerThread(LPVOID lpParameter); 
int main()
{
  WSADATA     wsaData;
  SOCKET      sListen,sClient;
  SOCKADDR_INlocal, client;
  int         iaddrSize= sizeof(SOCKADDR_IN);
  DWORD       dwThreadId;
 
  //Initialize Windows socket library
  WSAStartup(0x0202,&wsaData);
 
  //Create listening socket
  sListen= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 
  // Bind
  local.sin_addr.S_un.S_addr= htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
  bind(sListen,(struct sockaddr *)&local, sizeof(SOCKADDR_IN));
 
  //Listen
  listen(sListen,3);
 
  //Create worker thread
  CreateThread(NULL,0, WorkerThread, NULL, 0, &dwThreadId); 
 
  while(TRUE)
  {
    //Accept a connection
    sClient= accept(sListen, (struct sockaddr *)&client, &iaddrSize);
    printf("Acceptedclient:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
 
    //Add socket to g_CliSocketArr
    g_CliSocketArr[g_iTotalConn++]= sClient;
  }
 
  return0;
}
 
DWORD WINAPIWorkerThread(LPVOID lpParam)//WorkThread
{
  int               i;
  fd_set         fdread;
  int                ret;
  structtimeval tv = {1, 0};
  char           szMessage[MSGSIZE];
 
  while(TRUE)
  {
    FD_ZERO(&fdread);                    //将fdread初始化空集
    for(i = 0; i < g_iTotalConn; i++)
    {
      FD_SET(g_CliSocketArr,&fdread);        //将要检查的套接口加入到集合中
    }
 
    //We only care read event
    ret= select(0, &fdread, NULL, NULL, &tv);        //每隔一段时间,检查可读性的套接口
 
    if(ret == 0)
    {
      //Time expired
      continue;
    }
 
    for(i = 0; i < g_iTotalConn; i++)
    {
      if(FD_ISSET(g_CliSocketArr, &fdread))//如果可读
      {
        //A read event happened on g_CliSocketArr
        ret= recv(g_CliSocketArr, szMessage, MSGSIZE, 0);
    if(ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() ==WSAECONNRESET))
     {
      //Client socket closed
           printf("Clientsocket %d closed.\n", g_CliSocketArr);
      closesocket(g_CliSocketArr);
      if(i < g_iTotalConn - 1)
         {           
             g_CliSocketArr[i--]= g_CliSocketArr[--g_iTotalConn];
           }
    }
     else
     {
      //We received a message from client
           szMessage[ret]= '\0';
      send(g_CliSocketArr,szMessage, strlen(szMessage), 0);
        }
      }
    }
  } 
  return0;
}


服务器的几个主要动作如下:

1.创建监听套接字,绑定,监听;

2.创建工作者线程;

3.创建一个套接字数组,用来存放当前所有活动的客户端套接字,每accept一个连接就更新一次数组;

4.接受客户端的连接。

这里有一点需要注意的,就是我没有重新定义FD_SETSIZE宏,所以服务器最多支持的并发连接数为64。而且,这里决不能无条件的accept,服务器应该根据当前的连接数来决定是否接受来自某个客户端的连接。一种比较好的实现方案就是采用WSAAccept函数,而且让WSAAccept回调自己实现的Condition Function。如下所示:

 

int CALLBACKConditionFunc(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOSlpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORDdwCallbackData)
{
if (当前连接数 < FD_SETSIZE)
  returnCF_ACCEPT;
else
  returnCF_REJECT;
}

 

工作者线程里面是一个死循环,一次循环完成的动作是:

1.将当前所有的客户端套接字加入到读集fdread中;

2.调用select函数;

3.查看某个套接字是否仍然处于读集中,如果是,则接收数据。如果接收的数据长度为0,或者发生WSAECONNRESET错误,则表示客户端套接字主动关闭,这时需要将服务器中对应的套接字所绑定的资源释放掉,然后调整我们的套接字数组(将数组中最后一个套接字挪到当前的位置上) 

除了需要有条件接受客户端的连接外,还需要在连接数为0的情形下做特殊处理,因为如果读集中没有任何套接字,select函数会立刻返回,这将导致工作者线程成为一个毫无停顿的死循环,CPU的占用率马上达到100% 

关系到套接字列表的操作都需要使用循环,在轮询的时候,需要遍历一次,再新的一轮开始时,将列表加入队列又需要遍历一次.也就是说,Select在工作一次时,需要至少遍历2次列表,这是它效率较低的原因之一.在大规模的网络连接方面,还是推荐使用IOCPEPOLL模型.但是Select模型可以使用在诸如对战类游戏上,比如类似星际这种,因为它小巧易于实现,而且对战类游戏的网络连接量并不大. 

对于Select模型想要突破Windows64个限制的话,可以采取分段轮询,一次轮询64.例如套接字列表为128,在第一次轮询时,将前64个放入队列中用Select进行状态查询,待本次操作全部结束后.将后64个再加入轮询队列中进行轮询处理.这样处理需要在非阻塞式下工作.以此类推,Select也能支持无限多个.

实践应用中模型一:TCP 接受连接和收发数据在不同线程选择模型Server用例;

  

 //初始化一个TCP server;
    if(InitServer(socketport,ListenSocket) != NS_OK)//封装了初始化一个socket,bind端口,listen监听连接。
    {
        displayinfo("Init Server fail!");
        return NS_FAIL;
    }
    //创建数据接收线程
    //所有连接进来的socket组成一个客户端列表,每一个socket都有自己的属性封装,如收发数据队列,socket描述符,缓冲大小等。
    if(Create_Thread(DataRecvSendProc,this,&ThreadID,&ThreadHandle) != NS_OK)
    {
        displayinfo("Create_Thread fail!");
        return NS_FAIL;
    }
    //接收连接请求,需做限制,不能无限接受连接,最大64个并发socket。
    AcceptConnect();



实践应用中模型二:

fd_set fdRead;
fd_set fdAllSocket;
FD_ZERO(&fdAllSocket);
FD_SET(sListen,&fdAllSocket);

struct timeval outtime ;  // 超时结构
outtime.tv_sec = g_TimeOut;  
outtime.tv_usec =0;

for (;;)
{
	FD_ZERO(&fdRead);
	for (int j=0;j<fdAllSocket.fd_count;j++)
	{
		FD_SET(fdAllSocket.fd_array[j],&fdRead);
	}
	int nRet = ::select(0,&fdRead,NULL,NULL,&outtime);
        if (0 == nRet)
	{
		continue;	//无可读套接字
	}
	else if(SOCKET_ERROR == nRet)
	{
		break;		//出错退出
	}
	for (int i=0; i<(int)fdRead.fd_count; i++)
	{
		if (!FD_ISSET(fdRead.fd_array[i],&fdRead))
		{
			continue;	//该套接字状态没有改变
		}
		//是监听套接字可读
		if (fdRead.fd_array[i] == sListen)	
		{
			if (fdAllSocket.fd_count > FD_SETSIZE)
			{
				continue;	//连接已达最大数,放弃
			}
			sockaddr_in  clientAddr;
			int  addrLen = sizeof(clientAddr);

			SOCKET newClientSock = ::accept(sListen,(sockaddr *)&clientAddr,&addrLen);
			if (INVALID_SOCKET == newClientSock)
			{
				sprintf(Info,"Warning: Failed accept() ! \n");
				g_LogFile.WriteLogFile(Info);
			}
			else
			{					
				FD_SET(newClientSock,&fdAllSocket); //新连接进入,并添加到套接字集

			}
			continue;	
		}

		//连接套接字可读
		char	recvBuff[SOCK_RECV_MAX] = "";
		int nRecv = ::recv(fdRead.fd_array[i],recvBuff,SOCK_RECV_MAX,0);
		if (nRecv > 0)  //套接字可读
		{
			m_ProMSG.DispatchMSG(recvBuff,nRecv,fdRead.fd_array[i]);	//消息处理下发处理
			//closesocket(fdRead.fd_array[i]);		   短连接,主动关闭
			//FD_CLR(fdRead.fd_array[i],&fdAllSocket);
						
		} 
		else    
		{	//套接字关闭、中断、或者重启, 移除套接字
			closesocket(fdRead.fd_array[i]);
			FD_CLR(fdRead.fd_array[i],&fdAllSocket);
		}
	}
总结:在同一个线程,用select对监听套接字,以及客户连接套接字的accept ,读监听同时进行监听。因为连接请求时,监听套接字也会有读状态变化。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值