Python笔记_27_UDP协议_黏包现象

UDP协议

基本语法
  • server服务端
# ### 服务端
import socket
# socket.SOCK_DGRAM 代表udp协议
sk = socket.socket(type=socket.SOCK_DGRAM)
# 绑定地址
sk.bind(('127.0.0.1',8000))

# upd作为服务器,第一次一定是先接受消息
msg,addr = sk.recvfrom(1024)
print(msg,addr)

msg2 = '对,确实不错,因为今天周五'
#sendto ( '要发送的消息'.encode() , (ip,端口))
sk.sendto(msg2.encode('utf-8'),addr)

# 关闭udp连接
sk.close()
  • client客户端
# ### 客户端
import socket 
sk = socket.socekt(type=socket.SOCK_DGRAM)
msg = '今天天气不错'
# 发送数据
sk.sendto(msg.encode('utf-8'),('127.0.0.1',9000))

# udp 接受数据
msg,addr = sk.recvfrom(1024)
print(msg,addr)
res = msg.decode('utf-8')
print(res)

# 关闭udp连接
sk.close()

黏包现象

tcp协议在发送数据时,会出现黏包现象

  1. 数据黏包是因为在客户端/服务端都会有一个数据缓存区,缓存区用来临时保存数据,为了保证能够完整的接受到数据,因此缓存区 都会设置的比较大.
  2. 在收发数据频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度
    导致客户端/服务器端,都有可能把多条数据当成是一条数据进行截取,造成黏包
黏包出现的两种情况
  • 黏包现象一
    在发送端,由于两个数据短,发送的时间隔较短,所以在发送端形成黏包
  • 黏包现象二:
    在接收端,由于两个数据几乎同时被发送到对方的缓存中,所有在接收端形成了黏包
  • 总结:
    发送端,包之间时间间隔短 或者 接收端,接受不及时, 就会黏包
    核心是因为tcp对数据无边界截取,不会按照发送的顺序判断
黏包对比:tcp和udp
  • tcp协议:
    优点 : 不限制数据包的大小,稳定传输不丢包
    缺点 : 接收时数据之间无边界,有可能粘合几条数据成一条数据,造成黏包

  • udp协议:
    优点 : 接收时候数据之间有边界,传输速度快,不黏包
    缺点 : 限制数据包的大小(受带宽路由器等因素影响),传输不稳定,可能丢包

  • tcp和udp对于数据包来说都可以进行拆包和解包,理论上来讲,无论多大都能分次发送
    但是tcp一旦发送失败,对方无响应(对方无回执),tcp可以选择再发,直到对应响应完毕为止
    而udp一旦发送失败,是不会询问对方是否有响应的,如果数据量过大,易丢包

解决黏包问题
  • 解决黏包场景:
    应用场景在实时通讯时,需要阅读此次发的消息是什么
  • 不需要解决黏包场景:
    下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓.
    1.通过time.sleep或者提前传入下一个消息的长度解决
# ### 服务端
import socket
import time
sk = socket.socket()

# 在bind方法之前加上这句话,可以让一个端口重复使用
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 绑定地址端口(在网络上注册主机)
sk.bind( ("127.0.0.1",9000) )
sk.listen()

conn,addr = sk.accept()
conn.send("6".encode("utf-8"))
message = "hello,"
conn.send(message.encode("utf-8"))
# time.sleep(0.1)
conn.send("world".encode("utf-8"))


# 四次挥手
conn.close()
# 退还端口
sk.close()
# ### 客户端
import socket
import time
sk = socket.socket()
sk.connect( ("127.0.0.1",9000) )

# time.sleep(0.2)
res0 = int(sk.recv(1).decode("utf-8")) #res0 "6"
print(res0)
res1 = sk.recv(res0)
print(res1)
# print(res1.decode("utf-8"))
res2 = sk.recv(10)
print(res2)
sk.close()

  1. 通过传输8位‘00000000’字符串,固定客户端接收到的消息长度的字符数
# ### 服务端
import socket
import time
sk = socket.socket()

