第2篇:为什么要有分层?从工程实践到架构设计

第2篇:为什么要有分层?从工程实践到架构设计

问题引入

在开始学习网络协议时,很多人都会问:为什么要搞这么多层?直接把数据发出去不就行了吗?为什么要有OSI七层模型、TCP/IP四层模型?

这不是一个理论问题,而是一个从无数工程实践中总结出来的深刻教训。

真实场景

我的亲身经历

2015年,我在创业公司做即时通讯。最初版本很简单:

python

复制代码

# 版本1:简单粗暴

def send_message(message, peer_ip):

# 直接用UDP发送

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

sock.sendto(message.encode(), (peer_ip, 8888))

问题出现了:

消息经常丢失

不知道对方收到没有

网络波动时完全不可用

版本2:自己实现可靠传输

python

复制代码

# 版本2:加上确认和重传

def send_message(message, peer_ip, msg_id):

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

packet = struct.pack('!I', msg_id) + message.encode()

for attempt in range(3):

sock.sendto(packet, (peer_ip, 8888))

try:

sock.settimeout(1.0)

ack, _ = sock.recvfrom(1024)

if struct.unpack('!I', ack)[0] == msg_id:

return True

except socket.timeout:

continue

return False

新问题又来了:

代码越来越复杂

要处理乱序

要处理流量控制

要处理连接管理

版本3:开始分层

直到有一天,我看到了Linux内核的网络栈实现,恍然大悟:

复制代码

应用层(消息逻辑)

传输层(可靠传输)

网络层(路由)

链路层(物理传输)

重构后:

python

复制代码

# 传输层

class ReliableTransport:

def send(self, data):

# 处理确认、重传、流量控制

pass

def recv(self):

# 处理乱序重组

pass

# 应用层

class MessagingApp:

def __init__(self, transport):

self.transport = transport

def send_message(self, msg):

self.transport.send(msg.encode())

结果:

代码清晰了10倍

传输层可以复用

应用层只关注业务逻辑

结论先行

分层架构不是理论家的发明,而是无数工程师用血泪换来的工程实践总结。它通过解耦、复用、标准化三大核心价值,解决了复杂系统的可维护性、可扩展性和协作问题。

理解分层架构,你将掌握:

为什么网络要分层

分层带来的实际价值

如何在工程中应用分层思想

避免重蹈前人的覆辙

原理讲解

1. 分层的三大核心价值

1.1 解耦:关注点分离

没有分层的代码:

python

复制代码

def handle_network_packet(packet):

# 处理以太网

if packet.type == ETH_P_IP:

# 处理IP

if ip.proto == IPPROTO_TCP:

# 处理TCP

if tcp.dport == 80:

# 处理HTTP

http_request = parse_http(tcp.payload)

response = handle_http(http_request)

# 构造HTTP响应

# 构造TCP响应

# 构造IP响应

# 构造以太网响应

send_packet(response)

问题:

改HTTP要碰TCP代码

改TCP要碰IP代码

一个bug影响所有层

无法独立测试

分层后的代码:

python

复制代码

# 链路层

def eth_rx(packet):

if packet.type == ETH_P_IP:

ip_rx(packet.payload)

# 网络层

def ip_rx(packet):

if packet.proto == IPPROTO_TCP:

tcp_rx(packet.payload)

# 传输层

def tcp_rx(packet):

if packet.dport == 80:

http_rx(packet.payload)

# 应用层

def http_rx(request):

response = handle_http(request)

tcp_send(response)

每一层只关心:

接收来自上层的数据

处理自己的职责

传给下层

不关心其他层的实现

1.2 复用:一次实现,多处使用

没有分层:

python

复制代码

# HTTP服务器要重写一遍TCP

def http_server():

tcp_implementation()

http_implementation()

# FTP服务器也要重写一遍TCP

def ftp_server():

tcp_implementation()

ftp_implementation()

# SSH服务器还要重写一遍TCP

def ssh_server():

tcp_implementation()

ssh_implementation()

结果:

重复代码

Bug要修N次

改进要改N个地方

分层后:

python

复制代码

# TCP只实现一次

class TCP:

def connect(): ...

def send(): ...

def recv(): ...

# 所有应用都复用

class HTTPServer:

def __init__(self):

self.tcp = TCP()

class FTPServer:

def __init__(self):

self.tcp = TCP()

实际例子:Linux内核的TCP实现

复制代码

所有应用

