众所周知,在传输层有两大最核心,最常用的协议,他们是TCP与UDP,今天小颜就带大家认识,并了解他们传输数据的原理。
TCP
TCP(Transmission Control Protocol,传输控制协议),是互联网核心传输层协议,属于面向连接、可靠、字节流、全双工的协议,也是日常上网最常用协议(HTTP/HTTPS、FTP、SSH 等底层都基于 TCP)。
什么叫面向连接,意思是通信前必须先建立专属连接(三次握手),传输结束后正常断开(四次挥手),不像 UDP 直接发数据包。
诶呀,那问题又来了,什么叫三次握手,什么叫四次挥手呀
三次握手(建立连接)
客户端 → 服务端:SYN(我要吃黄焖鸡)
服务端 → 客户端:SYN+ACK(好吧,你真的要吃黄焖鸡吗)
客户端 → 服务端:ACK(没错,我真的要吃黄焖鸡)一直到这一步,才算真正建立了连接
2. 四次挥手(断开连接)
主动方发 FIN:我吃饱了
被动方回 ACK:行(还能继续发数据)
被动方发 FIN:真的吃饱了吗?
主动方回 ACK:真的吃饱了(彻底断开连接)
好,现在我们了解了TCP协议建立连接和断开连接的过程,和会发送的数据包
建立连接:SYN → SYN+ACK → ACK
断开连接:FIN+ACK → ACK → FIN+ACK → ACK
那么好,咱们多的不说少的不唠直接实操好吧,这里小颜会用python代码,编译环境是pycharm,来给大家做演示
首先准备我们的python代码,放到自己的主机上
# 导入Python标准socket库,主要用于网络相关操作(这里主要是辅助,核心发包用scapy) import socket # 从scapy库导入IP和TCP层构造模块,用于手动构造IP报文和TCP报文 from scapy.layers.inet import IP, TCP # 导入scapy的发送并接收响应模块 sr1:发送1个包,等待1个响应 from scapy.sendrecv import sr1 # 导入随机数模块,用于生成随机端口和随机序列号 import random def sdtcp_c(): # 1. 随机生成客户端源端口(12000~30000),避免端口冲突 port = random.randint(12000, 30000) # 2. 随机生成TCP初始序列号ISN(三次握手第一步需要) seq = random.randint(10000, 20000) # ===================== 第一次握手:发送 SYN 包 ===================== # 构造IP+TCP的SYN包:目标IP,目标端口,标志位S=SYN pkg_1 = IP(dst="这里写你建立连接目标的IPV4地址") / TCP(sport=port, dport=这里写目标端口,因为是实验环境可以随便写一个, flags="S", seq=seq) # 发送SYN包,等待服务器返回 SYN+ACK 包,超时3秒,不打印冗余日志 reply = sr1(pkg_1, timeout=3, verbose=False) # ===================== 处理服务器返回的 SYN+ACK 包 ===================== # 更新自己的序列号为:服务器返回的 ack 值(确认号) seq = reply[TCP].ack # 构造要发送给服务器的确认号:服务器的序列号 + 1 ack = reply[TCP].seq + 1 # ===================== 第三次握手:发送 ACK 包,完成连接建立 ===================== # 构造纯ACK包,标志位A=ACK,完成三次握手 pkg_2 = IP(dst="192.168.166.128") / TCP(sport=port, dport=65432, flags="A", seq=seq, ack=ack) # 发送ACK包,建立TCP连接 sr1(pkg_2, timeout=3, verbose=False) # ===================== 发送数据:PSH+ACK 包 ===================== # 构造带数据的TCP包:标志位PA=PSH+ACK,携带数据"hello" # PSH:告诉服务器立即将数据交给应用层,不要缓存 pkg_3 = IP(dst="192.168.166.128") / TCP(sport=port, dport=65432, flags="PA", seq=seq, ack=ack) / "hello" # 发送数据报文 sr1(pkg_3, timeout=3, verbose=False) # 主函数入口:程序从这里开始执行 if __name__ == '__main__': # 调用自定义函数,执行TCP三次握手+发送数据 sdtcp_c()
如图所示,就像这样放到主机的编译器上就可以了

那我们现在面临一个问题,大家都知道,TCP是用于传输数据的协议,那现在请求方(上面的代码就是模拟请求数据的客户端)已经准备好了,那我们该向谁去建立连接,请求数据呢,如果是正常的通信过程,会有专门的服务器来给我们提供服务,但是现在我们只能再写一个服务端的代码来模拟服务器给我们提供服务,如下
# 导入Python内置的socket库,用于创建标准的TCP/UDP网络连接
import socket
# 创建一个TCP socket对象,with语句会自动关闭socket,无需手动释放资源
with socket.socket() as s:
# 绑定本机地址和端口:localhost=本机,65432=监听端口
s.bind(("localhost", 65432))
# 开启监听状态,等待客户端连接
s.listen()
# 阻塞等待客户端连接
# conn:新的连接套接字(专门用来和这个客户端收发数据)
# addr:客户端的IP和端口
conn, addr = s.accept()
# with语句自动管理连接,断开时自动关闭
with conn:
# 打印客户端连接信息
print(f"connection by {addr}")
# 循环不断接收客户端发来的数据
while True:
# 最多接收1024字节数据,阻塞等待数据到来
data = conn.recv(1024)
# 如果data为空,说明客户端断开连接,退出循环
if not data:
break
# 打印接收到的数据(解码成字符串)
print(f"recv:{data.decode()}")
# 给客户端回复消息:hello client
conn.sendall(b"hello client")由于TCP连接是需要知道目标IP地址的,所以我们上面这个代码最好是放到和你主机IP不一样的计算机上,这时候需要我们的好朋友虚拟机出马,把这个代码打包放到虚拟机上,那现在这个虚拟机是不是相当于给我们提供服务的服务器啦,如图

