第8章Winsock I/O方法
本章重点是如何在Wi n d o w s套接字应用程序中对I / O(输入/输出)操作进行管理。
Wi n s o c k分别提供了“套接字模式”和“套接字I / O模型”,可对一个套接字上的I / O行为加以控制。其中,套接字模式用于决定在随一个套接字调用时,那些Wi n s o c k函数的行为。而另一方面,套接字模型描述了一个应用程序如何对套接字上进行的I / O进行管理及处理。要注意的是,“套接字I / O模型”与“套接字模式”是无关的。套接字模型的出现,正是为了解决套接字模式存在的某些限制。

Wi n s o c k提供了两种套接字模式:锁定和非锁定。本章第一部分将详细介绍这两种模式,并阐释一个应用程序如何通过它们管理I / O。如大家在本章的后面部分所见,Wi n s o c k提供了一些有趣的I / O模型,有助于应用程序通过一种“异步”方式,一次对一个或多个套接字上进行的通信加以管理。这些模型包括s e l e c t(选择)、W S A A s y n c S e l e c t(异步选择)、W S A E v e n t S e l e c t
(事件选择)、Overlapped I/O(重叠式I / O)以及Completion port(完成端口)等等。到本章结束时,我们打算对各种套接字模式以及I / O模型的优缺点进行总结。同时,帮助大家判断到底哪一种最适合自己应用程序的要求。
所有Wi n d o w s平台都支持套接字以锁定或非锁定方式工作。然而,并非每种平台都支持每一种I / O模型。如表8 - 1所示,在当前版本的Windows CE 中,仅提供了一个I / O模型。
Windows 98和Windows 95(取决于安装的是Winsock 1还是Winsock 2)则支持大多数I / O模型,唯一的例外便是I / O完成端口。而到了Windows NT和最新发布的Windows 2000中,每种I / O模型都是支持的。

8.1 套接字模式
就像我们前面提到的那样, Wi n d o w s套接字在两种模式下执行I / O操作:锁定和非锁定。
在锁定模式下,在I / O操作完成前,执行操作的Wi n s o c k函数(比如s e n d和r e c v)会一直等候下去,不会立即返回程序(将控制权交还给程序)。而在非锁定模式下, Wi n s o c k函数无论如何都会立即返回。在Windows CE和Windows 95(安装Winsock 1)平台上运行的应用程序仅支持极少的I / O模型,所以我们必须采取一些适当的步骤,让锁定和非锁定套接字能够满足各种场合的要求

8.1.1 锁定模式
对于处在锁定模式的套接字,我们必须多加留意,因为在一个锁定套接字上调用任何一个Winsock API函数,都会产生相同的后果—耗费或长或短的时间“等待”。大多数Wi n s o c k应用都是遵照一种“生产者-消费者”模型来编制的。在这种模型中,应用程序需要读取(或写入)指定数量的字节,然后以它为基础执行一些计算。程序清单8 - 1展示的代码片断便是一个典型的例子。

// 程序清单8-1 简单的锁定模式示例

SOCKET s;
char  buff[ 256 ];
int  done  =   0 ;

.

while ( ! done)
{
    nBytes 
=  recv(s,buff, 65 );
    
if (nBytes  ==  SOCKET_ERROR)
    
{
        printf(
" recv failed with error %d " ,WSAGetLastError());
        
return  ;    
    }

    
    
}

.


这段代码的问题在于,假如没有数据处于“待决”状态,那么r e c v函数可能永远都无法返回。这是由于从语句可以看出:只有从系统的输入缓冲区中读回点什么东西,才允许返回!有些程序员可能会在r e c v中使用M S G _ P E E K标志,或者调用i o c t l s o c k e( t 设置F I O N R E A D选项),
在系统的缓冲区中,事先“偷看”是否存在足够的字节数量。然而,在不实际读入数据的前提下,仅仅“偷看”数据(如实际读入数据,便会将其从系统缓冲区中将其删除),可不是一件光彩的事情。我们认为,这是一种非常不好的编程习惯,应尽全力避免。在“偷看”的时候,对系统造成的开销是极大的,因为仅仅为了检查有多少个字节可用,便发出一个或者更多的系统调用。以后,理所当然地,还需要牵涉到进行实际r e c v调用,将数据从系统缓冲区内删除的开销。那么,如何避免这一情况呢?在此,我们的目标是防止由于数据的缺乏(这
可能是网络出了故障,也可能是客户机出了问题),造成应用程序完全陷于“凝固”状态,同时不必连续性地检视系统网络缓冲!为达此目的,一个办法是将应用程序划分为一个读线程,以及一个计算线程。两个线程都共享同一个数据缓冲区。对这个缓冲区的访问需要受到一定的限制,这是用一个同步对象来实现的,比如一个事件或者M u t e x(互斥体)。“读线程”的职责是从网络连续地读入数据,并将其置入共享缓冲区内。读线程将计算线程开始工作至少需
要的数据量拿到手后,便会触发一个事件,通知计算线程:你老兄可以开始干活了!随后,计算线程从缓冲区取走(删除)一个数据块,然后进行要求的计算。

在程序清单8 - 2中,我们分别提供了两个函数,采取的便是上述办法。在两个函数中,一个负责读取网络数据( R e a d T h r e a d),另一个则负责对数据执行计算( P r o c e s s T h r e a d)。

// 程序清单8-2 多线程的锁定套接字示例

CRITICAL_SECTION    data;
HANDLE    hEvent;
TCHAR     buf[MAX_BUFFER_SIZE];
int          nBytes;

.

// read thread

void  ReadThread( void )
{
    
int  nTotal  =   0 ,
            nRead 
=   0 ,
            nLeft 
=   0 ,
            nBytes 
=   0 ;
            
            
while ( ! done)
            
{
                nTotal 
=   0 ;
                nLeft 
=  NUM_BYTES_REQUIRED;
                
while (nTotal  !=  NUM_BYTES_REQUIRED)
                
{
                    EnterCriticalSection(
& data);
                    nRead 
=  recv(sock, & (buff[MAX_BUFFERS_SIZE - nBytes],nLeft);
                    
if (nRead  ==   - 1 )
                    
{
                        printf(
" error " );
                        ExitThread();
                    }

                    nTotal 
+=  nRead;
                    nLeft 
-= nRead;
                    
                    nBytes 
+=  nRead;
                    LeaveCriticalSection(
& data);
                    
                }

                SetEvent(hEvent);
            }

}


////// compution thread

void     ProcessThread( void )
{
    WatiForSingleObject(hEvent);
    
    EnterCriticalSection(
& data);
    ..
    nBytes 
-=  NUM_BYTES_REQUIRED;
    
    LeaveCriticalSection(
& data);
}



对锁定套接字来说,它的一个缺点在于:应用程序很难同时通过多个建好连接的套接字通信。使用前述的办法,我们可对应用程序进行修改,令其为连好的每个套接字都分配一个读线程,以及一个数据处理线程。尽管这仍然会增大一些开销,但的确是一种可行的方案。唯一的缺点便是扩展性极差,以后想同时处理大量套接字时,恐怕难以下手。
Posted on 2006-09-11 17:48 艾凡赫 阅读(624) 评论(0)  编辑 收藏 引用 所属分类: 网络编程

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理