一、选择模型,即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次列表,这是它效率较低的原因之一.在大规模的网络连接方面,还是推荐使用IOCP或EPOLL模型.但是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 ,读监听同时进行监听。因为连接请求时,监听套接字也会有读状态变化。