网络基本知识和网络传输过程
局域网(LAN, Local Area Network)
广域网(WAN, Wide Area Network)
OSI 七层模型
其实在网络角度,OSI 定的协议 7 层模型其实非常完善,但是在实际操作的过程 中,会话层、表示层是不可能接入到操作系统中的,所以在工程实践中,最终落地的是 5 层协议。
TCP/IP 五层(或四层)模型
TCP/IP 是一组协议的代名词,它还包括许多协议,组成了 TCP/IP 协议簇.
集线器
一、核心特性
- 整个集线器是一个冲突域
- 同一时间只能有一台设备发送数据,其他设备必须等待
- 带宽分配:例如,一个100Mbps的集线器连接了4台设备,实际每台设备的平均可用带宽可能只有25Mbps(理论值,实际更低)。
- 工作在OSI模型的物理层(第一层)
二、工作原理
- 端口将信号广播到所有其他端口
交换机
-
MAC地址表学习
- 自动建立MAC地址与端口映射表(如:
00:1A:2B:3C:4D:11 → 端口1
) - 示例表结构:
MAC地址 端口 老化计时器 00:1A:2B:3C:4D:11 Gi0/1 287s
- 自动建立MAC地址与端口映射表(如:
-
精准转发
- 查表确定目标端口后,仅向该端口转发帧
- 避免集线器的广播式传输
-
配合ARP协议
- 首次通信时通过广播ARP获取目标MAC地址
- 之后通信不再广播而是发送给指定端口
4.工作在数据链路层
交换机MAC地址学习实例
通信流程(主机A→主机B)
阶段1:初始状态(空MAC表)
交换机MAC地址表:
MAC地址 | 端口 |
---|---|
(空表) |
阶段2:ARP请求(广播)
-
主机A发送ARP请求:"谁是192.168.1.22?"(目标IP对应主机B)
- 源MAC: 00:1A:2B:3C:4D:11
- 目标MAC: FF:FF:FF:FF:FF:FF(广播地址)
-
交换机处理:
- 从端口1收到帧,学习源MAC:
# 伪代码表示学习过程 mac_table['00:1A:2B:3C:4D:11'] = '端口1' # 新增条目
- 广播到所有其他端口(端口2和端口3)
- 从端口1收到帧,学习源MAC:
-
更新后的MAC表:
| MAC地址 | 端口 |
|-------------------|-------|
| 00:1A:2B:3C:4D:11 | 端口1 |
阶段3:ARP响应(单播)
-
主机B响应ARP:"192.168.1.22是我,MAC是00:1A:2B:3C:4D:22"
- 源MAC: 00:1A:2B:3C:4D:22
- 目标MAC: 00:1A:2B:3C:4D:11
-
交换机处理:
- 从端口2收到帧,学习源MAC:
mac_table['00:1A:2B:3C:4D:22'] = '端口2' # 新增条目
- 查表发现目标MAC 00:1A:2B:3C:4D:11在端口1
- 仅转发到端口1(不广播)
- 从端口2收到帧,学习源MAC:
-
更新后的MAC表:
| MAC地址 | 端口 |
|-------------------|-------|
| 00:1A:2B:3C:4D:11 | 端口1 |
| 00:1A:2B:3C:4D:22 | 端口2 |
阶段4:之后数据传输
-
主机A发送数据给主机B:
- 源MAC: 00:1A:2B:3C:4D:11
- 目标MAC: 00:1A:2B:3C:4D:22
-
交换机操作:
- 确认源MAC已在表中(端口1)
- 查表确定目标MAC 00:1A:2B:3C:4D:22对应端口2
- 精准转发到端口2(不涉及端口3)
网卡和网卡驱动程序
网卡:
网卡(Network Interface Card,NIC),也称为网络适配器或网络接口控制器,是计算机网络通信的核心硬件设备,支持有线网卡(以太网)或无线网卡(Wi-Fi)
以太网(Ethernet)是一种有线局域网(LAN)技术标准,定义了物理层(Layer 1) 和 数据链路层(Layer 2) 的规范,用于通过网线(如双绞线、光纤)连接计算机、交换机、路由器等设备,实现高速、稳定的数据传输。
Wi-Fi(Wireless Fidelity)是一种无线局域网(WLAN)技术,定义了物理层(Layer 1) 和 数据链路层(Layer 2) 的规范,允许设备通过无线电波(而非网线)连接网络。
网卡的组成结构
PHY芯片 | 负责物理层信号转换,数字信号和光电信号以及电磁波的转换。 |
DMA引擎 | 对内存缓冲区直接内存访问(Direct Memory Access),减少CPU负担。 |
接口 | 连接网线接收光电信号,或者无线接收电磁波 |
TDT寄存器 | 驱动通过写入该寄存器,显式告知网卡,数据已就绪,请从发送缓冲区中取出并发送 |
内核会为网卡维护发送缓冲区和接收缓冲区
驱动程序:
驱动程序(Device Driver)的本质是操作系统内核中管理硬件的软件接口,但并非传统意义上的进程
- 驱动程序是被动执行的代码块,依赖硬件中断或上层调用,而非自主运行。
- 它作为内核的一部分,与硬件寄存器、DMA、中断等直接交互,不具备进程的独立性。
网卡和驱动相互配合来发送和接受数据
1. 发送数据
(1)驱动程序的职责
- 构建MAC帧头部:
驱动程序从上层(如IP层)接收报文,然后查arp缓存,有mac地址记录的话,那就添加以下字段:- 源MAC地址(本机网卡的MAC地址)。
- 目标MAC地址。
- 类型/长度字段(如
0x0800
表示IPv4)。
- 计算帧校验序列:
驱动程序会生成CRC校验值(但现代网卡通常由硬件计算)。 - 填充数据:
将封装好的MAC帧存入发送缓冲区(TX Ring),并写入TDT寄存器通知网卡硬件处理。
(2)网卡硬件的职责
- DMA读取:
网卡通过DMA(直接内存访问)从驱动程序的发送缓冲区中获取MAC帧数据。 - 物理信号转换:
PHY芯片将数字信号转换为电信号或光信号或电磁波,通过接口发出去。
2. 接收数据
(1)网卡硬件的职责
- 信号检测与转换:
接口接收信号,PHY芯片将接收到的电信号/光信号转换为数字数据。 - DMA写入:
将数据存入接收缓冲区(RX Ring),并且网卡触发中断通知CPU。
(2)驱动程序的职责
- 中断处理:
cpu在中断处理方法中调用驱动程序,从RX Ring中读取原始数据。 - 解包MAC帧:
- 检查目标MAC地址(判断是否为本机、广播地址)。
- 解析帧类型字段(如
0x0800
表示IPv4,需传递给IP层)。 - 剥离MAC帧头部和CRC字段。
- 提交给上层协议栈:
将解包后的数据(如IP数据包)传递给网络层,本质其实就是去调用ip协议的接口。
报文和报头
根据首部中的 "上层协议字段" 将数据交给对应的上层协议处理,交付给上层协议,在操作系统上的体现就是调用那个协议的接口
telnet协议
Telnet 是一种基于 TCP/IP 的应用层协议,用于通过终端来远程访问服务器,默认端口23,对应的是标准telnet服务器,也可以远程访问其他服务器
telnet过程
打开一个终端,操作系统会创建一个终端模拟器进程,并且向内核申请一对伪终端的主从设备,将终端模拟器的0/1/2均重定向到主设备,然后创建子进程,将子进程设为新会话,将0/1/2重定向到从设备,并且进程替换为shell,你敲下了telnet ip port,终端模拟器最终得到了这些数据(怎么得到有点复杂)并显示在了终端上,然后write进主设备,write会接着调用终端驱动代码,将数据从主设备弄到从设备,然后唤醒从设备读缓冲区等待队列上的进程,shell读取数据然后解析字符串,创建子进程·,将子进程设为新的进程组,然后exec替换为telnet客户端进程,并将该进程组设为前台进程组,shell变为后台进程组,telnet客户端会先根据命令行参数的ip和port进行连接,调用connect系统调用,connect的底层也无非就是调用tcp协议接口,设置syn标志位并填上端口字段等,然后交给下一层网络层,交给网络层其实就是调用IP协议的接口,会先去查路由表,确定下一跳的IP地址和发送的网卡接口,然后设置IP报头里上一层协议的类型字段,设置ip地址字段等,然后调用以太网协议接口,调用时将下一跳ip地址和网卡接口传过去传参过去,接口里会调用网卡驱动代码,网卡驱动会查arp缓存表,如果有下一跳IP地址的mac地址那就将IP报文封装为ip类型mac帧,如果没有那就封装arp的mac帧,mac头再加上crc校验或pad填充位就都有了,然后根据网卡接口来将mac帧写进对应网卡的发送缓冲区,并写对应网卡的TDT寄存器,使网卡用DMA将mac读出并用HVY芯片转变数字信号为光电信号,然后用接口发出去,以太网中会有交换机,记录端口和mac地址的映射关系,这样如果有记录那光电信号只会被交换机发到指定端口,假设下一跳的IP地址对应的mac地址是一个路由器,则路由器的网卡收到光电信号,然后HVY芯片将光电信号转成数字信号,DMA将mac帧写进网卡接收缓冲区,然后网卡向中断控制器发送中断请求,中断控制器生成中断号,并将cpu中断引脚设成高电平,cpu执行完当前指令的执行周期,进入中断周期,检查if中断标志寄存器,并检查中断引脚,然后有高电平那就将pc指针保存在用户栈栈顶,然后cpu陷入内核,将寄存器保存在内核栈,然后从中断控制器那里得到中断号,去中断向量表中执行对应中断函数,中断方法里会调用网卡驱动代码,从网卡接收缓冲区中读出mac帧,看帧类型是ip类型,然后检查crc检验位和目的mac地址,没问题后调用IP协议的接口,将IP报文传过去,将TTL减1,如果归0就把IP报文扔了,路由器会查路由表,根据目的ip选择下一跳IP地址和网卡接口,选好之后调用以太网协议接口将IP报文、下一跳ip、网卡接口传过去,接口函数如同之前,调用网卡驱动代码,查arp缓存表,假如有下一跳ip的mac地址,那驱动就构建ip类型的mac帧,写进网卡的发送缓冲区,然后写网卡TDT寄存器通知,网卡的DMA读然后HVY转接口发,假设这次服务器的主机的网卡接口收到了光电数据,和之前一样,HVY转DMA写,并且硬件中断,cpu执行中断处理函数,从网卡接收缓冲区读然后先从固定长度mac头里查看mac帧类型,是ip类型那检查mac地址和crc校验码,没问题那就去掉mac头和crc校验位,调用ip协议的接口,传入IP报文,然后会根据ip报头的首部长度,报文总长度,将IP报头和载荷分开,并根据IP报头中的上层协议类型,调用tcp协议接口,将tcp报文传参进入,先看标志是SYN那就根据tcp报头里的目的端口号,找到绑定该端口的套接字,比如我们自定义的服务器此时已经设置好了监听套接字,然后accept导致服务器进程阻塞在了监听套接字全连接队列上,那么收到该tcp报文后会在半连接队列上创建一个struct request_sock,然后组装tcp报文,SYN和ACK填充进去,还有源端口目的端口等,调用IP协议接口函数,传参tcp报文,ip接口会填充IP报头的源和目的ip,8位协议类型等,查路由表得到目的ip的下一跳ip和网卡接口,然后将IP报文和这两个传参给以太网协议接口函数,网卡驱动添加mac头和crc校验,然后写进缓冲区,还要写网卡TDT寄存器进行通知,网卡将mac帧通过接口发出去,如此等到客户端ack发来后,tcp接口函数找到端口绑定的监听套接字,将半连接队列中的struct request_sock升级为全连接队列中的struct sock,并唤醒监听套接字全连接队列的等待队列上的进程,然后服务器accept将全连接队列中的struct sock取出并创建struct socket和struct file,返回通信套接字描述符,接下来服务器会read通信套接字,然后就阻塞了,这是服务器端的逻辑,再看客户端,telnet进程的connect终于结束了,三次握手已经完成,接下来 telnet客户端会往终端写几行提示语,然后进入文本模式(linux下默认行模式),此时你往终端里写的字符流在按下回车后,telnet进程会将字符流不带回车write写进通信套接字的发送缓冲区,如果缓冲区数据达到MSS或者没有未确认的数据(未确认就是发出请求但还没有确认应答,没有未确认就是都还没发送),那就是telnet客户端write后接着组装tcp报文,接着调用ip协议接口,组装ip报头,查路由表得到下一跳和网卡接口,传参给以太网协议接口,网卡驱动查arp缓存,封装IP类型mac帧,然后网卡发出去,服务器端网卡最后收到mac帧并触发硬件中断开始解包,最终将你在终端上写的数据给原封不动弄到服务器通信套接字的接收缓冲区里,并唤醒接收缓冲区等待队列的进程,服务器开始从通信套接字接收缓冲区里读数据,如此一来,就可以利用telnet来远程通过终端访问服务器了~
Telnet 通信全流程详解(AI提供的修正与补充)
以下是 Telnet 通信的完整流程,结合你的描述进行修正和补充,确保关键环节的准确性:
1. 终端与 Shell 的初始化
-
终端模拟器启动
- 用户打开终端(如 GNOME Terminal),操作系统创建 终端模拟器进程。
- 内核分配一对 伪终端(PTY):
- 主设备(PTM):终端模拟器持有,绑定到其
stdin/stdout/stderr
。 - 从设备(PTS):后续由 Shell 使用。
- 主设备(PTM):终端模拟器持有,绑定到其
- 终端模拟器调用
fork()
创建子进程,子进程:- 调用
setsid()
创建新会话(脱离原终端控制)。 - 将
stdin/stdout/stderr
重定向到从设备(PTS)。 - 通过
exec()
替换为 Shell 进程(如/bin/bash
)。
- 调用
-
用户输入
telnet IP PORT
- Shell 解析命令,
fork()
创建子进程并替换为telnet
客户端程序。 telnet
进程成为 前台进程组,Shell 转为后台。
- Shell 解析命令,
2. Telnet 客户端发起连接(三次握手)
(1) connect()
系统调用
- TCP 层处理:
- 构建
SYN
报文(源端口随机,目的端口为PORT
)。 - 设置
SYN=1
,初始化序列号(ISN
)。
- 构建
- IP 层处理:
- 查询路由表确定下一跳 IP(如默认网关)和出口网卡(
eth0
)。 - 封装 IP 报头:源 IP(本机)、目的 IP(
IP
)、协议类型(6
表示 TCP)。
- 查询路由表确定下一跳 IP(如默认网关)和出口网卡(
- 链路层处理:
- 通过 ARP 解析下一跳 MAC:
- 查 ARP 缓存 → 若无则广播 ARP 请求。
- 封装以太网帧:
- 目标 MAC(网关或服务器)、源 MAC(本机网卡)、类型(
0x0800
)。 - 附加 CRC 校验,填充(Padding)至最小帧长(64 字节)。
- 目标 MAC(网关或服务器)、源 MAC(本机网卡)、类型(
- 网卡驱动将帧写入 TX Ring Buffer,更新 TDT 寄存器触发 DMA 发送。
- 通过 ARP 解析下一跳 MAC:
(2) 服务器响应 SYN-ACK
- 物理层:服务器网卡接收光电信号,转为数字信号,DMA 写入接收缓冲区。
- 中断处理:
- 网卡触发硬件中断 → CPU 保存现场,调用中断处理程序。
- 内核从缓冲区提取以太网帧,校验 MAC 和 CRC,剥离帧头。
- IP 层检查目标 IP 和 TTL,转发给 TCP 层。
- TCP 层:
- 匹配监听套接字(
PORT
),将连接请求放入 半连接队列(SYN_RCVD
状态)。 - 回复
SYN-ACK
(SYN=1, ACK=1
),序列号初始化。
- 匹配监听套接字(
(3) 客户端完成握手(ACK
)
- 客户端收到
SYN-ACK
后发送ACK
,连接建立(ESTABLISHED
)。 - 服务器将连接从 半连接队列 移至 全连接队列,唤醒
accept()
阻塞的进程。 - 服务器
accept()
返回 通信套接字(struct socket + struct file
)。
3. 数据传输(Telnet 交互)
(1) 客户端发送数据
- 用户输入字符(如
ls
+ 回车):- 终端模拟器将字符写入 PTY 主设备 → 驱动转发到从设备 →
telnet
进程读取。 telnet
将数据写入 TCP 发送缓冲区(write()
系统调用)。
- 终端模拟器将字符写入 PTY 主设备 → 驱动转发到从设备 →
- TCP 层封装:
- Nagle 算法决定是否立即发送(数据≥MSS 或无未确认数据)。
- 封装 TCP 报头:源/目的端口、
ACK
号、PSH=1
(提示接收方尽快提交给应用层)。
- IP 层 + 链路层:同握手流程,通过路由和 ARP 发送到服务器。
(2) 服务器接收数据
- 服务器网卡收包 → 中断处理 → TCP 层确认
ACK
。 - 数据存入套接字接收缓冲区,唤醒
read()
阻塞的进程。 - 服务器从缓冲区读取数据(如
ls
),交给 Shell 执行,结果通过 TCP 返回客户端。
(3) 文本模式 vs 行模式
- 行模式(默认):客户端等待用户按回车后发送整行数据。
- 字符模式:通过 Telnet 命令
send wont linemode
禁用行缓冲,实现逐字符发送。
4. 关键修正与补充
-
ARP 与路由表的优先级
- 你的描述中 “查路由表得到下一跳 IP” 是正确的,但需明确:
- 若目的 IP 是同一子网,下一跳就是目的 IP 本身。
- 若跨子网,下一跳是默认网关的 IP。
- ARP 解析的是下一跳 IP 的 MAC,而非目的 IP 的 MAC(除非直连)。
- 你的描述中 “查路由表得到下一跳 IP” 是正确的,但需明确:
-
中断处理的细节
- 网卡中断触发后,内核通过 NAPI 或硬中断 收包,并非所有数据都依赖硬中断(高负载时可能轮询)。
- 中断处理程序通常分为 上半部(快速响应) 和 下半部(软中断处理数据)。
-
Nagle 算法与定时器
- Nagle 的触发条件是 数据≥MSS 或无可缓存的未确认数据,而非单纯超时。
- 超时(如 200ms)是某些实现为防止无限等待的补充机制,非协议标准。
-
TCP 半连接/全连接队列
- 半连接队列:存储
SYN_RCVD
状态的请求(struct request_sock
)。 - 全连接队列:存储
ESTABLISHED
的连接(struct sock
),accept()
从此队列取连接。 - 队列溢出会导致握手失败(如 SYN Flood 攻击时半连接队列满)。
- 半连接队列:存储
-
PTY 与终端驱动
- 终端模拟器(如 GNOME Terminal)和 Shell 通过 伪终端(PTY) 交互,而非直接读写 TTY 设备。
telnet
客户端的输入/输出通过 PTY 主从设备转发,与真实串口终端(如/dev/ttyS0
)无关。
Nagle 算法
- 无未确认数据 或 待发数据 ≥ MSS → 立即发送。
tcp发送缓冲区的数据如果大于等于MSS那就组装报文开始发,如果缓冲区没有那种发出去请求但还没有收到确认应答的数据,那就直接发
理解IP地址和端口
IP地址用来标识一个网卡
在主机或路由器发送数据封装报文的时候,会将目的ip地址进行路由,查找下一跳ip和发送数据的网卡接口,然后数据链路层又会根据下一跳IP地址得到对应mac地址,所以ip地址本质是标识了一个网卡,当然,有种说法叫IP地址表示了一台主机,很明显一个主机可以有多张网卡,所以标识网卡更细
端口用来标识一个套接字
在不使用套接字复用的情况下,一个套接字只能绑定一个端口,一个端口也只能对应一个套接字,有种说法是端口标识一个进程,实际是不准确的,因为一个进程实际上是可以创建多个套接字。在聊更多的话,端口其实不像ip,ip就单单一个IP就可以标识一个网卡,可端口往往是(协议,ip,端口)这样一个元组才能标识一个套接字,毕竟套接字bind绑定的时候绑定的不单单是一个端口
端口复用
1.端口的本质其实就是一个16bit的数,其范围是0~65535,但其背后代表的意义往往是一个类似(协议,ip,端口)这样的元组
2.其划分是
0-1023
:系统保留端口(需root权限绑定,如HTTP 80、SSH 22)。1024-49151
:用户端口(普通应用可用)。49152-65535
:临时端口(客户端连接时动态分配)。
3.解包时如何找到套接字?
在linux中使用不同哈希表,来进行解包时对应套接字的查找
哈希表 | 存储内容 | 键的组成 | |
---|---|---|---|
ehash | 通信套接字(ESTABLISHED) | {协议, 源IP, 源端口, 目标IP, 目标端口} | |
lhash | 监听套接字(LISTEN) | {协议, 目标IP, 目标端口} |
首先我们明确一些东西
1.建立连接也就是三次握手是客户端通信套接字和服务端监听套接字之前互发消息
2.断开连接也就是四次挥手是客户端通信套接字和服务端通信套接字之间发消息
3.三次握手后监听套接字的全连接队列里有struct sock,accept后会创建对应struct socket和struct file结构体然后返回通信套接字描述符,有一点很重要,通信套接字和监听套接字绑定的端口是一样的
解包时查找套接字:
SYN
包:严格由lhash
中的监听套接字处理。-
ACK/PSH/FIN
包:优先查ehash
,仅ACK
在未命中时备用查lhash
。
注意:为什么通信套接字的键需要五元组呢?因为有端口复用,所以套接字和端口不是一对一的关系,可能会有多个套接字绑定同一个端口,只靠三元组已经无法得出要给那个通信套接字了,而且服务器的通信套接字是和客户端的通信套接字已经建立连接的,所以当然可以通过源ip和源port来进一步确定通信套接字
那为什么监听套接字是三元组呢?监听套接字不和任何客户端套接字建立连接,所以就是三元组了
4.SO_REUSEADDR和SO_REUSEPORT
SO_REUSEADDR
:解决 TIME_WAIT
冲突
核心作用
- 允许绑定处于
TIME_WAIT
状态的端口,便于服务快速重启。 - 不允许多个活跃套接字同时绑定同一端口(仅解决历史连接残留问题)。
限制
- 若端口已被 非
TIME_WAIT
的套接字 占用(如ESTABLISHED
),仍会冲突。
SO_REUSEPORT
:真正的端口共享
核心作用
- 允许多个套接字同时绑定完全相同的
{协议, IP, 端口}
。 - 解包时内核通过 五元组哈希 将数据包分配给不同套接字
网络字节序
多字节数据相对于内存地址有大端和小端之分,网络数据流同样有大端小端之分
1.TCP/IP 协议规定,网络数据流应采用大端字节序,即低字节高地址.
2.不管这台主机是大端机还是小端机, 都得按照这个 TCP/IP 规定的网络字节序来发送/接收数据
这些函数名很好记,h 表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位短整数
套接字地址结构体
1.struct sockaddr
是 通用的套接字地址结构体,用于统一参数类型
字段 | 说明 |
---|---|
sa_family | 地址族(如 AF_INET 表示 IPv4,AF_INET6 表示 IPv6)。 |
sa_data | 存储具体的地址信息(如 IP + 端口),实际使用时需转换为具体结构体(如 sockaddr_in )。 |
2.struct sockaddr_in是IPV4的套接字地址结构体
3.IPv6套接字地址结构体sockaddr_in6
4.UNIX域套接字地址结构体(sockaddr_un
)