08.13笔记
socket(套接字)模块
# 收发消息的工具
# sk.setblocking(False) 设置非阻塞(什么accept,recv,不阻塞,报错就异常处理)
1, TCP协议基本语法
TCP(Transmission Control Protocol)一种面向连接的、可靠的、传输层通信协议(比如:打电话)
# 三次握手:三次信息的传递
# 四次挥手:通过与请求不可以合并
优点:可靠,稳定,传输完整稳定,不限制数据大小
缺点:慢,效率低,占用系统资源高,一发一收都需要对方确认
应用:Web浏览器,电子邮件,文件传输,大量数据传输的场景
1.1 服务端基本套路
import socket
sk = socket.socket() # 默认创建TCP协议对象
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 测试时加上这句话,端口可以重复利用
sk.bind(("127.0.0.1",3008)) # 绑定 ip+端口号
sk.listen() # 开始监听
while True:
conn,cil_appr = sk.accept() # 三次握手,返回值是(socket字节流,ip+端口号)
while True: # 收发消息的逻辑
# 接收消息
res = conn.recv(2024).decode()
print("收到{}消息:".format(cil_appr),res)
# 客户端主动断开连接
if res.lower() == "q":
print("{}连接已断开\n正在等待连接....".format(cil_appr))
break
# 发送消息
data = input("发送消息:")
# 服务端主动断开连接
conn.send(data.encode())
if data.lower() == "q":
print("{}连接已断开\n正在等待连接....".format(cil_appr))
break
conn.close()
sk.close() # 释放程序
1.2 客户端基本套路
import socket
sk = socket.socket() # 默认创建TCP协议对象
sk.connect(("127.0.0.1",3008)) # 连接("127.0.0.1",3008)服务器
while True: # 收发消息的逻辑
# 发送消息
data1 = input("发送消息:")
sk.send(data1.encode("utf-8"))
if data1.lower() == "q":
print("与服务器连接已断开")
break
res = sk.recv(2024).decode()
if res.lower() == "q":
print("与服务器连接已断开")
break
print("收到服务器消息:", res)
sk.close() # 关闭程序
2, UDP协议基本语法
UDP(User Datagram Protocol)一种无连接的,不可靠的传输层通信协议(比如:发短信)
优点:速度快,可以多人同时聊天,耗费资源少,不需要建立连接
缺点:不稳定,不能保证每次数据都能接收到
应用:IP电话,实时视频会议,聊天软件,少量数据传输的场景
2.1 服务端基本套路
import socket
sk = socket.socket(type=socket.SOCK_DGRAM) # 创建UDP协议对象
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 绑定 (ip+端口号)
sk.bind(("127.0.0.1",9005))
while True:
# 服务器永不断开
while True:
# 不需要建立连接,有消息就接收,同时获取msg,ip+端口号
msg,cil_appr = sk.recvfrom(1024)
# 接收消息
print("收到{}消息".format(cil_appr),msg.decode())
if msg.decode().lower() == "q":
print("{}连接已断开\n正在等待连接....".format(cil_appr))
break
# 发送消息
data = input("发送消息:")
sk.sendto(data.encode(),cil_appr)
if data.lower() == "q":
print("{}连接已断开\n正在等待连接....".format(cil_appr))
break
sk.close()
2.2 客户端基本套路
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
while True:
# 不需要建立连接,有ip+端口号就可以直接发送
data = input("发送消息:")
sk.sendto(data.encode(),("127.0.0.1",9005))
if data.lower() == "q":
print("与服务器连接已断开")
break
msg, cil_qppr = sk.recvfrom(1024)
if msg.decode().lower() == "q":
print("与服务器连接已断开")
break
print("收到{}消息".format(cil_qppr), msg.decode())
sk.close()
3 黏包
# TCP协议在发送数据时,会出现黏包现象.
(1)数据粘包是因为在客户端/服务器端都会有一个数据缓冲区,
缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区都会设置的比较大。
(2)在收发数据频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度
导致客户端/服务器端,都有可能把多条数据当成是一条数据进行截取,造成黏包
# 黏包对比
#tcp协议:
缺点:接收时数据之间无边界,有可能粘合几条数据成一条数据,造成黏包
优点:不限制数据包的大小,稳定传输不丢包
#udp协议:
优点:接收时候数据之间有边界,传输速度快,不黏包
缺点:限制数据包的大小(受带宽路由器等因素影响),传输不稳定,可能丢包
tcp和udp对于数据包来说都可以进行拆包和解包,理论上来讲,无论多大都能分次发送
但是tcp一旦发送失败,对方无响应(对方无回执),tcp可以选择再发,直到对应响应完毕为止
而udp一旦发送失败,是不会询问对方是否有响应的,如果数据量过大,易丢包
# 导致黏包的2种情况
1,发送端发送消息速度太快
2,接收端接收消息速度太慢
3.1 struct模块解决黏包问题
"""
通过UDP接收数据有边界的特点,用struct来解决TCP接收数据无边界来解决黏包问题
"""
# 服务端
import socket,struct,time
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(("127.0.0.1",3008))
sk.listen()
conn,cil_appr = sk.accept()
num = conn.recv(4)
num = struct.unpack("i",num)
data1 = conn.recv(num[0])
data2 = conn.recv(1024)
print(data1.decode())
print(data2.decode())
data2.decode()
conn.close()
sk.close()
# 客户端
sk = socket.socket()
sk.connect(("127.0.0.1",3008))
data = input("请输入发送的消息").encode()
num = struct.pack("i",len(data))
sk.send(num)
sk.send(data)
sk.send("黏包了吗".encode())
sk.close()
3.1.1 服务端脚本
import socket, struct
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk.bind(("127.0.0.1", 3008))
sk.listen()
while True:
conn, cil_appr = sk.accept()
while True:
num = conn.recv(4)
num = struct.unpack("i", num)
res = conn.recv(num[0])
print("收到{}消息:".format(cil_appr), res.decode())
res = conn.recv(1024)
print("收到{}消息:".format(cil_appr), res.decode())
if res.lower() == "q":
print("{}连接已断开\n正在等待连接....".format(cil_appr))
break
data = input("发送消息:")
conn.send(data.encode())
if data.lower() == "q":
print("{}连接已断开\n正在等待连接....".format(cil_appr))
break
conn.close()
sk.close()
3.1.2 客户端脚本
import socket,struct
sk = socket.socket()
sk.connect(("127.0.0.1",3008))
while True:
data = input("发送消息:").encode()
num = struct.pack("i",len(data)) # 获取任意数字转化后为4字节的字节流
sk.send(num) # 先告诉对方,我要发多少单位的数据
sk.send(data) # 再发送实际数据
sk.send("黏包了吗????".encode())
res = sk.recv(2024).decode()
if data.lower() == "q":
print("与服务器连接已断开")
break
if res.lower() == "q":
print("与服务器连接已断开")
break
print("收到服务器消息:", res)
sk.close()