7.3.4 流协议
由于大多面向连接的协议同时也是流式传输协议,所以,在此提一下流式协议。对于流套接字上收发数据所用的函数,需要明白的是:它们不能保证对请求的数据量进行读取或写入。比如说,一个2 0 4 8字节的字符缓冲,准备用s e n d函数来发送它。采用的代码是:

char sendBuff[2048];
int  nBytes = 2048;

int ret= 0;
ret = send(s,sendBuff,nBytes,0);

对s e n d函数而言,可能会返回已发出的少于2 0 4 8的字节。r e t变量将设为发送的字节数,这是因为对每个收发数据的套接字来说,系统都为它们分配了相当充足的缓冲区空间。在发送数据时,内部缓冲区会将数据一直保留到应该将它发到线上为止。几种常见的情况都可导致这一情形的发生。比方说,大量数据的传输可以令缓冲区快速填满。同时,对T C P / I P来说,还有一个窗口大小的问题。接收端会对窗口大小进行调节,以指出它可以接收多少数据。如果有大量数据涌入接收端,接收端就会将窗口大小设为0,为待发数据做好准备。对发送端来
说,这样会强令它在收到一个新的大于0的窗口大小之前,不得再发数据。在使用s e n d调用时,缓冲区可能只能容纳1 0 2 4个字节,这时,便有必要再提取剩下的1 0 2 4个字节。要保证将所有的字节发出去,可采用下面的代码。

char sendbuf[2048];
int nBytes= 2048,nLeft,idx;

nLeft = nBytes;
idx = 0;

while(nLeft>0)
{
 ret = send(s,&sendbuf[idx],nLeft,0);
 if(ret == SOCKET_ERROR)
 {
  //ERROR
 }
 nLeft -= ret;
 idx +=ret;
 }
 对在流套接字上接收数据来说,前一段代码有用,但意义不大。因为流套接字是一个不间断的数据流,应用程序在读取它时,和它应该读多少数据之间通常没有关系。如果应用需要依赖于流协议的离散数据,你就有别的事要做。如果所有消息长度都一样,则比较简单,也就是说, 5 1 2个字节的消息看起来就像下面这样:

char recvbuf[2048];
int ret,nLeft,idx;
nLeft = 512;
idx = 0;

while(nLeft>0)
{
 ret = recv(s,&recvbuf[idx],nLeft,0);
 if(ret = SOCKET_ERROR)
 {
  //ERROR
 }
 idx += ret;
 nLeft -= ret;
}

消息长度不同,处理也可能不同。因此,有必要利用你自己的协议来通知接收端,即将
到来的消息长度是多少。比方说,写入接收端的前4个字节一直是整数,表示即将到来的消息
有多少字节。然后,接收端先查看前4个字节的方式,把它们转换成一个整数,然后判断构成
消息的字节数是多少,通过这种方式,便开始逐次读取。

分散集合I/O
分散集合支持是Berkeley Socket中首次随R e c v和Wr i t e v这两个函数一起出现的概念。
它随W S A R e c v、W S A R e c v F r o m、W S A S e n d和W S A S e n d To这几个Winsock 2函数一起使用。
对收发格式特别的数据这一类的应用来说,它是非常有用的。比方说,客户机发到服务器的消息可能一直都是这样构成的,一个指定某种操作的固定的3 2字节的头,一个6 4字节的数据块和一个1 6字节的尾。这时,就可用一个由三个W S A B U F结构组成的数组调用W S A S e n d,这三个结构分别对应的三种消息类型。在接收端,则用3个W S A B U F结构来调用W S A R e c v,各个结构包含的数据缓冲分别是3 2字节、6 4字节和1 6字节。
在使用基于流的套接字时,分散集合I / O模式只是把W S A B U F结构中提供的数据缓冲当作一个连续性的缓冲。另外,接收调用可能在所有缓冲填满之前就返回。在基于消息的套接字上,每次对接收操作的调用都会收到一条消息,其长度由所提供的缓冲决定。如果缓冲不够,调用就会失败,并出现W S A E M S G S I Z E错误,为了适应可用的缓冲数据就会被截断。当然,如果用支持部分消息的协议,就可用M S G _ PA RT I A L标志来避免数据的丢失。

7.3.5 中断连接
一旦完成任务,就必须关掉连接,释放关联到那个套接字句柄的所有资源。要真正地释放与一个开着的套接字句柄关联的资源,执行c l o s e s o c k e t调用即可。但要明白这一点,
c l o s e s o c k e t可能会带来负面影响(和如何调用它有关),即可能会导致数据的丢失。鉴于此,应该在调用c l o s e s o c k e t函数之前,利用s h u t d o w n函数从容中断连接。接下来,我们来谈谈这两个A P I函数。
1. shutdown
为了保证通信方能够收到应用发出的所有数据,对一个编得好的应用来说,应该通知接收端“不再发送数据”。同样,通信方也应该如此。这就是所谓的“从容关闭”方法,并由s h u t d o w n函数来执行。s h u t d o w n的定义如下:

int shutdown(
       SOCKET s,
       int how
      );
h o w参数可以是下面的任何一个值: S D _ R E C E I V E、S D _ S E N D或S D _ B O T H。如果是S D _ R E C E I V E,就表示不允许再调用接收函数。这对底部的协议层没有影响。另外,对T C P套接字来说,不管数据在等候接收,还是数据接连到达,都要重设连接。尽管如此, U D P套接字上,仍然接受并排列接入的数据。如果选择S E _ S E N D,表示不允许再调用发送函数。对T C P套接字来说,这样会在所有数据发出,并得到接收端确认之后,生成一个F I N包。最后,
如果指定S D _ B O T H,则表示取消连接两端的收发操作。

2. closesocket
c l o s e s o c k e t函数用于关闭套接字,它的定义如下:

int closesocket(SOCKET s);

c l o s e s o c k e t的调用会释放套接字描述符,再利用套接字执行调用就会失败,并出现W S A E N O T S O C K错误。如果没有对该套接字的其他引用,所有与其描述符关联的资源都会被
释放。其中包括丢弃所有等侯处理的数据。
对这个进程中任何一个线程来说,它们执行的待决异步调用都在未投递任何通知消息的情况下被删除。待决的重叠操作也被删除。与该重叠操作关联的任何事件,完成例程或完成端口能执行,但最后会失败,出现W S A _ O P E R AT I O N _ A B O RT E D错误。异步和非封锁I / O模
式将在第8章深入讲解。另外,还有一点会对c l o s e s o c k e t的行为产生影响:套接字选项S O _ L I N G E R是否已经设置。要得知其中缘由,参考第9章中对S O _ L I N G E R选项的描述。

Posted on 2006-09-11 17:02 艾凡赫 阅读(472) 评论(0)  编辑 收藏 引用 所属分类: 网络编程

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