【二十七】Python全栈之路--TCP_UDP_黏包

本文详细介绍了Python中TCP和UDP协议下的socket使用,包括基本语法和循环发送消息的实现。同时,针对TCP协议的黏包现象,提出了临时性和永久性的解决方案,并利用struct模块进行了优化。通过实例展示了如何避免数据混淆,确保网络通信的准确性。
摘要由CSDN通过智能技术生成

1. tcp协议下的socket使用

1.1 tcp基本语法

server.py

# ### socket 服务端
"""
一发一收是一对,不匹配会导致数据异常
send 发送 recv 接受
"""
import socket

# 1.创建一个socket对象
sk = socket.socket()

# 一个端口绑定多个程序(仅在测试时使用)
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

# 2.在网络中注册该主机(绑定对应的ip和端口号)
""" 默认本地ip : 127.0.0.1  => localhost """
sk.bind(  ("127.0.0.1" , 9000) )
# 3.开启监听
sk.listen()

# 4.三次握手
conn,addr = sk.accept()

# 5.收发数据的逻辑

# 接受数据
"""一次最多接受1024个字节"""
res = conn.recv(1024)
print(res)
print(res.decode())

# 发送数据
conn.send("好好学习,天天向上".encode())


# 6.四次挥手
conn.close()

# 7.退还端口
sk.close()

client.py:

# ### TCP协议 客户端
import socket
# 1.创建一个socket对象
sk = socket.socket()

# 2.与服务端建立连接
sk.connect( ("127.0.0.1" , 9000) )

# 3.收发数据的逻辑
"""发送的数据类型是二进制字节流"""
"""b开头的字符串是二进制字节流格式,要求字符类型必须是ascii编码"""
sk.send("今天我们学习网络编程".encode()) 

# 接受数据
res = sk.recv(1024)
print(res.decode())
# 4.关闭连接
sk.close()

1.2 tcp循环发消息

server.py:

# ### 服务端
import socket

# (1) 创建socket对象
sk = socket.socket()

# 一个端口绑定多个程序(仅在测试时使用)
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

# (2) 在网络中注册该主机(绑定ip和端口号)
sk.bind(  ("127.0.0.1" , 9001)  )
# (3) 监听端口
sk.listen()
# (4) 三次握手
# conn,addr = sk.accept()
# (5) 收发数据的逻辑

"""
print(conn)
print(addr)
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9000), raddr=('127.0.0.1', 50176)>
('127.0.0.1', 50176)
"""

while True:
	conn,addr = sk.accept()
	while True:
		# 接受数据
		res = conn.recv(1024)
		print(res.decode())
		
		# 发送数据
		strvar = input("[服务端]请输入您要发送的数据>>>")
		conn.send(strvar.encode())
		
		# 退出
		if strvar == "q":
			break


	# (6) 四次挥手
	conn.close()
	
# (7) 退还端口
sk.close() 

client.py

# ### 客户端
import socket

# (1) 创建socket对象
sk = socket.socket()
# (2) 连接服务端
sk.connect( ("127.0.0.1" , 9001) )
# (3) 收发数据的逻辑

while True:
	# 发送数据
	strvar = input("[客户端]请输入您要发送的数据>>>")
	sk.send(strvar.encode())
	
	# 接受数据
	res = sk.recv(1024)
	if res == b"q":
		break
	print(res.decode())
# (4) 关闭连接
sk.close()

2. udp协议下的socket使用

2.1 udp基本语法

server.py

# ### UDP协议 服务端
import socket 

# 1.创建udp对象
sk = socket.socket(type=socket.SOCK_DGRAM)

# 2.在网络中注册该主机(绑定ip和端口号)
sk.bind( ("127.0.0.1",9000) )

# 3.收发数据的逻辑
"""udp协议下,默认第一次只能接收数据(没有三次握手,不清楚对方的ip和端口号)"""
# 接受数据
msg , addr  = sk.recvfrom(1024)
print(msg.decode())
print(addr)

# 发送数据
sk.sendto( "我喜欢你个锤子".encode()  , addr )

# 4.关闭连接
sk.close()

client.py

# ### UDP协议 客户端
import socket 

# 1.创建udp对象
sk = socket.socket(type=socket.SOCK_DGRAM)

# 2.收发数据的逻辑
# 发送数据
msg = "你喜欢我么~"
# sendto(  二进制字节流 , ip端口号  )
sk.sendto(   msg.encode() ,  ("127.0.0.1",9000) )

# 接受数据
msg , addr = sk.recvfrom(1024)
print(msg.decode())
print(addr)

# 3.关闭连接
sk.close()

2.2 udp循环发消息

server.py

# ### udp 服务端
import socket 

sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind( ("127.0.0.1",9000) )

while True:
	# 接受数据
	msg , addr = sk.recvfrom(1024)
	print(msg.decode())
	print(addr)
	
	# 发送数据
	strvar = input("[服务端]请输入您要发送的内容>>>")
	sk.sendto( strvar.encode() ,  addr)
sk.close()

client.py

# ### udp 客户端
import socket

sk = socket.socket(type=socket.SOCK_DGRAM)

while True:
	# 发送数据
	strvar = input("[客户端]请输入您要发送的内容>>>")
	sk.sendto( strvar.encode() , ("127.0.0.1",9000) )
	
	# 接受数据
	msg , addr = sk.recvfrom(1024)
	print(msg.decode())
	
sk.close()

3. 黏包

3.1 黏包的现象