Socket API(复用)

TCP实现(只一份)

IP实现(只一份)

1.3 标准化:跨厂商协作

没有标准化:

复制代码

Cisco设备 Huawei设备 Juniper设备

| | |

私有协议 私有协议 私有协议

| | |

无法互通 无法互通 无法互通

有了标准化:

复制代码

Cisco设备 Huawei设备 Juniper设备

| | |

TCP/IP TCP/IP TCP/IP ← 标准

| | |

完美互通 完美互通 完美互通

真实故事:

1970年代:各个厂商私有协议

1980年代:TCP/IP成为标准

今天:全球互联网基于TCP/IP

2. 分层的代价

分层不是免费的,有代价:

代价

说明

如何权衡

性能开销

每层封装/解封装

现代硬件足够快

复杂度

理解多层架构

长期收益更大

调试困难

跨层问题定位难

工具和经验

但这些代价是值得的:

可维护性提升10倍

可扩展性提升10倍

协作效率提升10倍

3. 网络分层的实际收益

3.1 IPv4升级IPv6

没有分层:

复制代码

所有应用都要改

所有中间件都要改

所有驱动都要改

工作量:百万行代码

有分层:

复制代码

只改网络层

应用层不用动

驱动层不用动

工作量:可控

3.2 从有线到无线

没有分层:

复制代码

所有应用都要改WiFi逻辑

TCP要改WiFi逻辑

IP要改WiFi逻辑

噩梦!

有分层:

复制代码

只改链路层

上层完全不知道

WiFi就像有线一样用

抓包实验

实验1:观察分层封装

1. 抓包

bash

复制代码

# 抓取HTTP包

sudo tcpdump -i any -nn -X 'tcp port 80' -c 1

2. 观察输出

复制代码

14:32:15.123456 IP 192.168.1.100.54321 > 93.184.216.34.80: Flags [S], seq 12345, win 65535, length 0

0x0000: 4500 003c 1234 0000 4006 0000 c0a8 0164 E..<....@.....d

0x0010: 5db8 d822 d431 0050 0001 2345 0000 0000 ]..".1.P..#.....

0x0020: 5002 ffff 0000 0000 0000 0000 0000 0000 P...............

3. 解析分层

复制代码

【以太网头 14字节】

0x0000: 4500 003c ...

目的MAC(6) 源MAC(6) 类型(2) = 0x0800 (IP)

【IP头 20字节】

0x0000: 4500 003c ...

版本(4) IHL(4) TOS(8) 总长度(16)

标识(16) 标志(3) 片偏移(13)

TTL(8) 协议(6)=TCP 首部校验和(16)

源IP: 192.168.1.100

目的IP: 93.184.216.34

【TCP头 20字节】

0x0010: 5db8 d822 ...

源端口: 54321

目的端口: 80

序号: 12345

确认号: 0

数据偏移(4) 保留(6) 标志(6) 窗口(16)

校验和(16) 紧急指针(16)

4. 每一层的职责

层级

字段

职责

以太网

MAC地址

本地传输

IP

IP地址

路由寻址

TCP

端口号

进程寻址

HTTP

URL

应用逻辑

实验2:对比分层 vs 不分层的代码复杂度

1. 不分层的实现(简化版)

python

复制代码

# 不分层:1000行才能实现简单功能

def send_http_request(url, data):

# 1. 构造以太网头

eth = struct.pack('!6s6sH',

b'\xff\xff\xff\xff\xff\xff', # 广播MAC

b'\x00\x11\x22\x33\x44\x55', # 源MAC

0x0800) # IP类型

# 2. 构造IP头

ip = struct.pack('!BBHHHBBH4s4s',

0x45, # 版本+IHL

0x00, # TOS

20+20+len(data), # 总长度

12345, # ID

0x0000, # 标志+偏移

64, # TTL

6, # TCP

0, # 校验和(稍后计算)

socket.inet_aton('192.168.1.100'), # 源IP

socket.inet_aton('93.184.216.34')) # 目的IP

# 3. 构造TCP头

tcp = struct.pack('!HHLLBBHHH',

54321, # 源端口

80, # 目的端口

12345, # 序号

0, # 确认号

5<<12, # 数据偏移

0x02, # SYN标志

65535, # 窗口

0, # 校验和

0) # 紧急指针

# 4. 计算校验和

# ... (50行代码)

# 5. 发送

raw_socket.send(eth + ip + tcp + data)

