第一百零七章:造船之术
问道期涉及内核源码:
一
清晨的码头笼罩在薄雾中。
林小源走进了码头旁的造船厂。厂房很大,地上散落着木料、铁钉和图纸。一个年轻的造船匠正蹲在船坞里,手里拿着刨子,仔细地修整一块船板。
"你是新来的?" 造船匠抬起头,脸上沾着木屑。"我负责 ,所有船都从我手里出去。"
他拍了拍身旁三条并排的船。第一条船体厚重,龙骨粗壮,甲板上装着滑轮和绞盘。第二条船身纤细,像一片柳叶,船尾绑着一只鸽笼。第三条船通体透明,能看到内部的每一个结构。
"你看好了。" 造船匠指着第一条,",TCP 商船。它可靠——每件货物都有编号,到了对岸要对方签字确认。丢了?重发。乱了?排序。慢是慢了点,但货到付款,万无一失。"
他又指向第二条:",UDP 信鸽船。把信往鸽子腿上一绑,撒手就飞。到没到?不知道。到了几只?不知道。但它快啊——DNS 查个名字、视频流几帧画面、游戏里报个坐标,要的就是这个快。丢了就丢了,应用层自己兜着。"
最后他敲了敲那条透明的船:",原始潜艇。这条船能看到海底的每一根缆线——IP 层、甚至以太网帧,全都暴露在外面。你可以自己造协议、自己填报文头。但是," 他压低声音,"没有 root 修为,你碰都别想碰。"
林小源蹲下来,仔细看三条船的船板。TCP 商船的船板之间严丝合缝,每块板上都烙着序列号。UDP 信鸽船的船板轻薄,几乎没有冗余。原始潜艇的船板干脆就是半透明的,能直接看到里面流水的纹理。
"前辈,还有别的船型吗?"
造船匠从角落里拖出一条半成品:",有序消息船。跟 TCP 商船一样可靠、有序,但它有消息边界——每批货物是独立的一包,不会跟下一批混在一起。Unix domain socket 最常用这种。"
林小源琢磨了一下——船型不同,走的路就不同,这道理跟修仙选功法差不多。
二
造船匠开始给 TCP 商船装货。
他把一批货物搬上甲板,但没有用箱子分开——直接堆成了一座小山。货物之间没有隔板、没有标记,从头到尾就是一整条连续的货带。
"你看到了?这就是流式。" 造船匠说,"TCP 发出去的数据,就像水流——你往河里倒一桶水和倒两桶水,下游收到的是一条连续的河,你分不清哪滴水是哪桶的。"
他指了指船尾的一台绞盘:"所以应用层得自己做分界。要么定长,要么加分隔符,要么在头部写长度。TCP 不管这事——它只保证字节按序到达,不保证消息边界。"
然后他走到 UDP 信鸽船旁边,把货物一包一包装进密封的信封,每封信上都写着收件人地址。"UDP 不同。你发 100 字节,对方就收到 100 字节的一封信。你发 200 字节,那就是另一封信。每封信是独立的,有明确的开头和结尾。"
林小源拿起一个信封,封口处用火漆封着。"所以如果用 TCP 发两个消息,接收方可能一次性全收到?"
"对。可能收到半条、一条半、甚至两条黏在一起——这叫粘包。TCP 根本不知道'消息'是什么,它只知道'字节流'。" 造船匠把信封扔进鸽笼,"所以你得自己定规矩。UDP 天然有边界,但不可靠。TCP 可靠,但没边界。没有两全其美的船。"
搞了半天,TCP 的粘包问题根源就在这——它压根不认识"消息",只认识"字节流"。
三
傍晚,造船匠在码头边生了一堆火。
"你知道为什么 TCP 这么麻烦吗?" 他往火里扔了一根木柴。"可靠性不是免费的。"
他掰着手指算:"三次握手——来回三个报文才能开始传数据。确认机制——每收一批货都得回一封信说'收到了'。超时重传——没收到确认就得重发。滑动窗口——不能一口气全发出去,得看对方能接多少。拥塞控制——网络堵了还得减速。"
林小源坐在火堆旁,看着海面上那些轻快的 UDP 信鸽船在夕阳下掠过水面。"那些信鸽船就不需要这些?"
"不需要。发了就走,不管到不到。所以 UDP 的报文头只有 8 个字节——源端口、目的端口、长度、校验和,完事。TCP 的报文头至少 20 字节,加上选项能到 60。" 造船匠望着火光,"这就是代价。你想要可靠,就得付出开销。你想要快,就得接受可能丢包。"
"那怎么选?"
"看你运什么。运金银珠宝——TCP。运街边传单——UDP。运军火——那得看情况。" 造船匠笑了笑,"没有最好的船,只有最合适的船。"
他忽然明白了码头老者之前说的那句话——天下没有白来的可靠,每一份确认都是要花时间的。
/*
* socket 类型:
*
* SOCK_STREAM (TCP):
* - 可靠传输
* - 有序
* - 面向连接
* - 流式(无消息边界)
*
* SOCK_DGRAM (UDP):
* - 不可靠传输
* - 无连接
* - 消息式(有消息边界)
* - 快速
*
* SOCK_RAW:
* - 直接访问 IP 层
* - 可以构造自定义协议
* - 需要 root 权限
*
* SOCK_SEQPACKET:
* - 有序、可靠
* - 消息式(有消息边界)
* - 用于 Unix domain socket
*/
printf("=== socket 类型 — 不同的"船" ===\n\n");
printf("socket 类型对比:\n\n");
printf("%-15s %-10s %-10s %-10s %-10s\n",
"类型", "可靠", "有序", "连接", "边界");
printf("%-15s %-10s %-10s %-10s %-10s\n",
"---", "---", "---", "---", "---");
printf("%-15s %-10s %-10s %-10s %-10s\n",
"SOCK_STREAM", "是", "是", "是", "无");
printf("%-15s %-10s %-10s %-10s %-10s\n",
"SOCK_DGRAM", "否", "否", "否", "有");
printf("%-15s %-10s %-10s %-10s %-10s\n",
"SOCK_RAW", "否", "否", "否", "无");
printf("%-15s %-10s %-10s %-10s %-10s\n",
"SOCK_SEQPACKET", "是", "是", "是", "有");
printf("\n--- SOCK_STREAM (TCP) ---\n");
printf("可靠的商船:\n");
printf(" 保证数据到达\n");
printf(" 保证顺序正确\n");
printf(" 面向连接(三次握手)\n");
printf(" 流式传输(无消息边界)\n\n");
printf("--- SOCK_DGRAM (UDP) ---\n");
printf("迅捷的信鸽:\n");
printf(" 不保证到达\n");
printf(" 不保证顺序\n");
printf(" 无连接\n");
printf(" 消息式(有边界)\n\n");
printf("--- SOCK_RAW ---\n");
printf("透明的潜水艇:\n");
printf(" 直接访问 IP 层\n");
printf(" 可以构造自定义协议\n");
printf(" 需要 root 权限\n\n");
printf("--- 协议选择 ---\n");
printf("TCP: 文件传输、网页浏览、邮件\n");
printf("UDP: 视频流、游戏、DNS 查询\n");
printf("RAW: 网络诊断、自定义协议\n");#include <stdio.h>
/*
* socket 类型:
*
* SOCK_STREAM (TCP):
* - 可靠传输
* - 有序
* - 面向连接
* - 流式(无消息边界)
*
* SOCK_DGRAM (UDP):
* - 不可靠传输
* - 无连接
* - 消息式(有消息边界)
* - 快速
*
* SOCK_RAW:
* - 直接访问 IP 层
* - 可以构造自定义协议
* - 需要 root 权限
*
* SOCK_SEQPACKET:
* - 有序、可靠
* - 消息式(有消息边界)
* - 用于 Unix domain socket
*/
int main() {
printf("=== socket 类型 — 不同的"船" ===\n\n");
printf("socket 类型对比:\n\n");
printf("%-15s %-10s %-10s %-10s %-10s\n",
"类型", "可靠", "有序", "连接", "边界");
printf("%-15s %-10s %-10s %-10s %-10s\n",
"---", "---", "---", "---", "---");
printf("%-15s %-10s %-10s %-10s %-10s\n",
"SOCK_STREAM", "是", "是", "是", "无");
printf("%-15s %-10s %-10s %-10s %-10s\n",
"SOCK_DGRAM", "否", "否", "否", "有");
printf("%-15s %-10s %-10s %-10s %-10s\n",
"SOCK_RAW", "否", "否", "否", "无");
printf("%-15s %-10s %-10s %-10s %-10s\n",
"SOCK_SEQPACKET", "是", "是", "是", "有");
printf("\n--- SOCK_STREAM (TCP) ---\n");
printf("可靠的商船:\n");
printf(" 保证数据到达\n");
printf(" 保证顺序正确\n");
printf(" 面向连接(三次握手)\n");
printf(" 流式传输(无消息边界)\n\n");
printf("--- SOCK_DGRAM (UDP) ---\n");
printf("迅捷的信鸽:\n");
printf(" 不保证到达\n");
printf(" 不保证顺序\n");
printf(" 无连接\n");
printf(" 消息式(有边界)\n\n");
printf("--- SOCK_RAW ---\n");
printf("透明的潜水艇:\n");
printf(" 直接访问 IP 层\n");
printf(" 可以构造自定义协议\n");
printf(" 需要 root 权限\n\n");
printf("--- 协议选择 ---\n");
printf("TCP: 文件传输、网页浏览、邮件\n");
printf("UDP: 视频流、游戏、DNS 查询\n");
printf("RAW: 网络诊断、自定义协议\n");
return 0;
}道藏笔记
内核启示
造船匠说得没错——socket 类型就是你选的船型,选错了船,货到不了岸。
是 TCP 商船,可靠、有序、面向连接,但它是流式的,没有消息边界,你往河里倒一桶水和倒两桶水,下游收到的是一条连续的河。 是 UDP 信鸽船,不可靠、无连接,但它有消息边界——你发多少字节,对方就收到多少字节的一封信,不会混在一起。 是原始潜艇,能直接看到 IP 层甚至以太网帧,没有 root 修为别碰。 有序可靠又有消息边界,Unix domain socket 最常用。
流式和消息式的区别说白了就是:流式像河流,消息式像信封。TCP 根本不知道"消息"是什么,它只知道字节流,所以应用层得自己做分界。UDP 天然有边界,但不保证到达。没有两全其美的船,选哪个取决于你运什么。
socket 类型之试
造船之术里,面向连接、可靠传输的 socket 类型最终对应哪种传输协议?