winsock IO模型 select模型

两种I/O模式

* 阻塞模式:执行I/O操作完成前会一直进行等待,不会将控制权交给程序。套接字 默认为阻塞模式。可以通过多线程技术进行处理。
* 非阻塞模式:执行I/O操作时,Winsock函数会返回并交出控制权。这种模式使用 起来比较复杂,因为函数在没有运行完成就进行返回,会不断地返回 WSAEWOULDBLOCK错误。但功能强大。

比较容易想到的一种服务器模型就是采用一个主线程,负责监听客户端的连接请求,当接收到某个客户端的连接请求后,创建一个专门用于和该客户端通信的 套接字和一个辅助线程。以后该客户端和服务器的交互都在这个辅助线程内完成。这种方法比较直观,程序非常简单而且可移植性好,但是不能利用平台相关的特 性。例如,如果连接数增多的时候(成千上万的连接),那么线程数成倍增长,操作系统忙于频繁的线程间切换,而且大部分线程在其生命周期内都是处于非活动状 态的,这大大浪费了系统的资源。所以,如果你已经知道你的代码只会运行在Windows平台上,建议采用Winsock I/O模型。

select 五种IO模型之一

select模型:
  通过调用select函数可以确定一个或多个套接字的状态,判断套接字上是否有数据,或 者能否向一个套接字写入数据。

  int  select( int nfds, fd_set FAR * readfds, fd_set FAR * writefds,       fd_set FAR *exceptfds, const struct timeval FAR * timeout );  

Select模型是最常见的I/O模型。

使用

int select( int nfds , fd_set FAR* readfds , fd_set FAR* writefds,fd_set FAR* exceptfds,const struct timeval FAR * timeout ) ;

函数来检查你要调用的Socket套接字是否已经有了需要处理的数据。

select包含三个Socket队列,分别代表: readfds ,检查可读性,writefds,检查可写性,exceptfds,例外数据。 timeout是select函数的返回时间。

例如,我们想要检查一个套接字是否有数据需要接收,我们可以把套接字句柄加入可读性检查队列中,然后调用select,如果,该套接字没有数据需要接收, select函数会把该套接字从可读性检查队列中删除掉,所以我们只要检查该套接字句柄是否还存在于可读性队列中,就可以知道到底有没有数据需要接收了。

WinSock提供了一些宏用来操作套接字队列fd_set。

FD_CLR( s,*set) 从队列set删除句柄s。
FD_ISSET( s, *set) 检查句柄s是否存在与队列set中。
FD_SET( s,*set )把句柄s添加到队列set中。
FD_ZERO( *set ) 把set队列初始化成空队列。
◆先来看看涉及到的结构的定义: a、 fd_set结构:

#define FD_SETSIZE 64
typedef struct fd_set
{
u_int  fd_count;     /* how many are SET? */
SOCKET  fd_array[FD_SETSIZE];    /* an array of SOCKETs */
} fd_set;      

fd_count为已设定socket的数量

fd_array为socket列表,FD_SETSIZE为最大socket数量,建议不小于64。这是微软建 议的。(是否是不应该大于64)

B、timeval结构:

struct timeval
{
  long tv_sec;    /* seconds */
  long tv_usec;  /* and microseconds */
};

tv_sec为时间的秒值。 tv_usec为时间的毫秒值。 这个结构主要是设置select()函数的等待值,如果将该结构设置为(0,0),则select()函数 会立即返回。
◆再来看看select函数各参数的作用:

  1. nfds:没有任何用处,主要用来进行系统兼容用,一般设置为0。

  2. readfds:等待可读性检查的套接字组。

    1. writefds;等待可写性检查的套接字组。
  3. exceptfds:等待错误检查的套接字组。

  4. timeout:超时时间。

  5. 函数失败的返回值:调用失败返回SOCKET_ERROR,超时返回0。

readfds、writefds、exceptfds三个变量至少有一个不为空,同时这个不为空的套接字组 种至少有一个socket,道理很简单,否则要select干什么呢。 举例:测试一个套接字是否可读:

fd_set fdread;

FD_SET(s,&fdread)//加入套接字

if(FD_ISSET(s,&fread)   //是否存在fread中

如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的。Windows操作系统提供了选择(Select)、异步选择 (WSAAsyncSelect)、事件选择(WSAEventSelect)、重叠I/O(Overlapped I/O)和完成端口(Completion Port)共五种I/O模型。每一种模型均适用于一种特定的应用场景。程序员应该对自己的应用需求非常明确,而且综合考虑到程序的扩展性和可移植性等因 素,作出自己的选择。

(节选自《Windows网络编程》第八章) 下面的这段程序就是利用选择模型实现的Echo服务器的代码:

#include <winsock.h> 
#include <stdio.h>
#define PORT       5150 
#define MSGSIZE    1024
#pragma comment(lib, "ws2_32.lib")
int    g_iTotalConn = 0;
SOCKET g_CliSocketArr[FD_SETSIZE];
DWORD WINAPI WorkerThread(LPVOID lpParameter);
int main()
{
    WSADATA     wsaData; 
    SOCKET      sListen, sClient; 
    SOCKADDR_IN local, 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 
    HANDLE hHandle = CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
    CloseHandle(hHandle);
    while (TRUE) 
    {    
        // Accept a connection   
        sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);   
        printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
        // Add socket to fdTotal   
        g_CliSocketArr[g_iTotalConn++] = sClient; 
    }
    return 0; 
}

DWORD WINAPI WorkerThread(LPVOID lpParam) 
{
    int            i; 
    fd_set         fdread; 
    int            ret; 
    struct timeval tv = {1, 0}; 
    char           szMessage[MSGSIZE];
    while (TRUE) 
    {   
        FD_ZERO(&fdread);    
        for (i = 0; i < g_iTotalConn; i++)   
        {     
            FD_SET(g_CliSocketArr[i], &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[i], &fdread))     
            {     
                // A read event happened on pfdTotal->fd_array[i]    
                ret = recv(g_CliSocketArr[i], szMessage, MSGSIZE, 0);     
                if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))    
                {    
                    // Client socket closed        
                    printf("Client socket %d closed.\n", g_CliSocketArr[i]);    
                    closesocket(g_CliSocketArr[i]);      
                    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[i], szMessage, strlen(szMessage), 0);     
                }     
            }   
        } 
    }
    return 0; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值