socket用法(TCP, UDP)
socket : 通络通信过程中,信息拼接的工具(中文:套接字)
开发中,一个端口只对一个程序生效,在测试时,允许端口重复捆绑 (开发时删掉), 在bind方法之前加上这句话,可以让一个端口重复使用: sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
TCP: 服务端和客户端简单的一次性通信
# 服务端 import socket # 1. 创建socket对象 sk = socket.socket() # 2. 绑定对应IP和端口(注册网络,让其他主机能够找到) sk.bind(("127.0.0.1",9000)) # 127.0.0.1 代表本地IP # 3. 开启监听 sk.listen() # 4. 建立三次握手 addr接收客户端的IP和端口号 conn,addr = sk.accept() # 5. 收发数据(recv里面的参数单位是字节,表示一次最多接收多少数据) res = conn.recv(1024) print(res) print(res.decode("utf-8")) # 6. 四次挥手 conn.close() # 7. 退还端口 sk.close() # 客户端 import socket # 1. 创建一个socket对象 sk = socket.socket() # 2. 与服务器进行连接 sk.connect(("127.0.0.1",9000)) # 3. 发送数据 sk.send("今天下了一天的雨".encode("utf-8")) # 4. 关闭连接 sk.close()
TCP: 服务端和客户端循环通信
# TCP循环通信: # 服务端 import socket # 1. 创建一个socket对象 sk = socket.socket() # 2. 在网络中注册主机(绑定IP和端口) sk.bind(("127.0.0.1",9000)) # 3. 监听 sk.listen() # 4. 建立三次握手 # 5. 接收数据 while True: # 保证服务端永不关闭,随时能跟客户端连接 conn, addr = sk.accept() while True: res = conn.recv(1024) print(res.decode("utf-8")) str1 = input("服务端想给客户端发送: ") conn.send(str1.encode("utf-8")) if str1.upper() == "Q": break # 6.四次挥手 conn.close() # 7.退还端口 sk.close() # 客户端 import socket # 1. 建立一个socket对象 sk= socket.socket() # 2. 连接服务端 sk.connect(("127.0.0.1",9000)) # 3. 发送数据 while True: strvar = input("客户端想给服务端发送的消息为: ") sk.send(strvar.encode("utf-8")) res = sk.recv(1024) if res.upper() == b"Q": break print(res.decode("utf-8")) # 4.断开连接 sk.close()
TCP的黏包现象:
(1)数据粘包是因为在客户端/服务器端都会有一个数据缓冲区,
缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区都会设置的比较大。
(2)在收发数据频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度
导致客户端/服务器端,都有可能把多条数据当成是一条数据进行截取,造成黏包
黏包现象一:
在发送端,由于两个数据短,发送的时间隔较短,所以在发送端形成黏包
黏包现象二:
在接收端,由于两个数据几乎同时被发送到对方的缓存中,所有在接收端形成了黏包
总结:
发送端,包之间时间间隔短 或者 接收端,接受不及时, 就会黏包
核心是因为tcp对数据无边界截取,不会按照发送的顺序判断
# TCP的黏包现象 # 服务端 import socket sk = socket.socket() sk.bind(("127.0.0.1",9000)) sk.listen() coon,addr = sk.accept() coon.send("下了一天的雨".encode()) coon.send("适合发呆".encode()) coon.close() sk.close() # 客户端 import socket import time sk = socket.socket() sk.connect(("127.0.0.1",9000)) time.sleep(0.1) res1 = sk.recv(1024) print(res1.decode()) res2 = sk.recv(1024) print(res2.decode()) # 下了一天的雨适合发呆 两个数据粘在了一起,这就是数据的黏包 sk.close()
TCP黏包解决方式:
# TCP的黏包现象 # 服务端 # 方式一: import socket sk = socket.socket() sk.bind(("127.0.0.1",9000)) sk.listen() coon,addr = sk.accept() # 在发送真实数据之前,先发送真实数据的大小 coon.send("18".encode()) coon.send("下了一天的雨".encode()) coon.send("适合发呆".encode()) coon.close() sk.close() # 客户端 import socket import time sk = socket.socket() sk.connect(("127.0.0.1",9000)) time.sleep(0.1) res = sk.recv(2) n = int(res.decode()) res1 = sk.recv(n) print(res1.decode()) res2 = sk.recv(1024) print(res2.decode()) # 下了一天的雨 # 适合发呆 sk.close() ############## # TCP的黏包现象 # 服务端 # 方式二: import socket sk = socket.socket() sk.bind(("127.0.0.1",9000)) sk.listen() coon,addr = sk.accept() # 在发送真实数据之前,先发送真实数据的大小 coon.send("00000018".encode()) # 以免客户端每次都要改第一次接收字节数 coon.send("下了一天的雨".encode()) coon.send("适合发呆".encode()) coon.close() sk.close() # 客户端 import socket import time sk = socket.socket() sk.connect(("127.0.0.1",9000)) time.sleep(0.1) res = sk.recv(8) # 将第一次接受的大小固定 n = int(res.decode()) res1 = sk.recv(n) print(res1.decode()) res2 = sk.recv(1024) print(res2.decode()) # 下了一天的雨 # 适合发呆 sk.close() # 但是方式一和方式二都太麻烦,每次都需要改动
TCP黏包解决方式三(推荐)
""" pack: 把任意长度的数字转化成居右4个字节的固定长度的字节流 unpack: 把4个字节值恢复成原本的数字,返回是元组 # 整型用i表示, 浮点型用f表示, 字符串用s表示 """ # TCP的黏包现象 # 服务端 # 方式三: 引入struct import socket import struct sk = socket.socket() sk.bind(("127.0.0.1",9000)) sk.listen() coon,addr = sk.accept() strvar = input("请输入数据:") msg = strvar.encode() length = len(msg) res = struct.pack("i",length) # 将要发送的第一个数据的长度先用struct进行压缩 coon.send(res) coon.send(strvar.encode()) coon.send("适合发呆".encode()) coon.close() sk.close() # 客户端 import socket import time import struct sk = socket.socket() sk.connect(("127.0.0.1",9000)) time.sleep(0.1) res = sk.recv(4) # struct压缩的数据永远都是4个字节 tup = struct.unpack("i",res) print(tup[0]) # 18 res1 = sk.recv(tup[0]) print(res1.decode()) res2 = sk.recv(1024) print(res2.decode()) # 下了一天的雨 # 适合发呆 sk.close()
解决黏包场景: 应用场景在实时通讯时,需要阅读此次发的消息是什么
不需要解决黏包场景: 下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓.
udp数据传输简单通信
# dup 数据传输 # 服务端 import socket # 1. 创建一个socket对象 type=socket.SOCK_DGRAM表明创建的是UDP协议对象 sk = socket.socket(type=socket.SOCK_DGRAM) # 2. 绑定IP和端口号 sk.bind(("127.0.0.1",9000)) # 4. 接收消息,(UDP作为服务端的时候,第一次肯定是接收消息) msg,cli_addr = sk.recvfrom(1024) print(msg.decode()) # 今天天气咋样啊? print(cli_addr) # ('127.0.0.1', 64877) # 发送消息给客户端 msg = "下了一天的雨" sk.sendto(msg.encode(),cli_addr) # 5. 关闭连接 sk.close() # 客户端 import socket # 1. 创建socket对象 sk = socket.socket(type=socket.SOCK_DGRAM) # 2. 发送数据 msg = "今天天气咋样啊?" sk.sendto(msg.encode(),("127.0.0.1",9000)) msg,ser_addr = sk.recvfrom(1024) print(msg.decode()) # 下了一天的雨 print(ser_addr) # ('127.0.0.1', 9000) # 3. 断开连接 sk.close()
udp 数据传输循环通信
# udp循环通信 # 服务端 import socket sk = socket.socket(type=socket.SOCK_DGRAM) sk.bind(("127.0.0.1",9000)) while True: while True: msg,cli_addr = sk.recvfrom(1024) print(msg.decode()) msg = input("服务端给客户端发送的消息: ") sk.sendto(msg.encode(), cli_addr) if msg.upper() == "Q": break sk.close() # 客户端 import socket sk = socket.socket(type=socket.SOCK_DGRAM) while True: msg = input("客户端给服务端发送的消息: ") sk.sendto(msg.encode(),("127.0.0.1",9000)) msg, ser_addr = sk.recvfrom(1024) if msg.upper() == b"Q": break print(msg.decode()) sk.close()
tcp协议:
缺点:接收时数据之间无边界,有可能粘合几条数据成一条数据,造成黏包
优点:不限制数据包的大小,稳定传输不丢包
udp协议:
优点:接收时候数据之间有边界,传输速度快,不黏包
缺点:限制数据包的大小(受带宽路由器等因素影响),传输不稳定,可能丢包
tcp和udp对于数据包来说都可以进行拆包和解包,理论上来讲,无论多大都能分次发送
但是tcp一旦发送失败,对方无响应(对方无回执),tcp可以选择再发,直到对应响应完毕为止
而udp一旦发送失败,是不会询问对方是否有响应的,如果数据量过大,易丢包