关于TCP中send发送数据、select的一些困惑及实验

来源:百度文库 编辑:神马文学网 时间:2024/04/24 12:02:31
不知道是不是应该发在这里,看了一下这里好像主要是交流网络配置的?但其它更想不出什么地方了。内容比较多,请大家见谅。

在阻塞式的socket中,recv/read调用肯定不一定返时刚好读满缓冲区(即使对端没有关闭还在发送),这个比较容易理解,也很容易测试出来。但对于send的返回值,许多资料中讲得都很含糊,在MSDN中说是“非阻塞的socket有可能发送成功而返回的值小于要发送的长度“,即只发送了一部分出去,感觉言下之义就是阻塞式的只要发送成功就肯定是指定的发送长度,和文件写一样。Linux/FreeBSD中man手册都没有明确的说明,只说成功时返回发送的数据字节数,失败返回-1。
以前刚学网络时看的是《UNIX网络编程》的第二版,现在俺这本书找不到了,于是找了一本第三版的电子书看,但浏览了一下没看到什么说明。这个发送实际上以前就比较困惑,由于自己不做网络开发也没有专门测试过,问过一些专门做网络的好像也不是很清楚;而接收如果按每次能收满这个在工作中是见到过问题的。
《UNIX网络编程》第三版的代码:
  1. [color=Navy]
  2. ssize_t                                                /* Write "n" bytes to a descriptor. */
  3. writen(int fd, const void *vptr, size_t n)
  4. {
  5.         size_t                nleft;
  6.         ssize_t                nwritten;
  7.         const char        *ptr;

  8.         ptr = vptr;
  9.         nleft = n;
  10.         while (nleft > 0) {
  11.                 if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
  12.                         if (nwritten < 0 && errno == EINTR)
  13.                                 nwritten = 0;                /* and call write() again */
  14.                         else
  15.                                 return(-1);                        /* error */
  16.                 }

  17.                 nleft -= nwritten;
  18.                 ptr   += nwritten;
  19.         }
  20.         return(n);
  21. }
  22. /* end writen */

  23. [/color]
复制代码

vsFTP中的代码:
  1. int
  2. vsf_sysutil_write_loop(const int fd, const void* p_buf, unsigned int size)
  3. {
  4.   int retval;
  5.   int num_written = 0;
  6.   if (size > INT_MAX)
  7.   {
  8.     die("size too big in vsf_sysutil_write_loop");
  9.   }
  10.   while (1)
  11.   {
  12.     retval = vsf_sysutil_write(fd, (const char*)p_buf + num_written, size);
  13.     if (retval < 0)
  14.     {
  15.       /* Error */
  16.       return retval;
  17.     }
  18.     else if (retval == 0)
  19.     {
  20.       /* Written all we're going to write.. */
  21.       return num_written;
  22.     }
  23.     if ((unsigned int) retval > size)
  24.     {
  25.       die("retval too big in vsf_sysutil_read_loop");
  26.     }
  27.     num_written += retval;
  28.     size -= (unsigned int) retval;
  29.     if (size == 0)
  30.     {
  31.       /* Hit the write target, cool. */
  32.       return num_written;
  33.     }
  34.   }
  35. }
复制代码

后面一个实际上是写socket的。从这两种看感觉都是认为写操作有可能不能一次写完,而且这里显然不是处理非阻塞式的,因为没有处理非阻塞式特别的返回码。


然后我开始做实验。结果发现不管缓冲区多大,对端阻塞的话本端要么阻塞,要么全部发送。不管多大缓冲区发送,都无法构造出有可能发磅成功但只发送部分的情况。
如果是这样的话也算OK吧,因为Linux的man手册中讲到send/recv的第四个参数是WAITALL只提到读数据。但这样就又使我对于 select疑惑起来。因为select对读很简单,保证读一次不会阻塞是可以的。但对于这个测试结果,select可写的话后面的写操作有可能还是阻塞 掉,因为select时并不知道后续的发送要发多少数据。UNPv3给出的select返回socket可写的条件(其它三个条件不相关):

The number of bytes of available space in the socket send buffer is greater than or equal to the current size of the low-water mark for the socket send buffer and either: (i) the socket is connected, or (ii) the socket does not require a connection (e.g., UDP). This means that if we set the socket to nonblocking (Chapter 16), a write operation will not block and will return a positive value (e.g., the number of bytes accepted by the transport layer). We can set this low-water mark using the SO_SNDLOWAT socket option. This low-water mark normally defaults to 2048 for TCP and UDP sockets.

最开始我以WinXP 做Server,连接之后等待键盘输入才读数据,这样Linux下的客户端最终总会阻塞,而在Server读几次之后客户端会再写一些数据。最后发24K 包发送时确实会出来select返回可写,但实际的send阻塞的情况,不过好像有一些随机。另外此时发现一个奇怪的现象,因为Windows下SOL_RCVBUF缺省为8K,而Linux下SOL_SNDBUF缺省为16K,但实际上发了许多24K包才阻塞,实在是想不明白,所以抓包看了一下,发现确实发给Windows的数据包远超过8K之后它才发Window Full,不知道除了TCP的接收缓冲区Windows还有哪也缓存数据了。
后来在FreeBSD下面编译客户端也测试了一下。在FreeBSD下发送缓冲区缺省是32K,但肯定地发完第5包之后select再交返回1,然后就阻 塞了。Linux下有些随机,往往是发完第6包阻塞在select上,后面随着Server读一些数据有可能select返回1但却发送阻塞,但有时也是 发完第5包之后select返回1后发送 阻塞。

另外比较奇怪的是在Linux下SOL_SNDLOWAT值为1,而FreeBSD是前面UNP中提到的2K,所以按道理Linux应该更容易出现select返回中写而发送阻塞,但测试结果FreeBSD更固定地出现。
环境是Fedora 7.0和FreeBSD 7.0。

由于是测试代码,有些乱。Client代码如果要在FreeBSD下编译需要在
#include
#include
后面再包含一些文件:
#include

tcp_send_test_srv.rar (2.43 KB)

下载次数:32

2008-05-02 00:04

Server测试代码

tcp_send_test.rar (2.4 KB)