图中那个叫sockttcp的文件就是我们上面的代码(记得看看你自己虚拟机的ip地址,填到上面写的客户端的代码里面哦)直接双击运行,并同时运行你主机上的客户端代码,那我们该怎么知道正常建立了连接呢,当然是用抓包软件啦,这里小颜用的是wireshark,选择你虚拟机的虚拟网卡,就我选中的这个

点开看看,可以发现,已经抓到了,source是来源,是你主机的IP,destination是目标ip也就是你的虚拟机ip

过滤一下可以看到完整的三次握手数据包,在上面过滤的地方写 tcp.port == 你客户端发送的端口号,意思是只看tcp协议,并且只看在目标端口的数据包

那么好,TCP的讲解学习到这里,就可以不用深入了,没什么必要,主要用于演示,可能有些同学看不懂代码,没关系python是一个很好的工具,你在关键时刻知道复制什么到哪里,根据注释去理解每行代码的意思,就可以了,当然越熟悉肯定越好。
UDP
UDP(User Datagram Protocol),用户数据报协议,工作在传输层,和 TCP 并列,是互联网两大核心传输协议。
那TCP和UDP有什么区别吗,有而且很大
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 (三次握手) | 无连接 |
| 可靠性 | 可靠 (不丢、不乱序) | 不可靠 (易丢包) |
| 速度 | 慢 | 极快、低延迟 |
| 控制机制 | 流量控制、拥塞控制 | 无 |
| 报文模式 | 字节流(粘包) | 数据报(无粘包) |
| 头部开销 | 大 | 极小 |
TCP因为有三次握手去进行连接所以可靠,UDP就没有这么麻烦,就像上面表的,无连接,意思是我管你有没有收到,反正给你了,正因如此,他才没有 TCP那么可靠,但是TCP这样每次连接还要握手什么的,就导致他传输数据的速度慢,而UDP直接发包,他的传输速度肯定比TCP快,所以他们两个针对不同的需求,选择不同的协议,应对不同的工作环境也能游刃有余。
举个例子
要数据完整不差错:用 TCP(网页、文件、登录)
要实时不卡顿:用 UDP(游戏、直播、语音)
那和上面一样,小颜也为同学们准备了python编写的UDP客户端代码和服务端代码,使用和操作和上面TCP一模一样,感兴趣的同学可以实验一下,抓包看看
客户端:
# 导入网络套接字库
import socket
# -------------------------- UDP服务端 --------------------------
def udp_server():
# AF_INET : IPv4 网络协议
# SOCK_DGRAM : UDP 数据报模式(核心标识UDP)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定本机IP和监听端口
host = "127.0.0.1"
port = 65432
server_socket.bind((host, port))
print(f"UDP服务端已启动,监听端口:{port}")
# UDP无连接,不需要listen()、accept()
while True:
# recvfrom:接收UDP数据,同时获取发送方地址
# 1024:单次最大接收字节数
data, client_addr = server_socket.recvfrom(1024)
# 解码并打印客户端消息
recv_msg = data.decode("utf-8")
print(f"来自客户端{client_addr}:{recv_msg}")
# 回复数据给客户端
send_msg = "收到,我是UDP服务端"
server_socket.sendto(send_msg.encode("utf-8"), client_addr)
if __name__ == "__main__":
udp_server()服务端:
# 导入网络套接字库
import socket
# -------------------------- UDP客户端 --------------------------
def udp_client():
# 创建UDP套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 服务端地址信息
server_host = "127.0.0.1"
server_port = 65432
# UDP特点:无连接,不需要connect、不需要握手
# 直接通过 sendto 发送数据
msg = "Hello UDP"
client_socket.sendto(msg.encode("utf-8"), (server_host, server_port))
# 接收服务端的回复
data, server_addr = client_socket.recvfrom(1024)
print(f"服务端回复:{data.decode('utf-8')}")
# 关闭套接字
client_socket.close()
if __name__ == "__main__":
udp_client()好的,那咱们今天的学习就到此为止吧,感谢大家的耐心观看,有什么问题和想法,欢迎大家在评论区留言讨论。
小颜后续会针对评论区一些关键的,有建设性的问题,专门写一篇回复文章哦。