server.py

# ### 服务端
import socket
import time
"""
黏包现象:
	(1)发送端,数据小,时间间隔短,造成黏包
	(2)接收端,没有及时接受数据,可能把多次发送的数据当成一条截取.
"""

sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind( ("127.0.0.1",9001) )
sk.listen()
conn,addr = sk.accept()

# 收发数据的逻辑
conn.send("world,".encode())
time.sleep(1)
conn.send("hello".encode())

conn.close()
sk.close()

client.py

# ### 客户端
import socket
import time 

sk = socket.socket()
sk.connect( ("127.0.0.1",9001)  )

time.sleep(2)
# 收发数据的逻辑
res1 = sk.recv(1024)
print(res1.decode() , "<==1===>")
res2 = sk.recv(1024)
print(res2.decode() , "<==2===>")

sk.close()

3.2 临时性解决黏包

server.py

# ### 服务端
import socket
import time
"""
黏包现象:
	(1)发送端,数据小,时间间隔短,造成黏包
	(2)接收端,没有及时接受数据,可能把多次发送的数据当成一条截取.
"""

sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind( ("127.0.0.1",9001) )
sk.listen()
conn,addr = sk.accept()

# 收发数据的逻辑

# 第一步,先把接下来要发送的数据的字节大小发送过去
conn.send("6".encode())
# 第二部,发送真实的数据
conn.send("world,".encode())
conn.send("hello".encode())

conn.close()
sk.close()

client.py

# ### 客户端
import socket
import time 

sk = socket.socket()
sk.connect( ("127.0.0.1",9001)  )

time.sleep(2)
# 收发数据的逻辑
# 第一步,先接受接下来要发送的数据的总大小
res = sk.recv(1)
num = int(res.decode())

# 第二部,在接受真实的数据
res1 = sk.recv(num)
print(res1.decode() , "<==1===>")
res2 = sk.recv(1024)
print(res2.decode() , "<==2===>")

sk.close()

3.3 永久性解决黏包

server.py

# ### 服务端
import socket
import time
"""
黏包现象:
	(1)发送端,数据小,时间间隔短,造成黏包
	(2)接收端,没有及时接受数据,可能把多次发送的数据当成一条截取.
"""

sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind( ("127.0.0.1",9001) )
sk.listen()
conn,addr = sk.accept()

# 收发数据的逻辑

# 第一步,先把接下来要发送的数据的字节大小发送过去
conn.send("00000180".encode())
# 第二部,发送真实的数据
msg = "world," * 30
conn.send(msg.encode())
conn.send("hello".encode())

conn.close()
sk.close()

client.py

# ### 客户端
import socket
import time 

sk = socket.socket()
sk.connect( ("127.0.0.1",9001)  )

time.sleep(2)
# 收发数据的逻辑
# 第一步,先接受接下来要发送的数据的总大小
res = sk.recv(8)
num = int(res.decode())  # 180

# 第二部,在接受真实的数据
res1 = sk.recv(num)
print(res1.decode() , "<==1===>")
res2 = sk.recv(1024)
print(res2.decode() , "<==2===>")

sk.close()

3.4 struct模块解决黏包(最终优化)

struct模块

# ### struct 模块使用
import struct
"""
pack     打包 
	把任意长度数字转换成具有固定4个字节长度的字节流
unpack   解包
	把4个字节长度的值恢复成原来的数字,返回元组
	
"""

# pack
# i => int  要转换的当前类型是整型
"""范围: -21亿~21亿左右 控制在1.8G之内"""
res = struct.pack("i" , 999999998)
print(res , len(res))

res = struct.pack("i" , 1111111119)
print(res , len(res))

res = struct.pack("i" , 3)
print(res , len(res))


res = struct.pack("i" , 2000000000)
print(res , len(res))

# unpack
# i => 把对应的数据转化成整型
tup = struct.unpack("i" , res)
print(tup) #(2000000000,)
print(tup[0])
 
 
"""
#解决黏包场景:
	应用场景在实时通讯时,需要阅读此次发的消息是什么
#不需要解决黏包场景:
	下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓.
"""

server.py

# ### 服务端
import socket
import time
import struct

"""
黏包现象:
	(1)发送端,数据小,时间间隔短,造成黏包
	(2)接收端,没有及时接受数据,可能把多次发送的数据当成一条截取.
"""

sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind( ("127.0.0.1",9001) )
sk.listen()
conn,addr = sk.accept()

# 收发数据的逻辑
strvar = input("[服务端]请输入您要发送的数据")
msg = strvar.encode()
length = len(msg)
res = struct.pack("i",length)

# 第一次发送数据 (数据长度)
conn.send(res)

# 第二次发送数据 (真实数据)
conn.send(msg)

# 第三次发送数据 (真实数据)
conn.send(b"world,hello")


conn.close()
sk.close()


client.py

# ### 客户端
import socket
import time 
import struct

sk = socket.socket()
sk.connect( ("127.0.0.1",9001)  )

time.sleep(2)
# 收发数据的逻辑


# 第一次接受数据 (数据长度)
num = sk.recv(4)
tup = struct.unpack("i",num)
print(tup[0])

# 第二次接受数据 (真实数据)
res = sk.recv(tup[0])
print(res.decode())

# 第三次接受数据 (真实数据)
res = sk.recv(1024)
print(res.decode())

sk.close()

小提示:tcp是面向连接的无边界(会产生黏包)协议,udp是面向无连接的有边界的协议

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值