您的位置:首页 > 理论基础 > 计算机网络

windows Socket编程之select网络模型

2016-08-08 23:08 225 查看
在此之前呢,介绍了TCP/UDP的服务端的实现。但是,它们有很大的缺点,比如说,效率很低,开销太大等。因此,接下来我们先介绍select网络模型。

我们在TCP的服务端里边,接收一个客户端的时候,我们调用accept函数,这个函数会返回一个客户端的socket,我们在主线程里边不停的接收客户的连接,每当有客户连接时,我们就会在开一个线程,用于对客户的服务。因此,如果有N个的客户进行连接的话,那么线程数量就会有N+1个(N个服务线程+主线程),若N比较大,则线程就会非常多,以至于将整个电脑都给拖垮掉。而我们的select模型呢,就是为了解决这个问题而设计的。接下来看下它是如何实现的。

首先,TCP的服务端一样,它需要初始化环境,然后执行绑定,监听等操作。但是之后,我们会直接开一个线程来对客户进行服务,然后才是我们原来的一个循环,来接待客户的连接。接下来看一个结构体,其声明如下:

typedef struct fd_set {
u_int    fd_count;                 // 有多少个socket
SOCKET   fd_array[FD_SETSIZE];     // 客户端socket数组,FD_SETSIZE为64
} fd_set;
当我们调用accept来获取客户端的连接之后,会调用FD_SET这个宏,它实际上是会将我们的客户那个socket保存到fd_array这个数组里边去,因为这个数组最大为64个,所以最多只能有64个客户端进行连接。

我们把服务客户的那个线程叫做工作者线程,在里边我们会调用一个函数叫做select,其声明如下:

int select(
int nfds,                            //已经忽略了
fd_set FAR *readfds,                 //可读fd_set的地址
fd_set FAR *writefds,                //可写fd_set地址
fd_set FAR *exceptfds,               //异常错误fd_set地址
const struct timeval FAR *timeout    //timeval结构
);

这个函数会检查fd_array这个数组里边所有的socket是否有信号到来,如果有就成功返回,否则会阻塞在这里,不过我们在最后一个参数那里,传一个等待时间。

调用完select之后,我们可以在调用FD_ISSET这个宏来判断是fd_array这个数组里边的那个socket有信号了。之后我们就可以进行数据收发了。

以下是select的示例代码:

#include <winsock2.h>
#include <stdio.h>
#define PORT 6000
#pragma comment (lib, "Ws2_32.lib")
fd_set  g_fdClientSock;
int clientNum = 0;

BOOL WinSockInit()
{
WSADATA data = {0};
if(WSAStartup(MAKEWORD(2, 2), &data))
return FALSE;
if ( LOBYTE(data.wVersion) !=2 || HIBYTE(data.wVersion) != 2 ){
WSACleanup();
return FALSE;
}
return TRUE;
}
// 工作者线程
DWORD WINAPI WorkThreadProc(LPARAM lparam)
{
fd_set fdRead;
FD_ZERO( &fdRead );
int nRet = 0;
char* recvBuffer =(char*)malloc( sizeof(char) * 1024 );
if ( recvBuffer == NULL )
return -1;
memset( recvBuffer, 0, sizeof(char) * 1024 );
while ( true )
{
fdRead = g_fdClientSock;
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 10;
//检查fd_arrray数组里边是否有信号到来
nRet = select( 0, &fdRead, NULL, NULL, &tv );
if ( nRet != SOCKET_ERROR )
{

for ( int i = 0; i < g_fdClientSock.fd_count; i++ )
{
// 遍历出来哪些SOCKET有信号
if ( FD_ISSET(g_fdClientSock.fd_array[i],&fdRead)  )
{
// 下面是数据的收发
memset( recvBuffer, 0, sizeof(char) * 1024 );
nRet = recv( g_fdClientSock.fd_array[i], recvBuffer, 1024, 0);
if ( nRet == SOCKET_ERROR )
{
closesocket( g_fdClientSock.fd_array[i]);
clientNum--;
FD_CLR( g_fdClientSock.fd_array[i], &g_fdClientSock );
}
else if ( nRet == 0 )
{
closesocket( g_fdClientSock.fd_array[i]);
clientNum--;
FD_CLR( g_fdClientSock.fd_array[i], &g_fdClientSock );
}
else
{
printf("Recv msg:%s\n",recvBuffer);
send(g_fdClientSock.fd_array[i], recvBuffer, strlen(recvBuffer), 0);
}
}
}
}
}

if ( recvBuffer != NULL )
free( recvBuffer );
return 0;
}
int main()
{
//初始化环境
WinSockInit();

SOCKET listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = htonl(INADDR_ANY);
server.sin_port = htons(PORT);
//绑定
int ret = bind(listenSock, (sockaddr*)&server, sizeof(server));
//监听
ret = listen(listenSock, 4);

sockaddr_in clientAddr;
int nameLen = sizeof( clientAddr );
// 先把工作者线程创建起来
CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)WorkThreadProc, NULL, NULL, NULL);

while( clientNum < FD_SETSIZE )//FD_SETSIZE==64
{
// 当有一个客户端进行连接时,主线程的accept会进行返回
SOCKET clientSock = accept( listenSock, (sockaddr*)&clientAddr, &nameLen );

FD_SET(clientSock, &g_fdClientSock);

clientNum++;
}
closesocket(listenSock);
WSACleanup();
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息