问题:

1000行代码只实现了基础功能

没有重传

没有流量控制

没有连接管理

2. 分层的实现

python

复制代码

# 分层:10行代码实现相同功能

import requests

response = requests.get('http://example.com')

print(response.text)

差异:

10行 vs 1000行

功能完整 vs 功能简陋

易维护 vs 难维护

源码入口

Linux内核的分层实现

让我们看看Linux内核是如何体现分层思想的。

1. 应用层 → 传输层

文件路径: net/socket.c

关键函数:

c

复制代码

// net/socket.c

SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,

unsigned int, flags, struct sockaddr __user *, addr,

int, addr_len)

{

// 应用层系统调用入口

// 不关心下层如何实现

return sock_sendmsg(sock, &msg, len);

}

特点:

只负责接收应用请求

调用传输层

不关心TCP还是UDP

2. 传输层 → 网络层

文件路径: net/ipv4/tcp.c

关键函数:

c

复制代码

// net/ipv4/tcp.c

int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)

{

// TCP层处理

// 处理可靠传输

// 调用IP层

return tcp_send_skb(sk, skb);

}

特点:

只负责TCP逻辑

不关心路由

调用IP层

3. 网络层 → 链路层

文件路径: net/ipv4/ip_output.c

关键函数:

c

复制代码

// net/ipv4/ip_output.c

int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl)

{

// IP层处理

// 处理路由

// 调用链路层

return dev_queue_xmit(skb);

}

特点:

只负责IP路由

不关心物理传输

调用链路层

4. 链路层

文件路径: net/core/dev.c

关键函数:

c

复制代码

// net/core/dev.c

int dev_queue_xmit(struct sk_buff *skb)

{

// 链路层处理

// 调用网卡驱动

return netdev_start_xmit(skb, dev);

}

特点:

只负责设备队列

不关心网络协议

调用驱动

5. 完整调用链

复制代码

应用层: write()

系统调用: sys_sendto() [net/socket.c]

Socket层: sock_sendmsg() [net/socket.c]

传输层: tcp_sendmsg() [net/ipv4/tcp.c]

网络层: ip_queue_xmit() [net/ipv4/ip_output.c]

链路层: dev_queue_xmit() [net/core/dev.c]

驱动层: hard_start_xmit() [drivers/net/...]

硬件: 网卡发送

每层都是独立的,只调用下一层!

常见陷阱

1. 过早优化:合并分层

场景: 为了性能,把TCP和IP合并。

结果:

复制代码

合并前:5层,清晰

合并后:1层,混乱

难以维护

无法复用

Bug不断

真实案例:

某公司为了"优化"合并了两层

3年后没人敢碰

最后花了3个月重构回来

教训:

不要为了微小的性能优化破坏架构

先测量,再优化

保持架构清晰

2. 过度分层:为了分层而分层

场景: 每层只有几行代码,分成10层。

结果:

性能开销大

调试困难

理解困难

教训:

分层要合理

每层要有明确的职责

避免过度设计

3. 跨层调用:破坏分层

场景: 应用层直接调用驱动层。

结果:

耦合严重

无法替换驱动

测试困难

教训:

严格遵守分层边界

只调用相邻层

保持解耦

思考题

分层有性能开销(封装/解封装、函数调用),为什么仍然值得?在什么情况下可以考虑合并分层?

如果你要设计一个全新的网络协议栈,你会分成几层?每层的职责是什么?

在你的工作中,有没有遇到过因为没有分层导致的问题?后来是如何解决的?

除了网络协议栈,你还知道哪些系统使用了分层架构?它们的分层思想是什么?

微服务架构和分层架构有什么异同?如何结合使用?

下篇预告

理解了为什么要分层,下一个问题自然是:数据在各层之间传递时,到底发生了什么?数据包是如何像洋葱一样被一层层包裹,又被一层层剥开的?

下篇文章,我们将深入探讨"数据在各层是如何变形的",通过真实的抓包分析和内核源码,揭示数据穿越网络栈的完整旅程。

相关探索

我要还款:钱小乐在线还款攻略
bet·365

我要还款:钱小乐在线还款攻略

美利达野狼5配置我要点评
365流水不够不能提现

美利达野狼5配置我要点评

好玩的十大经典老游戏有哪些 有趣的经典游戏盘点2025
365官网登录网址

好玩的十大经典老游戏有哪些 有趣的经典游戏盘点2025