1. 网络这门课最核心的思想:分层到底在分什么
1.1 网络要解决的第一难题:异构设备如何协作
我学计算机网络的时候,最强烈的感受是:这门课太“虚”了。操作系统能打开进程列表,数据结构能写出二叉树,到网络这儿,我看不见网线里的电信号,也没法手工把一个数据包举起来观察。
后来我换了个角度才想明白:计算机网络不是一个“设备”的问题,而是一个“协作”的问题。全世界有几十亿台设备,它们的操作系统不一样、硬件厂商不一样、通信速率不一样,凭什么能互相传文件、刷网页、打视频电话?如果让每台设备直接跟其他所有设备约定一套通信方式,工业界早就乱套了。
这就是分层思想的出发点:把一件巨复杂的事,拆成若干件相对独立的小事,每一层只解决一个明确的问题。就像快递寄包裹,你不需要知道货轮怎么开、飞机怎么飞,你只需要把东西交给快递员,填好地址;后面每一段运输都有专人负责,但这整个链条对外看起来是“寄快递”一件事。
网络分层之后,每层都向上一层提供固定能力的服务,同时使用下一层提供的服务。层与层之间只需要认准“接口约定”,至于内部怎么实现,其他层完全不关心。这个思路本质上和软件工程里的模块化、服务化是一回事。
1.2 七层模型与四层模型的现实取舍
我当年背OSI七层模型时相当痛苦:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。背完之后过两天又忘了。直到后来看到TCP/IP四层模型,才明白OSI的意义不是让你按图索骥,而是让你知道早期设计者理想中的网络应该有哪些环节。
两套模型的对应关系是学习时绕不开的坎。我自己整理过一张对照表,现在每次讲网络都会用:
| OSI七层模型 | TCP/IP四层模型 | 核心职责 | 典型协议 |
|---|---|---|---|
| 应用层 | 应用层 | 面向用户提供语义化服务 | HTTP、DNS、FTP |
| 表示层 | (并入应用层) | 数据格式转换、加密、压缩 | TLS其实在这一层说话 |
| 会话层 | (并入应用层) | 建立、管理、终止会话 | 实际工程中很少单独拆分 |
| 传输层 | 传输层 | 端到端可靠或尽力传输 | TCP、UDP |
| 网络层 | 网络层 | 寻址与路由,跨网络转发 | IP、ICMP |
| 数据链路层 | 链路层 | 相邻节点之间的帧传输 | Ethernet、Wi-Fi |
| 物理层 | 链路层 | 0和1的物理信号传输 | 网线、光纤、无线频谱 |
真正在互联网上跑的是TCP/IP模型。OSI里的会话层和表示层,工程上基本被应用层或传输层吸收掉了,比如TLS握手虽然发生在传输层之上,但我们在写代码时通常把它看作是HTTP客户端库的一部分。
学习时我建议用“两头抓中间放”的策略:应用层是离代码最近的,传输层是面试最容易问的,网络层是理解全局的关键,链路层和物理层了解原理就够了。不要把时间平均分配到七层上,否则很容易陷入细节堆砌。
1.3 数据在层间流动:封装与解封装
分层模型不是摆设,数据从一台机器到另一台机器的过程,就是一层层“套壳”和“脱壳”的过程。
发送端:应用层产生数据,例如你提交了一串JSON。传输层加上端口信息,封装成段。网络层加上IP地址,封装成包。链路层加上MAC地址和帧校验,封装成帧。物理层把帧变成比特流发送出去。
接收端反过来:物理层收到比特流,链路层校验帧,网络层取出IP包,传输层根据端口号把数据交给正确的进程,应用层拿到原始数据。
我理解这个过程最有效的类比是套娃寄信:内容写好后,装进信封写上收件人和发件人,然后套进更大的外包装箱,填上地址,再交给快递系统。每一层只看得懂自己那一层写的“面单信息”,不会去拆开里面看内容。
面试里经典的“从浏览器输入网址到页面展示,中间经历了什么”,本质就是考察你能不能把这套封装和解封装的过程走一遍。很多新手答不完整,不是因为不知道协议名,而是脑子里没有“数据逐层下沉、逐层上升”的画面。后面第3章我会结合DNS解析,把这个完整过程再走一遍。
2. TCP的可靠承诺,用什么代价换来的
2.1 端口:从“和谁通信”到“和哪个程序通信”
IP地址解决的是“哪台机器”的问题,但一台机器上同时跑着浏览器、微信、邮件客户端,网络包到了却被认领?这个活儿靠传输层端口号完成。
端口是一个16位的数字,范围0到65535。0到1023是知名端口,比如HTTP用80、HTTPS用443、DNS用53。客户端程序发起连接时,操作系统会给它分配一个随机的临时端口,通常在一万以上。
所以我喜欢把“IP地址+端口号”理解成快递里的“小区门牌号+房间号”。小区对应的是一台主机,房间对应一个进程。没有端口字段,操作系统收到数据只能干瞪眼。
TCP报文头部里,源端口和目的端口各占两个字节,紧跟在开头的序号字段之前。如果你抓过包,会发现前两个字节往往是随机数,那个就是客户端临时端口。
2.2 三次握手与四次挥手:为什么次数这么讲究
TCP是面向连接的协议。所谓“连接”,本质是通信双方各自维护一套状态,并且确认对方也愿意通信。建立连接的标准流程是三次握手:
- 客户端发送SYN报文,并携带一个初始序号x。
- 服务器回复SYN+ACK,确认号是x+1,同时携带自己的初始序号y。
- 客户端再回复ACK,确认号是y+1。
我第一次学的时候想不明白,为什么不能省掉第三步,让服务器发完SYN+ACK就开始传数据?
原因是:TCP面临一个场景——网络里可能残留一个“迟到的旧连接请求”。假设客户端曾经发送过SYN,但迟迟没收到回复,于是超时重发。第一份SYN在网络上绕了一圈,现在才到服务器。服务器如果收到SYN就直接进入已连接状态,它就会为一个根本不存在的连接开资源。等真实请求再来时,服务器资源已经被浪费了。
三次握手让服务器必须在收到客户端对“服务器初始序号”的确认之后,才认为连接真的建立。也就是说,它不是单向确认,而是双向确认:客户端确认了服务器能收到自己的消息,服务器也确认了客户端能收到自己的消息。
断开连接时是四次挥手,很多教材直接让你背FIN、ACK、FIN、ACK四个包。这里的关键在于TCP是全双工的,两个方向的数据传输彼此独立。关闭时,每一方都要单独声明“我这边没有数据要发了”,并且等对方确认。所以最少需要各一次FIN和一次ACK,加起来就是四次。
说个面试常见的追问:为什么客户端在最后一个ACK之后还要等TIME_WAIT?因为最后的ACK可能丢失,服务器会重发FIN,如果没有等待时间,客户端可能已经关闭,重发的FIN到达后找不到对应连接,服务器就会一直处于关闭不完全的状态。TIME_WAIT一般持续两倍的最大报文生存期,确保旧连接的迟到数据包不会污染新连接。
2.3 流量控制与拥塞控制:网络卡顿的根因之一
TCP号称可靠传输,但它不是简单地“发了等确认”就完了。真实网络里还有两个更棘手的问题:接收方处理不过来,以及网络中间设备转发不过来。
接收方处理不过来的问题,靠流量控制解决。TCP报文头里有个窗口字段,接收方在回复ACK时,会顺便告诉发送方“我现在的接收缓冲区还能装下多少字节”。发送方根据这个窗口大小限制自己未确认数据的总量。就好比你往朋友家寄快递,朋友每次只收两箱,你一次寄十箱,剩下的只能在路上或者在快递点等着,甚至被拒收。窗口字段就是“我现在能收几个箱子”的实时通告。
网络中间设备转发不过来的问题,靠拥塞控制解决。拥塞不是接收方能力不足,而是网络本身堵了。TCP的拥塞控制算法分几个阶段:
- 慢启动:从一个小窗口(如10个报文段)开始,每收到一个确认就翻倍,指数增长直到阈值。
- 拥塞避免:超过阈值后改为线性增长,每次往返只增加一个报文段,慢慢试探网络容量。
- 快速重传:如果连续收到三次重复确认,就认为某个报文丢失了,不等超时立刻重传。
我实习时遇到过一个问题:某服务到客户端的文件传输偶尔特别慢,抓包发现大量TCP重传,现象上是消息确认迟迟不来。后来定位到是跨运营商链路拥塞,加上服务端默认的TCP参数是面向低延迟场景调的,慢启动阈值偏保守,大文件传输一直被卡在拥塞避免阶段。那时候我才真正体会到拥塞控制不是课本概念,它直接影响业务体验。
2.4 UDP什么时候比TCP更合适
每次讲完TCP,必然绕不开UDP。两者最直观的差异可以用一张表说清楚:
| 对比维度 | TCP | UDP |
|---|---|---|
| 连接方式 | 面向连接 | 无连接 |
| 可靠性 | 可靠、按序、去重 | 尽力而为,可能丢包乱序 |
| 传输效率 | 头部20字节起,握手成本高 | 头部8字节,零握手 |
| 流量与拥塞控制 | 有 | 无 |
| 适用场景 | 文件传输、网页、邮件 | 音视频、DNS、游戏、实时控制 |
UDP被嫌弃的不可靠,在某些场景下恰恰是优点。视频通话里丢一两帧画面,用户感知不到,但用TCP重传反而会拖慢实时性,导致画面卡顿。游戏里玩家操作指令也走UDP,因为旧指令还没重传完,新指令已经产生了,重传旧数据没有意义。
我在学习时有个体会:不要默认“遇事不决用TCP”。实时性优先且少量丢包可接受的场景,UDP是更合理的选择。甚至会有人直接在UDP上面实现自己的可靠层,比如把流量控制、重传逻辑定制成适合特定业务的版本。这属于典型的“牺牲通用性换极端性能”。
3. 寻址与解析:IP、路由和DNS如何把人找出来
3.1 IP地址与MAC地址:两套标识各管一段
一台设备在网络里至少有两套标识。IP地址是逻辑地址,负责跨网络定位;MAC地址是物理地址,负责在一个局域网内定位网卡。这两者的关系,有点像你的收货地址和你的身份证号。地址可以随搬家改变,是路由的依据;身份证号一辈子不变,只在特定环节核对身份。
IPv4地址是32位,通常写成四段十进制数。早期的地址分类体系(A、B、C类)已经基本被CIDR无类别域间路由取代。现在你看到192.168.1.0/24这种写法,意思是前24位是网络号,后8位是主机号。
我刚开始学子网掩码时很烦躁,后来找到了一个最实用的理解方式:子网掩码就是告诉你“这个网段里有多少台机器可以直接在局域网里通信”。比如/24代表最多有254个可用主机地址(去掉网络地址和广播地址),/16就是65534个。这个理解在日常排查网络问题时比背公式管用得多。
MAC地址是48位的,出厂烧录在网卡里。但注意,MAC地址只在同一链路内生效。一个数据包从北京传到上海,它在每个局域网段里的MAC帧都会被重新封装。所以你不能把MAC地址当成设备的全球唯一身份,而应该当成“当前这一段路的门牌”。
3.2 私网地址与NAT:IPv4不够用的现实解法
IPv4地址总数约43亿,早就分配完了。现实世界是怎么撑下来的?靠私有地址加NAT。
私有地址段有几块固定的,比如192.168.0.0/16、10.0.0.0/8、172.16.0.0/12。内网设备分配私有地址,出口路由器把内网地址转换成公网地址。家里那台路由器就是一台NAT设备:多个手机和电脑只共用一个公网IP,路由器通过端口号区分是哪个设备的流量。
NAT严重拖延了IPv4的耗尽,但也带来了一个副产品:内网设备默认不对外暴露。这从安全角度讲反而是好事,很多家庭设备不直接被公网扫描到。
IPv6把地址扩到128位,设计初衷是让每台设备都有全球唯一地址,彻底摆脱NAT。但因为演进周期长,很多网络仍是IPv4和IPv6双栈并存。学习阶段你只需要理解IPv6的地址格式、地址类型和过渡机制的基本思想,真正在业务里大面积配置的场景还不多。
3.3 路由转发:数据包逐跳前进的接力赛
有了IP地址,数据包怎么从源主机到目标主机?答案是逐跳转发。每一台路由器只知道自己直连的网段以及下一跳该把包送给谁,并不需要知道整条路径。
教科书里会讲路由协议,比如RIP、OSPF、BGP,做运维的同学需要深入学。但大部分人首先要建立的是“路由表”和“默认网关”的概念。
路由表本质是一套规则:目标网段匹配到哪个条目,就送到对应的接口或下一跳IP。如果所有条目都不匹配,就走默认路由,通常指向运营商网关。家里的电脑发出的非局域网流量,先送到路由器,路由器再送到运营商,一层一层接力到目标服务器。
有个小细节值得记住:数据包在每一跳转发时,源IP和目的IP始终不变,但MAC帧的源地址和目的地址每经过一个链路都会被改写。这也是为什么抓包时你看到的是路由器(网关)的MAC,而不是远方服务器的MAC。
3.4 输入网址到页面返回:一次完整的寻址旅程
把前面的概念串起来,就是“浏览器输入https://example.com后发生了什么”的完整版本:
- 浏览器先查本地DNS缓存。没命中,就向配置的DNS递归解析器发出域名查询请求。这个请求往往是UDP到53端口。
- 递归解析器如果也没有缓存,就一路问下去:根域名服务器告知顶级域服务器地址,顶级域名服务器告知权威服务器地址,权威服务器返回
example.com对应的IP。这个过程叫递归+迭代查询。 - 浏览器拿到IP后,发起TCP连接,也就是第2章的三次握手。
- 应用层构造HTTP请求,传输层封装TCP报文并附上源端口和443端口,网络层封装IP包并附上源IP和目标IP。
- 链路层通过ARP获得默认网关的MAC地址,封装成帧发送。路由器逐跳转发,最终到达目标服务器。
- 服务器响应后,数据沿着相同路径返回,浏览器解析渲染页面,然后关闭连接或复用连接。
这条路看着很长,实际往往在几十毫秒内完成。每次排查网络问题时,我就按照这条链路逐段测:先看域名解析是否正常,再看TCP建连是否正常,最后看HTTP响应是否正常。能定位到层,就能缩小问题范围。
4. HTTP到HTTPS:从明文信任到加密握手
4.1 HTTP的基本盘:方法、状态码与无状态会话
HTTP是应用层最重要的协议,前端后端每天都打交道。我整理它的核心只需要四部分:请求方法、状态码、请求头、响应体。
常用的方法就那么几个:GET取资源,POST提交数据,PUT覆盖资源,DELETE删资源。早期开发者习惯用GET带参数查询,但涉及修改数据的操作,标准做法是用POST或PUT,避免敏感信息出现在请求行里,同时也防止被浏览器历史或代理日志记录。
状态码是快速判断问题的一类信息。1xx表示继续,2xx表示成功,3xx表示重定向,4xx是客户端出错,5xx是服务端出错。我最常查的几个:301永久重定向、302临时重定向、401未认证、403无权限、404资源不存在、500服务端内部错误、502网关错误、503服务不可用、504网关超时。排查线上问题时,先看响应码基本能决定下一步往哪里查。
另外一句话记住HTTP的本质:它是无状态协议。服务器默认不记得你上一次请求是谁。为了解决“记住登录状态”的需求,才有了Cookie和Session这套方案。服务器在响应头里下发Set-Cookie,客户端后续请求自动带上Cookie;服务器根据Cookie里的会话ID,从Session存储中恢复用户状态。我第一次写登录功能时,就是没搞清这两者的关系,结果把用户信息直接塞进Cookie,导致可以伪造身份。后来才明白,Cookie只是凭证,真正的状态信息应该存在服务器端。
4.2 明文带来的风险:抓包视角看HTTP
纯HTTP风险中最要命的一点是:所有内容明文传输。在一个共享网络里,任何能截获数据包的人都能直接读到你的请求内容。
我做过一次演示:开一个HTTP测试站点,输入用户名密码,然后抓包看数据。明文里清清楚楚能看到username=admin&password=123456。如果这套流程发生在登录接口,密码相当于裸奔。
除窃听之外,明文传输还给了中间人篡改的机会。攻击者可以在网关位置改掉响应内容,往返回的HTML里注入脚本。用户看到页面明明是从自己的会话请求来的,实际上内容已经被替换过。
这些风险引出了网络安全的第一个底层要求:传输数据需要机密性(不能被偷看)和完整性(不能被篡改)。单靠应用层自己写加密逻辑不够,因为每个开发者自己实现的加密,正确性很难保障。行业需要一套标准化的安全传输协议,于是HTTPS成了默认选择。
4.3 HTTPS与TLS握手:证书、密钥协商与加密信道
HTTPS不是一种新协议,它是HTTP跑在TLS之上。TLS的核心目标是给通信双方建立一条加密信道,同时保证彼此身份可信。
这里要理解一个密码学难题:对称加密(比如AES)效率高,但双方需要共享同一个密钥;这个密钥怎么在不安全的网络里安全交付?如果直接传密钥,攻击者截获后所有加密都白搭。
解决思路是非对称加密:一对密钥里,公钥可以公开分发,私钥自己保存。用公钥加密的数据,只有私钥能解开;反之,用私钥签名、公钥验证。但非对称加密性能远不如对称加密,所以TLS的实际策略是“混合加密”:用非对称算法安全协商出一个临时的对称会话密钥,然后用会话密钥加密后续所有数据。
简化版的握手过程是:
- 客户端发送ClientHello,包含支持的TLS版本和密码套件列表,以及一个客户端随机数。
- 服务器回复ServerHello,选定TLS版本和密码套件,附带服务器随机数,并下发自己的证书。
- 客户端验证证书是否可信,确认服务器确实是域名对应的主体。
- 客户端生成预备主密钥,用服务器公钥加密发送过去。
- 双方基于两个随机数和预备主密钥,各自动派生相同的会话密钥。
- 之后所有HTTP数据都用会话密钥对称加密传输。
这个过程里最值得花时间理解的是第3步:证书验证。如果证书无效,后面的密钥协商其实就是在跟不明身份的人说话,加密也没意义。
4.4 证书链与信任模型:理解“锁标志”背后的含义
TLS证书是“域名和公钥绑定”的凭证。服务器证书里包含域名、公钥、有效期、签发者等信息。这个证书不能是自说自话,得由可信的第三方签发。
这里引出CA(证书颁发机构)体系。用户设备和操作系统里内置了一批受信任的根证书。服务器提供的证书,由某个CA的私钥签名,而这个CA的证书又可能由更上层的根CA签名,形成一条证书链。浏览器验证时,沿着证书链往上找到受信任的根证书,再用根公钥逐级验证签名,最终确认服务器的证书是真的。
我遇到过同事问:为什么公司内网自建的证书,浏览器总报不安全?原因就是浏览器里没有信任该自建CA的根证书。解决办法是在内网设备上手动安装根证书,并把这个根证书标记为受信任。但这里有个安全禁忌:绝不要在生产设备上随便安装来路不明的根证书,一旦装了,攻击者持有对应私钥时就能伪造任意网站。
日常开发时,本地调试用自签名证书没问题,但不要让浏览器永久信任它。测试完就该移除。我自己习惯把本地证书加入“仅供当前项目”的信任范围,而不是全局信任。
5. 计算机安全:从威胁模型到可落地的防御习惯
5.1 安全的三个基础目标与威胁分类
先记三个字母:CIA。这不是中央情报局,是安全的三大目标——机密性(Confidentiality)、完整性(Integrity)、可用性(Availability)。机密性保证数据不泄露给未授权的人,完整性保证数据不被篡改,可用性保证服务和数据在需要时可访问。
所有攻击行为都可以从“破坏了哪个目标”来分类:
- 想要机密性失效:网络嗅探、数据窃取、撞库。
- 想要完整性失效:中间人篡改、SQL注入改数据、文件内容替换。
- 想要可用性失效:DDoS攻击打满带宽或CPU、勒索软件加密磁盘、删库。
我最早学安全时总是记不住各种攻击名,后来发现更高效的方式是按攻击发生的阶段归类:侦察(扫描端口、探测指纹)、入侵(漏洞利用、弱口令爆破、钓鱼)、持久化(留后门、创建隐藏账号)、横向移动(从一台机器跳到内网其他机器)、影响(加密数据、删库、对外发出)。
按照阶段去理解,防御时就能做到“每条链路都有对应的检测手段”,而不是孤立地封堵某一个攻击。
5.2 攻击面思维:系统暴露越多越危险
防御安全最怕思维误区是“我加了密码就安全了”。真正的安全工程师会先问:我的攻击面有多大?
攻击面指所有可以被未授权用户接触到的入口。常见入口包括:对外暴露的端口、开放的API、可上传文件的接口、登录页面、甚至是电脑上插着的USB口。攻击面越大,被找到弱点的概率越高。
我自己做过一次服务巡检时发现,一个内部服务为了图方便,监听在0.0.0.0:8080上,结果这个端口直接被外网扫描到,再配合一个老版本框架的漏洞,差点被打穿。后来把监听地址改成127.0.0.1,攻击面立刻缩小了一大截。
减少攻击面有可操作的做法:只暴露必要的端口;服务监听地址绑定到内网或回环地址,而不是所有网卡;关闭不必要的管理接口;对外API要限流、认证、校验输入;开发环境和生产环境彻底隔离。
理解攻击面之后,你会发现很多所谓的“安全漏洞”其实不是黑客用多么高深的技术,而是系统自己把门敞开了。
5.3 纵深防御与最小权限:不把鸡蛋放在一个篮子里
纵深防御的思想是:即使某一层防守被突破,后面还有更多层兜住,不让攻击者轻松直达核心。就像银行金库,外有围墙、中有安保、内有保险柜,而不是只靠一扇门。
落到具体工程:
- 网络层:防火墙放行最小规则、划分VLAN隔离网段。
- 主机层:及时打补丁、配置主机防火墙、开启日志审计。
- 应用层:身份认证、参数校验、防止SQL注入和XSS。
- 数据层:数据库账号独立、备份加密、最小权限表结构。
- 备份与恢复:定期验证备份可还原,这是最后一道防线。
最小权限原则和纵深防御是一对组合拳。给服务账户只分配它需要的权限,给员工只开他业务需要的访问权。权限越小,被攻破一个账号后的爆炸半径就越小。
我见过一个典型案例:某开发同学拥有云平台全部权限,他的笔记本被钓鱼攻击拿下,攻击者跟着他的API密钥把整个环境配置都拖走了。如果当初只给他项目级权限,即使笔记本沦陷,影响范围也能可控。安全设防时,一定要思考“如果一个环节被突破,损失会被限制在多大范围”。
5.4 普通人也能执行的安全基线
不少人觉得安全是运维和安全工程师的事,其实大多数攻击是从人入手的。我给自己和家人定的安全基线只有几条,但落实之后效果很明显:
- 密码:不用同一个密码跨多个网站;优先使用密码管理器生成并保存随机密码。
- 双因素认证:重要账号打开MFA,短信验证码可用但首选认证器应用。
- 补丁:系统和软件开启自动更新,不要长期停留在不维护的版本。
- 备份:重要数据按3-2-1原则备份,至少一份在异地或离线存储。
- 识别钓鱼:看到“账号异常”“逾期通知”“中奖信息”,先核对网址和发件人,不随便点附件。
这些事一点不玄乎,但缺一条可能全盘皆输。学安全的意义,不是让你成为一个渗透攻击专家,而是建立“每个环节都可能被攻击”的意识,然后让系统即使被攻击,损失也可控。
6. 我自己怎么把这份笔记用起来的
最后一次复习计算机网络时,我给自己定了三个“不看笔记也能讲清楚”的挑战:
第一,画出一张完整的分层模型图,标注每层在“浏览器输入网址到页面展示”这条链路里具体做了什么。第二,解释三次握手为什么必须三次、四次挥手为什么必须四次,并且能用生活例子说服一个完全不学技术的人。第三,说清楚HTTPS的握手过程中,非对称加密和对称加密分别解决什么问题,证书在这个链路里起到什么作用。
这三个挑战全部能顺利讲完,才说明我不是在背概念,而是真的理解了网络的设计逻辑。
安全部分我个人最大的收获,不是记住威胁列表,而是养成了一个习惯:每次设计系统或写接口时,都会下意识问一句“如果这个接口被恶意调用,最坏会发生什么”。答案往往就是需要补防护的地方。你也试着拿自己的项目走一遍这个提问,比看完十篇安全文章都有用。
这份速成课笔记只是起点。网络与安全像一张大网,越深入越会发现每个协议、每个安全机制背后都有一整套工程权衡。保持“求甚解”的心态,当你能用自己的话把那几条链路讲明白时,这门课就算真正入门了。