网络并发编程02--socket&粘包
1. socket 套接字编程
# 要求:自己想写一款可以数据交互的程序。
只要涉及到远程数据交互,必须要操作OSI七层,所有由现成的模块直接实现。
socket模块
架构启动一定是先启动服务端,再启动客户端。
# 服务端
import socket
# 阅读源码
sock = socket.socket() # 默认是基于网络的TCP传输协议
sock.bind(('127.0.0.1', 8001)) # 绑定IP和port,插卡
sock.listen(5) # 半连接池 开机
conn, addr = sock.accept() # 监听 三次握手的listen态
# -> 箭头函数,后面相当于他的返回值
# : 冒号表示,后面的数据类型
print(addr) # 客户端地址
# conn 是一个对象,可以做一些操作
data = conn.recv(1024) # 接收客户端发送的消息,听别人说话
print(data)
conn.send(b'hello world!') # 给被人回话
conn.close() # 挂电话
sock.close() # 关机
扩展
# 在阅读源码的时候
"1. -> 函数后面跟横杠加大于号,表示的是函数的返回值类型"
"2. 变量名后面跟冒号 : , 表示的意思是该变量名需要指代的数据类型"
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8001)) # 直接拨号
# 讲话
client.send(b'hello big big!!')
# 听
data = client.recv(1024)
print(data)
client.close()
2. 通信循环 & 代码优化
- 客户端校验消息不能为空。
- 服务端添加兼容性代码(mac Linux)
- 服务端重启频繁报端口占用错误
from socket import SOL_SOCKET, SO_REUSEADDR server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 加在bind前面
- 客户端异常关闭,服务端报错的问题,可以用异常捕获
- 服务端连接循环
- 半连接池
设置可以等待的客户端数量
# 服务端
import socket
from sokcet import SOL_SOCKET, SO_REUSEADDR
sock = socket.socket()
sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8001))
sock.listen(5) # 半连接池
while True: # 针对问题5优化
conn, addr = sock.accept()
print(addr)
while True:
try:
data = conn.recv(1024)
"""
针对mac和Linux的优化
if len(data) == 0:
continue
"""
print(data.decode())
conn.send(data+b'123')
except ConnectionResetError as e:
print(e)
break
conn.close()
sock.close()
3. 粘包问题
- 数据管道的数据没有被完全取出,从而导致数据残留,被下一次数据取到。
- TCP协议有一个特性:
当数据量比较小,且时间间隔比较短的多次数据,
TCP会自动打包成一个数据包发送 - 报头
能够标识即将到来的数据具体信息(数据量大小)
报头的长度必须是固定的。
示例一:
import subprocess
sub = subprocess.Popen("ipconfig", shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
print(sub.stdout.read().decode('gbk'))
print(sub.stderr.read().decode('gbk'))
# 客户端
import socket
client = socket.socket()
client.connect(("127.0.0.1", 8001)) # 直接拨号
while True:
# 说话
msg = input('请输入命令>>>:').strip()
if len(msg) == 0:
continue
client.send(msg.encode('gbk'))
# 听
data = client.recv(1024)
print(data.decode('gbk'))
client.close()
# 服务端
import socket
import subprocess
sock = socket.socket()
sock.bind(("127.0.0.1", 8001))
sock.listen(5) # 半连接池
while True: # 针对问题5优化
conn, addr = sock.accept()
print(addr)
while True:
try:
data = conn.recv(1024)
"""
针对mac和Linux的优化
if len(data) == 0:
continue
"""
command_cmd = data.decode('gbk')
sub = subprocess.Popen(command_cmd, shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)data.decode('gbk')
res = sub.stdout.read() + sub.stderr.read()
conn.send(res.encode('gbk'))
except ConnectionResetError as e:
print(e)
break
conn.close()
示例二:
# 服务端
import socket
sock = socket.socket()
sock.bind(('127.0.0.1', 8888))
sock.listen(5)
conn, addr = sock.accept()
data = conn.recv(1024)
print(data)
data = conn.recv(1024)
print(data)
data = conn.recv(1024)
print(data)
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8888))
client.send(b'hello')
client.send(b'jsono')
client.send(b'oscar')
4. struct模块解决粘包
import struct
# ########### 数值转换为固定4个字节,四个字节的范围 -2147483648 <= number <= 2147483647 ###########
v1 = struct.pack('i', 199)
print(v1) # b'\xc7\x00\x00\x00'
for item in v1:
print(item, bin(item))
# ########### 4个字节转换为数字 ###########
v2 = struct.unpack('i', v1)
# v1= b'\xc7\x00\x00\x00'
print(v2)
# (199,)
简易版报头
"服务端"
import socket
import subprocess
import struct
import json
sock = socket.socket()
sock.bind(('127.0.0.1', 8008))
sock.listen(5)
while True:
conn, addr = sock.accept()
while True:
data = sock.recv(1024) # 接收cmd命令
command_cmd = data.decode('utf8')
sub = subprocess.Popen(command_cmd, shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
res = substdout.read() + sub.stderr.read()
# 1. 制作报头
data_fir = struct.pack('i', len(res))
# 2. 发送报头
sock.send(data_fir) # 数字
# 3. 发送数据
sock.send(res)
"客户端"
import socket
import struct
client = socket.socket()
client.connect(('127.0.0.1', 8008))
while True:
msg = input('请输入cmd命令>>>:').strip()
if len(msg) == 0:
continue
client.send(msg.encode())
# 1. 先接收固定长度为4的报头数据
recv_fir = client.recv(4)
# 2. 解析报头
real_length = struct.unpack('i', recv_fir)[0]
# 3. 接收真实数据
real_data = client.recv(real_length)
print(real_data.decode('gbk'))
高级版本
服务端
import socket
import subprocess
import struct
import json
sock = socket.socket()
sock.bind(('127.0.0.1', 8080))
sock.listen(5)
while True:
conn, addr = sock.accept()
while True:
data = sock.recv(1024)
command_cmd = data.decode('utf8')
sub = subprocess.Popen(command_cmd, shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
res = substdout.read() + sub.stderr.read()
# 1. 定义一个字典数据
data_dict = {
'desc':'重要数据',
'size':len(res),
'info':'哈喽啊啊啊啊啊'
}
data_json = json.dumps(data_dict)
# 2. 制作字典报头
data_fir = struct.pack('i', len(data_json))
# 3. 发送字典报头
sock.send(data_fir) # 数字
# 4. 发送字典
sock.send(data_json.encode('utf8'))
# 5. 发送真实数据
sock.send(res)
客户端
import socket
import struct
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
msg = input('请输入cmd命令>>>:').strip()
if len(msg) == 0:
continue
client.send(msg.encode())
# 1. 先接收固定长度为4的报头数据
recv_fir = client.recv(4)
# 2. 解析字典报头
dict_length = struct.unpack('i', recv_fir)[0]
# 3. 接收字典数据
real_data = client.recv(dict_length)
# 4. 解析字典
"json格式的bytes数据,loads自动解码,先解码再反序列化"
real_dict = json.loads(real_data)
print(real_dict)
# 5. 获取字典中的各项数据
data_length = real_dict.get('size')
data_bytes = client.recv(data_length)
print(data_bytes.decode('gbk'))