免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 5802 | 回复: 9
打印 上一主题 下一主题

关于TCP中send发送数据、select的一些困惑及实验 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-05-02 00:04 |只看该作者 |倒序浏览
不知道是不是应该发在这里,看了一下这里好像主要是交流网络配置的?但其它更想不出什么地方了。内容比较多,请大家见谅。

在阻塞式的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 <sys/types.h>
#include <sys/socket.h>
后面再包含一些文件:
#include <netinet/in.h>

tcp_send_test_srv.rar

2.43 KB, 下载次数: 77

Server测试代码

tcp_send_test.rar

2.4 KB, 下载次数: 79

Client测试代码

论坛徽章:
0
2 [报告]
发表于 2008-05-04 11:25 |只看该作者
没人想讨论,还是我贴错地方了?

论坛徽章:
5
IT运维版块每日发帖之星
日期:2015-08-06 06:20:00IT运维版块每日发帖之星
日期:2015-08-10 06:20:00IT运维版块每日发帖之星
日期:2015-08-23 06:20:00IT运维版块每日发帖之星
日期:2015-08-24 06:20:00IT运维版块每日发帖之星
日期:2015-11-12 06:20:00
3 [报告]
发表于 2008-05-04 11:35 |只看该作者
正如你所说这个是讨论网络设计于配置的。

这个还是放到C语言板块比较好。

论坛徽章:
0
4 [报告]
发表于 2008-05-05 21:43 |只看该作者
read调用肯定不一定返

论坛徽章:
0
5 [报告]
发表于 2008-05-05 21:46 |只看该作者
调用肯定不一定返

论坛徽章:
0
6 [报告]
发表于 2008-05-05 21:47 |只看该作者
定返

论坛徽章:
0
7 [报告]
发表于 2008-05-07 17:06 |只看该作者
不错的帖子,慢慢研究,,,,转移到c/c++ 板块 就可以了,

论坛徽章:
0
8 [报告]
发表于 2008-05-11 23:19 |只看该作者
原帖由 qingfengjianke 于 2008-5-7 17:06 发表
不错的帖子,慢慢研究,,,,转移到c/c++ 板块 就可以了,


怎么转移啊?

论坛徽章:
0
9 [报告]
发表于 2008-05-11 23:20 |只看该作者
原帖由 我是西瓜 于 2008-5-5 21:43 发表
read调用肯定不一定返


read调用在select认为有数据可读时能保证读一次不会阻塞,而写的话实际上是不一定的,和SOL_SNDLOWAT及发送的数据大小有关。这个测试的结果是这样的,但各种资料上都含糊其辞。

论坛徽章:
0
10 [报告]
发表于 2008-05-12 19:31 |只看该作者
学习
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP