模块scoket小试牛刀
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()