
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); 这样用来起来再很多场合就非常方便了。
1 shijingshijing Jul 8, 2018 主要是考虑到通用性吧,在 Kernel 内部都以 Stream 方式来操作数据,这样 pipe,文件,网络都可以有一个很好的模型 cover 到,编程也简单,给 application 更大的自由度。 |
2 MeteorCat Jul 8, 2018 via Android TCP 的流只是说,数据到达顺序的正确性,但是其中受限于 MTU,缓冲区可能会把“ 123456789 ”一次性发动(假设 MTU>9 位),也可能超过最大传输分别发“ 123 ”,“ 456 ”,“ 789 ”这样的,为了抑制最大传输>MTU,就按你所说来转成“数据块”,从最开始说设计成流保证了传输数据正确性(流没有乱序包问题),而之所以应用层要又改成块则是为了适应 MTU |
3 des Jul 8, 2018 via Android 你可以选用 sctp 这种设计还是通用性和简单性吧 |
4 hjc4869 Jul 8, 2018 via iPhone 楼主是想要 SEQPACKET ? |
5 SYP Jul 8, 2018 流这个是为了帮助理解,实际在 TCP 层处理的数据也是一块一块的小块,你说的这种封装属于上层协议要处理的内容。 |
6 whileFalse Jul 8, 2018 因为人家本来就是流。 |
7 xmadi Jul 8, 2018 via iPhone 本来就是流 +1 我思考了下 更倾向这个说法 因为再往下到物理层电信号光信号的传输也是流 |
8 iwtbauh Jul 8, 2018 via Android TCP/IP 是在 Unix 上开发的 这其实就是 Unix 的终极设计思想:“一切都是文件” “一切都是文件”意味着“一切都是数据流” 受到这种理念的影响,协议和编程接口有意设计成这种形式。而事实是,这种形式的接口( BSD socket )战胜了其他的接口,并成为标准。 再说一下你的想法,知道 TCP/IP 为什么要分层吗,因为分层降低了软件的整体复杂度,TCP/IP 协议栈的每一层只需要考虑自己这一层的任务,并给上层提供机制。你的需要只是在 TCP 上再加一层,如果你愿意,就写成一个库的形式(参考 OpenSSL ) |
9 yulon Jul 8, 2018 因为可靠协议有序不丢包做成流是最好的,你要无序丢包直接用 UDP,你要无序不丢包直接用 UDP 做丢包验证。 |
10 teleme Jul 8, 2018 数据块的处理成本高,在规模型生产环境下,主要是基于流进行运算处理。 |
11 zhujinliang Jul 8, 2018 什么是流,给“块”加上 FIFO 缓冲就是流,为什么要加缓冲,可以想一下,通信线路不停地把数据送入 IO 设备,假设每毫秒到达一个字节 1. 如果没有缓冲,CPU 必须每毫秒至少检查一次设备寄存器,如果没来得及检查,要么设备丢弃后来的数据,要么后来的数据覆盖之前的数据,总之,因为没地方存放新来的数据,必须丢弃一个 2. 如果有一个 16KB 的缓冲,不考虑延迟的话,系统只需 8K 毫秒左右查询一次缓冲即可,如果缓冲有数据,就全部取出,批量处理,既减少了在查询操作上的开销,又降低了丢弃数据的概率 3. 还有一个方法是增加一个设备忙信号,IO 设备接收到一个数据后置为设备忙状态,直到 CPU 从设备中取走数据,这种方式可避免丢弃数据,但得到的结果是通信线路大量闲置,本可以 CPU 处理数据与线路传输同时进行,但因为没有缓冲,必须接收、处理轮流进行 |
12 c3824363 OP @hjc4869 是的就是要这个,bbr 对它有效么。 但是 windows 平台应该不支持吧 @des SEQPACKET 就是用 sctp 实现的吧 @shijingshijing @iwtbauh 看一些很早期的代码发现一个习惯就是要尽可能的节省内存, 感觉是这个原因导致的用"流"不用"块" @MeteorCat @SYP @whileFalse @xmadi 我觉得只有模拟音频信号通信才算绝对的流,TCP 是因为把数据弄到一块丢失了分块信息才看起来变成了“流” @teleme 是因为内存开销么 |
13 c3824363 OP @zhujinliang 还是不能解释既然有 UDP 也有 TCP,为什么不早早就弄一个有 UDP 优点的 TCP 呢。 而是近些年才有的 SEQPACKET |
14 hjc4869 Jul 8, 2018 @c3824363 SEQPACKET 也需要 congestion control,但是目前 Linux 的 bbr 似乎只是针对 TCP。 SCTP 既实现了 stream 也实现了 seqpacket,Windows 上有第三方驱动可以用,也可以用 raw socket 在用户态实现 SCTP。但是各类 NAT 设备可能没有很好地支持 SCTP。 |
15 hjc4869 Jul 8, 2018 另外楼主所说的,“绝大部分情况下”实际上是不成立的,例如 HTTP 协议就没有这么做。 |
17 jtsai Jul 8, 2018 via Android 流节省内存,流就是缓存的块。 |
18 liuminghao233 Jul 8, 2018 via iPhone 现在的基于 proactor 模型的网络库 你可以在包头加一个长度 跟 udp 比也就多一次 callback 还不够简单吗 |
20 owenliang Jul 8, 2018 via Android xml 流了解一下,并不是所有协议都是 package。 |
21 bao3 Jul 8, 2018 via iPhone 楼主你把一杯水倒入另一个杯子,你期待的是水像冰块一样掉到另一个杯子里吗?可是你不确定另一个杯子的口径,你也无法提前分割冰块的大小。另外冰块掉到另一个杯子可能的先后顺序是乱的。 但当你用液态水来倒的话,你就不作关心对方的口径以及到达的顺序。 对你来说,你期待的是用块来发送数据还是用流发送? |
22 CRVV Jul 8, 2018 > 如果 TCP 原本就保留“块”信息, 则使用起来就会简单很多 如果程序说要发送一个 1200 字节的块,要求保证送达,当前链路的 MTU 只有 800,该怎么处理? 1. 返回错误,这太难用了 2. 把 1200 的块拆开发出去 2.1 用 2 个包只发 1200 字节,这样浪费了 400 字节( 2 个包本来可以发 1600 ) 2.2 用第 1 个包发 800,第 2 个包发 400 再加上下一个块的 400 1 大约是带重传的 IPv6 2.1 大约是带重传的 IPv4 2.2 是 TCP 加上分块,所以新的问题是应该用哪个方法来分块? 固定长度, 加包头指定长度还是用特殊的间隔标记? 结论是 TCP 不能保留“块”信息,这样做只是把分块的问题推到了 TCP 上,而传输层比应用层更不知道需要什么分块方式 |
23 c3824363 OP @CRVV 用 UDP 那种方式就行, 现有的 iphdr 就能处理。参考 IP 分片 知道块的长度总会带来很多方便的, 很多用户态的代码都在处理下面这个事情 接收固定长度包头 根据包头信息接收指定长度的包 重复以上步骤 从实用的角度看,TCP 可以携带分块信息。 |
24 ipwx Jul 8, 2018 那当然是因为“流”比“块”更底层啊。 楼主你以为 UDP 是发了多大的块,就接收到多大的块嘛? IP 协议允许的包大小不超过 64K,但实际中不一定能达标。而且事实上这个 64K 打包会发生分片传输,实际的包传输大小也不过几百字节。 https://en.wikipedia.org/wiki/IP_fragmentation https://stackoverflow.com/questions/3712151/udp-ip-fragmentation-and-mtu 而且就算 IP fragmentation 默默地帮你搞定了重整,性能也实打实损失了的。 - - - - 总结一下,IP 协议中的“分块”是 IP 协议根据大部分传输介质的性质定出来的 IP 协议实现的标准,本身对于上层应用具有很有限的参考意义。由于 IP 包传输过程中大小不确定、分片机制不明,对于上层应用而言,“流”才是比“包”更底层的模型。 |
25 yanaraika Jul 8, 2018 sctp/quic/http2 欢迎你 |
27 redsonic Jul 8, 2018 楼主你理解错了,TCP 设计之初主要是面向文件传输的,在这种情况下没人会关心或干涉“流”之中的“块”。另一种用途是远程登录,因为这是人机的不间断交互所以本质也是流,同样不会关心“块”。如果你需要通过某些精心设计的“块”来驱动应用程序,那么 UDP 是干这事的。或者是 sctp。 |
28 hjc4869 Jul 8, 2018 @c3824363 能否 NAT 跟 SEQPACKET 无关,关键在于路由设备是否支持特定传输层协议。例如如果路由器支持 SCTP NAT,那么自然也支持 SCTP 的 SEQPACKET。 HTTP 的头部内容直接顺序读取流即可,直到 \r\n\r\n 即头部结束。后续的 content 也不需要分包,是货真价实的流。 |
29 julyclyde Jul 8, 2018 如果按块发,你还得自己拼顺序,就不只是从一个保证顺序的流里边抠出块那么简单了 |
32 q397064399 Jul 8, 2018 流是更低一层次的抽象,块是高层次的抽象,Unix 的哲学就是简单,一些都是文件的哲学 而文件正好就是流的形式, 你需要更高层次的接口,在这个抽象上进行封装就好了, |
33 dacapoday Jul 8, 2018 咋不看看当年有块设备吗?都是磁带,内存还是靠延时线存储的。 |
34 dacapoday Jul 8, 2018 流这种抽象一直沿用到现在,说明它最实用。 |
35 akira Jul 8, 2018 发送一个字节的时候怎么办 |
36 chinawrj Jul 8, 2018 你们啊,还太年轻。哈哈 |
37 momocraft Jul 8, 2018 首先流是很好的抽象, tcp 不是一上的消息完整性的. 另外明 tcp 不像在, 便人 protobuf 就能正理消息界 (看看中文互有多少人"粘包"). 在黎明期一接只一消息, 用接表示消息束并不罕, 比如 ftp 甚至很久后的 http0.9 和用是互相推的, 在怪 tcp 不是消息位可事後葛亮 |
38 goodniuniu Jul 8, 2018 本质就是流+1 |
39 yankebupt Jul 9, 2018 @c3824363 估计是参考当时的网速综合了实时性做出的妥协... 以包为单位,确认了这个包,这个包就算传到了,如果是实时聊天或者网游的话就可以拿去渲染了,延迟和 ping 一样... 如果是流,确认频次和包一样的话对比包没多大节省,如果确认间隔太长了碰到误码稍大,实时性差太远,即使 UDP 自定义纠错也比强行用 FEC 之类的纠错码压误码率来的性能略高(应该)。 |
40 Mirana Jul 9, 2018 分成 N 个块,每块之间都有次序,拼起来不就是个完整的流吗 协议设计不应该考虑太多平台,实现细节方面的问题 |
42 ca1123 Jul 9, 2018 不用流,你发的时候岂不是得知道尾巴在哪?有些应用,发的时候根本不知道什么时候会有尾巴。其实流的意思是,我的块就这样,全是标准的,看你怎么用吧。 |
43 enenaaa Jul 9, 2018 流比块更好用。 如果设计成块结构, 就会有人问, 我想像文件流那样读数据还得自己写代码? 我想自定义块结构, 还得在块上加块? |
46 zhicheng Jul 9, 2018 via iPhone 不是要设计成流,而是只能设计成流,不然设计成块,你打算让 OS 怎么办?传送一个 1G 的块,OS 要把它全 Cache 到内存里?内存不够怎么办?存到磁盘?一下可用场景就少了。如果一开始加上最大容量,比如 4G,现在又会嫌不够,又要处理多块变成流了。如果你真的需要块,请在创建连接的时候发一个 4 字节或 8 字节的长度,然后不停的 recv,至于存到内存里还是磁盘里还是转发给另一个设备,取决于 App 自己。如果一个工程师连这都解决不了,不建议去吐槽 TCP 的设计。 |