# 在bind方法之前加上这句话,可以让一个端口重复使用
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 绑定地址端口(在网络上注册主机)
sk.bind( ("127.0.0.1",9000) )
sk.listen()

conn,addr = sk.accept()
conn.send("00000120".encode("utf-8"))
message = "hello," * 20
conn.send(message.encode("utf-8"))
# time.sleep(0.1)
conn.send("world".encode("utf-8"))


# 四次挥手
conn.close()
# 退还端口
sk.close()
# ### 客户端
import socket
import time
sk = socket.socket()
sk.connect( ("127.0.0.1",9000) )

# time.sleep(0.2)
res0 = int(sk.recv(8).decode("utf-8")) #res0 "6"
print(res0)
res1 = sk.recv(res0)
print(res1)
# print(res1.decode("utf-8"))
res2 = sk.recv(10)
print(res2)
sk.close()
  1. 通过struct 将任意长度数字打包成4字节
# ### 服务端
import socket
import struct

sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind( ("127.0.0.1",9000) )
sk.listen()

conn,addr = sk.accept()
inp = input(">>>msg:")
msg = inp.encode("utf-8")
# 发送数据的长度通过pack进行转换,变成具有固定长度的4个字节的值
res = struct.pack("i",len(msg))

conn.send(res)
# 接下来,开始真正的发送数据
conn.send(msg)
conn.send("world".encode("utf-8"))

res = conn.recv(1024)
print(res)
print(res.decode("utf-8"))
# 四次挥手
conn.close()
# 退还端口
sk.close()
# ### 客户端
import socket
import struct
import time
sk = socket.socket()
sk.connect( ("127.0.0.1",9000) )
time.sleep(0.1)
# 接受4个字节长度, 他是实际要发送的那个数字转化来的.
n = sk.recv(4)
n = struct.unpack("i",n)[0]
print(n)

# 接下来接受服务端发送过来的数据
res1 = sk.recv(n)
print(res1.decode("utf-8"))
res2 = sk.recv(1024)
print(res2.decode("utf-8"))

# 空格不是ascii编码中的,大家注意.
sk.send(b"i_love_you")

# 关闭连接
sk.close()
struct用法
import struct
# pack     把任意长度的数字转化成具有固定4个字节长度的字节流
# unpack   将4个字节的值恢复成原本的数据,最后返回一个元组
# i => int 我要转化的当前数据类型是整型
res = struct.pack("i",304535345)
print(res)
print(len(res))

res = struct.unpack("i",res)
print(res)
print(res[0],type(res[0]))

模块socketserver

网络协议的最底层就是socket,基于原有socket模块,又封装了一层,就是socketserver
socketserver 为了实现tcp协议server端的并发.

  • 基本用法
# ### 服务端
import socketserver
# 需要自定义一个类,并且继承socketserver.BaseRequestHandler
class MyServer(socketserver.BaseRequestHandler):
	def handle(self):
		#<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', 54088)>
		print(self.request) 
		print("--->执行这句话")

# Threading( (ip,端口号)   ,  自定义类 )
server = socketserver.ThreadingTCPServer( ("127.0.0.1",9000) , MyServer)
# 循环调用
server.serve_forever()

# ### 客户端
import socket
sk = socket.socket()
sk.connect( ("127.0.0.1",9000) )
sk.close()
  • 循环发送接收
# 服务器
import socketserver
class MyServer(sockerserver.BaseRequestHandler):
	def hander(self):
		print('这句话被执行了')
		conn = self.request
		while True:
			msg = conn.recv(1024).decode('utf-8')
			print(msg)
			conn.send(msg.upper().encode('utf
			-8'))
# 产生一个对象
server = socektserver.ThreadingTCPServer(('127.0.0.1',8000),MyServer)
server.socke_forever()
# 客户端
import socket 
sk = socekt.socket()
sk.connect(('127.0.0.1',8000))

while True:
	sk.send(b'i_miss_you')
	msg = sk.recv(1024)

sk.close()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值