绝大部分情况下, 从 TCP 接收数据都存在一个把 "TCP 流" 转成 "数据块" 的问题, 那么为什么 TCP 当初还要设计成 "流"

2018-07-08 10:19:14 +08:00
 c3824363

TCP 设计成 "流", 只是实现起来容易一些吧, 但用起来真不爽 实际上程序里用到的都是一块一块的内存, TCP 强行弄出一个"流"来. 这样在接收方必然要从 "流" 转换成 "块", 转换的方式有: 固定长度, 加包头指定长度, 用特殊的间隔标记.

固定长度: 简单无开销, 但是太死板不灵活, 适用场合很少. 加包头指定长度: 编程简单, 但会多一次读取开销. 用特殊的间隔标记: 比如 HTTP 这种, 就需要遍历全部内容

如果 TCP 原本就保留“块”信息, 则使用起来就会简单很多了。 比如这样定义

struct iovec {
   void  *iov_base;
   size_t iov_len;
};

/*
  返回发送出去的 struct iovec 的个数, 不要发送半个
  出错的情况的返回值和 write()/send() 一样
 */
int my_writev(int fd, struct iovec *vec, size_t n_vec);

/*
  返回接收到的 struct iovec 的个数, 不要接收半个
  出错的情况的返回值和 read()/recv() 一样
  接收的时候可以把 struct iovec *vec 预先分配,也可以不分配直接接收到 buf 里面同时把分块信息保存到 struct iovec *vec。
 */
int my_readv(int fd, struct iovec *vec, size_t n_vec, void *buf, size_t size_of_buf);

这样用来起来再很多场合就非常方便了。

6025 次点击
所在节点    程序员
53 条回复
shijingshijing
2018-07-08 10:25:49 +08:00
主要是考虑到通用性吧,在 Kernel 内部都以 Stream 方式来操作数据,这样 pipe,文件,网络都可以有一个很好的模型 cover 到,编程也简单,给 application 更大的自由度。
MeteorCat
2018-07-08 10:27:49 +08:00
TCP 的流只是说,数据到达顺序的正确性,但是其中受限于 MTU,缓冲区可能会把“ 123456789 ”一次性发动(假设 MTU>9 位),也可能超过最大传输分别发“ 123 ”,“ 456 ”,“ 789 ”这样的,为了抑制最大传输>MTU,就按你所说来转成“数据块”,从最开始说设计成流保证了传输数据正确性(流没有乱序包问题),而之所以应用层要又改成块则是为了适应 MTU
des
2018-07-08 10:29:12 +08:00
你可以选用 sctp

这种设计还是通用性和简单性吧
hjc4869
2018-07-08 10:29:24 +08:00
楼主是想要 SEQPACKET ?
SYP
2018-07-08 10:33:51 +08:00
流这个是为了帮助理解,实际在 TCP 层处理的数据也是一块一块的小块,你说的这种封装属于上层协议要处理的内容。
whileFalse
2018-07-08 10:36:09 +08:00
因为人家本来就是流。
xmadi
2018-07-08 10:41:21 +08:00
本来就是流 +1 我思考了下 更倾向这个说法 因为再往下到物理层电信号光信号的传输也是流
iwtbauh
2018-07-08 10:44:43 +08:00
TCP/IP 是在 Unix 上开发的
这其实就是 Unix 的终极设计思想:“一切都是文件”
“一切都是文件”意味着“一切都是数据流”
受到这种理念的影响,协议和编程接口有意设计成这种形式。而事实是,这种形式的接口( BSD socket )战胜了其他的接口,并成为标准。
再说一下你的想法,知道 TCP/IP 为什么要分层吗,因为分层降低了软件的整体复杂度,TCP/IP 协议栈的每一层只需要考虑自己这一层的任务,并给上层提供机制。你的需要只是在 TCP 上再加一层,如果你愿意,就写成一个库的形式(参考 OpenSSL )
yulon
2018-07-08 10:51:32 +08:00
因为可靠协议有序不丢包做成流是最好的,你要无序丢包直接用 UDP,你要无序不丢包直接用 UDP 做丢包验证。
teleme
2018-07-08 11:19:33 +08:00
数据块的处理成本高,在规模型生产环境下,主要是基于流进行运算处理。
zhujinliang
2018-07-08 11:34:47 +08:00
什么是流,给“块”加上 FIFO 缓冲就是流,为什么要加缓冲,可以想一下,通信线路不停地把数据送入 IO 设备,假设每毫秒到达一个字节
1. 如果没有缓冲,CPU 必须每毫秒至少检查一次设备寄存器,如果没来得及检查,要么设备丢弃后来的数据,要么后来的数据覆盖之前的数据,总之,因为没地方存放新来的数据,必须丢弃一个
2. 如果有一个 16KB 的缓冲,不考虑延迟的话,系统只需 8K 毫秒左右查询一次缓冲即可,如果缓冲有数据,就全部取出,批量处理,既减少了在查询操作上的开销,又降低了丢弃数据的概率
3. 还有一个方法是增加一个设备忙信号,IO 设备接收到一个数据后置为设备忙状态,直到 CPU 从设备中取走数据,这种方式可避免丢弃数据,但得到的结果是通信线路大量闲置,本可以 CPU 处理数据与线路传输同时进行,但因为没有缓冲,必须接收、处理轮流进行
c3824363
2018-07-08 11:44:33 +08:00
@hjc4869 是的就是要这个,bbr 对它有效么。 但是 windows 平台应该不支持吧

@des SEQPACKET 就是用 sctp 实现的吧

@shijingshijing @iwtbauh 看一些很早期的代码发现一个习惯就是要尽可能的节省内存, 感觉是这个原因导致的用"流"不用"块"

@MeteorCat @SYP @whileFalse @xmadi 我觉得只有模拟音频信号通信才算绝对的流,TCP 是因为把数据弄到一块丢失了分块信息才看起来变成了“流”

@teleme 是因为内存开销么
c3824363
2018-07-08 11:47:04 +08:00
@zhujinliang 还是不能解释既然有 UDP 也有 TCP,为什么不早早就弄一个有 UDP 优点的 TCP 呢。 而是近些年才有的 SEQPACKET
hjc4869
2018-07-08 12:05:57 +08:00
@c3824363 SEQPACKET 也需要 congestion control,但是目前 Linux 的 bbr 似乎只是针对 TCP。
SCTP 既实现了 stream 也实现了 seqpacket,Windows 上有第三方驱动可以用,也可以用 raw socket 在用户态实现 SCTP。但是各类 NAT 设备可能没有很好地支持 SCTP。
hjc4869
2018-07-08 12:12:58 +08:00
另外楼主所说的,“绝大部分情况下”实际上是不成立的,例如 HTTP 协议就没有这么做。
MeteorCat
2018-07-08 12:26:34 +08:00
@c3824363 模拟音频信号通信和网络信号不能混为一谈,如果按照这样的类比那完全没有说下去的必要
jtsai
2018-07-08 12:30:28 +08:00
流节省内存,流就是缓存的块。
liuminghao233
2018-07-08 12:31:47 +08:00
现在的基于 proactor 模型的网络库
你可以在包头加一个长度
跟 udp 比也就多一次 callback
还不够简单吗
c3824363
2018-07-08 13:07:14 +08:00
@hjc4869 SEQPACKET 到底能不能过 大部分 NAT 呢。
HTTP 1.1 头部的内容肯定要遍历吧,可能是边读边遍历。
owenliang
2018-07-08 13:33:51 +08:00
xml 流了解一下,并不是所有协议都是 package。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/468985

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX