一、Socket基础:网络分层模型、Socket是什么、TCP与UDP的区别、Socket通信流程

各位同学,咱们今天正式开讲。做网络编程这么多年,我见过太多人一上来就撸Socket代码,结果连数据是怎么从A机器跑到B机器的都说不清楚。说实话,这样写出来的程序,线上不出问题才怪。

所以第一章,咱们先把地基打牢。你想想看,盖楼不打地基,早晚得塌。网络编程也一样。

1.1 网络分层模型:数据是怎么「飞」过去的?

先问大家一个问题:你在浏览器里输入一个网址,回车之后,数据到底经历了什么?

答案就在网络分层模型里。目前主流的有两套:OSI七层模型和TCP/IP四层模型。OSI是理论上的「完美标准」,但实际互联网用的是TCP/IP模型。我个人的习惯是,把两者结合起来理解,但写代码时只认TCP/IP。

OSI七层模型 TCP/IP四层模型 典型协议/设备 一句话理解
应用层 应用层 HTTP, FTP, DNS 你直接打交道的东西
表示层 SSL/TLS, JPEG 数据格式翻译、加密
会话层 NetBIOS, RPC 建立/管理会话
传输层 传输层 TCP, UDP 端到端传输,核心战场
网络层 网络层 IP, ICMP, 路由器 寻址和路由选择
数据链路层 网络接口层 以太网, MAC地址, 交换机 相邻节点传输
物理层 网线, 光纤, 集线器 比特流在介质上传输
我的经验: 做Socket编程,你只需要盯死传输层和应用层。网络层及以下,操作系统和网卡帮你搞定了。但如果你连IP和端口的概念都模糊,那后面调试起来会非常痛苦。

数据发送时,是从上往下层层「打包」——每一层加上自己的头部信息。接收时,从下往上层层「拆包」。这个过程叫封装与解封装。

核心概念: 每一层只关心自己的职责,下层为上层提供服务。这就是「分层」的精髓——各司其职,解耦。

下面这张图,是我自己画的数据封装流程,你一看就明白。

数据封装与解封装流程 发送端 应用层数据 (Data) TCP/UDP 头部 + Data IP 头部 + TCP/UDP 头部 + Data 以太网头部 + IP头部 + ... + 尾部 网络传输介质 接收端 以太网头部 + IP头部 + ... + 尾部 IP 头部 + TCP/UDP 头部 + Data TCP/UDP 头部 + Data 应用层数据 (Data) 发送端逐层「加头」封装 → 接收端逐层「去头」解封装

1.2 Socket到底是什么?别把它想得太玄乎

很多初学者觉得Socket是个很高深的东西。其实说白了,Socket就是操作系统提供的一个编程接口,让你能通过文件描述符的方式,操作网络数据收发。

嗯,这里要注意:Socket本身不是协议,它只是对TCP/IP协议栈的一层封装。你调用的send()、recv(),底层走的还是TCP或UDP。

一句话总结: Socket = IP地址 + 端口号 + 协议类型。它是网络通信的「门把手」,你握住它,就能跟另一台机器上的程序对话。

我记得刚入行那会儿,有个同事把Socket理解成「一条管道」,总觉得数据发出去就一定能到。后来线上出了丢包问题,他才明白Socket只是接口,可靠性得靠协议本身保证。

1.3 TCP与UDP的区别:选错协议,线上哭都来不及

这是面试高频题,也是实际选型最容易踩坑的地方。我直接给你上对比表,一目了然。

对比维度 TCP UDP
连接状态 面向连接(先握手,再通信) 无连接(直接发,不打招呼)
可靠性 可靠传输(有确认、重传机制) 不可靠(发出去就不管了)
数据顺序 保证数据按序到达 不保证顺序
传输方式 字节流(无边界) 数据报(有边界,一次一个报文)
速度 相对较慢(有开销) 快(无握手、无确认)
典型应用 HTTP, FTP, SSH, 数据库连接 DNS, 视频直播, 在线游戏, VoIP
我曾经踩过的坑: 做一个实时日志采集系统,为了「省事」用了UDP。结果高峰期丢包率超过10%,日志不全,排查问题排查到崩溃。后来老老实实改成TCP,虽然慢一点,但数据一条不丢。选协议之前,先问自己:我能接受丢数据吗?

为什么会这样?TCP有三次握手、四次挥手、滑动窗口、拥塞控制……这些机制保证了可靠性,但也带来了延迟。UDP呢?什么都不管,闷头就发,所以快,但数据可能半路就没了。

你想想看,视频通话丢一帧画面,你眨个眼就过去了。但银行转账丢一条记录,那可就出大事了。这就是选型的关键。

1.4 Socket通信流程:从创建到关闭,一个完整的生命周期

这部分是写代码的基础。我习惯把Socket通信分成三个角色:服务端、客户端、以及它们之间的「对话」。

先看服务端,它是个「被动方」,一直等着别人来找它。

  1. 创建Socket:socket(),指定协议族和类型。
  2. 绑定地址和端口:bind(),把Socket和本地的IP、端口绑在一起。
  3. 监听:listen(),告诉内核:「我准备好了,有人连我就通知你」。
  4. 接受连接:accept(),阻塞等待,直到有客户端连上来。
  5. 收发数据:recv() / send(),开始通信。
  6. 关闭连接:close(),释放资源。

客户端就简单多了,它是「主动方」。

  1. 创建Socket:socket()。
  2. 连接服务端:connect(),指定服务端的IP和端口。
  3. 收发数据:send() / recv()。
  4. 关闭连接:close()。

下面这张流程图,把整个过程串起来了。

TCP Socket 通信流程 服务端 socket() 创建 bind() 绑定地址 listen() 监听 accept() 阻塞等待 客户端 socket() 创建 connect() 连接 三次握手 连接建立 连接建立 数据收发 (recv/send) close() 关闭 close() 关闭 四次挥手 服务端先启动,客户端后连接;通信结束后,双方各自关闭

下面是一个最简的Python示例,演示了TCP服务端和客户端的基本骨架。

# tcp_server.py - 服务端
import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('0.0.0.0', 8888))
server_socket.listen(5)
print('服务端已启动,等待连接...')

client_socket, client_addr = server_socket.accept()
print(f'客户端 {client_addr} 已连接')

data = client_socket.recv(1024)
print(f'收到: {data.decode()}')

client_socket.send(b'Hello, Client!')
client_socket.close()
server_socket.close()
# tcp_client.py - 客户端
import socket

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('127.0.0.1', 8888))

client_socket.send(b'Hello, Server!')
data = client_socket.recv(1024)
print(f'收到: {data.decode()}')

client_socket.close()
我建议: 初学者先把这个流程背下来,然后手敲一遍代码。别复制粘贴,一个字一个字打。打完之后,你自然就理解了每一步在干什么。这是最笨的方法,也是最有效的方法。

好了,第一章的内容就到这里。网络分层模型让你知道数据怎么走,Socket让你知道怎么操作,TCP/UDP让你知道怎么选,通信流程让你知道怎么写代码。这四块拼在一起,就是Socket编程的完整地基。