socket 为我们提供了一组接口,这样我们在与另外一台计算机通信的时候不用再把数据加多层协议进行封装,直接调用socket接口就可以了。
import socket
import subprocess
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 套接字类型,TCP协议
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 重用当前地址,有时候出现地址被占用的情况的解决办法。
phone.bind(('127.0.0.1', 8080)) # 绑定地址,端口,本机自测地址
phone.listen(2) # 最大等待连接数
while True: # 实现多连接,没有用并发
conn, client_addr = phone.accept() # phone.accept()产生了两个元素,拿到其中套接字类型conn。
while True: # 循环交互
try: # 异常处理,用户端可能断开连接
cmd = conn.recv(1024) # 接受最大字节数
res = subprocess.Popen(cmd.decode('utf-8'), shell=True, # 解码bytes类型,一定要写shell,解析命令
stdout=subprocess.PIPE, # 结果bytes类型,且存在了管道里,只能读取一次
stderr=subprocess.PIPE # 结果如果没有正确执行也会存在管道里
)
stdout = res.stdout.read()
stderr = res.stderr.read()
conn.send(stdout+stderr) # 有待优化
except ConnectionResetError:
break
conn.close()
phone.close()
import socket
import subprocess
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080)) # 连接到服务端
while True:
cmd = input('>>:').strip()
if cmd == 'q':
break
if not cmd: # 避免发空包
continue
phone.send(cmd.encode('utf-8')) # 命令转换成bytes类型,服务器端解码
data = phone.recv(1024) # 会产生粘包问题,一次接受不完,在发送命令之后,两次执行结果粘合在一次。
print(data.decode('gbk')) # 解码显示,windows用gbk,linux 用utf-8
phone.close()
客户端发送命令之后,接收服务端执行完毕发来结果时,一次接受不完,就产生粘包问题。比如利用上述代码,在客户端输入ipconfig命令后接收结果时,只能接受1024,(这个数字改大了没多大意义,总会有结果超过这个长度,改的太大影响速度和稳定性),再执行命令后,接受的是上一次剩余的结果的1024个字节。
解决粘包问题的方式就是让发送端在发送数据前,先发送一个数据长度,接收端在接收到数据长度之后,知道要接收的数据长度,循环多次,接收完服务端一次发送来的数据。
不过发送端在发送数据长度之后,再发数据信息时,数据长度的信息息和数据信息在一起,需要客户端识别出长度和真实数据。
可以用struct模块把数据长度固定长度,接受端接收固定长度的字节,解析出数据长度。
以下代码块解决客户端发送命令之后,接收服务端执行完毕发来结果时,一次接受不完,产生的粘包问题。
import socket
import subprocess
import struct
# 粘包问题
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
phone.bind(('127.0.0.1', 8080))
phone.listen(2)
while True:
conn, client_addr = phone.accept()
while True:
try:
cmd = conn.recv(8000) # 一般命令都不那么长
res = subprocess.Popen(cmd.decode('utf-8'), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
stdout = res.stdout.read()
stderr = res.stderr.read()
# 制作固定长度报头需要struct模块
total_size = len(stdout) + len(stderr)
header = struct.pack('i', total_size) # i 模式把文件长度固定为4
# 发送
conn.send(header)
# 发真实数据
conn.send(stdout)
conn.send(stderr)
except ConnectionResetError:
break
conn.close()
phone.close()
import socket
import subprocess
import struct
# 粘包问题
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while True:
cmd = input('>>:').strip()
if cmd == 'q':
break
if not cmd:
continue
phone.send(cmd.encode('utf-8'))
# 先收报头
obj = phone.recv(4) # struct 把数据长度固定为4
# 解析出数据长度,struct模块解析返回元祖形式,[0]拿到数据长度
total_size = struct.unpack('i', obj)[0]
# 接收数据
recv_size = 0
recv_data = b''
while recv_size < total_size:
data = phone.recv(1024)
recv_data += data
recv_size += len(data)
print(recv_data.decode('gbk'))
phone.close()
struct模块 i 模式只能把一定长度固定成4个字节,如果一个文件特别长,无论是 i 模式还是 l(L小写)模式(固定为8个字节)都会报错。
而且,header现在只包含数据长度,如果要增加其他信息怎么办?
可以把要添加的信息组合成字典结构,然后 json 序列化下,这样还可以解析成字典,然后编码转成bytes才能发送。
相当于发送了两部分信息,一个header,一个真实数据信息。
首先接收端要知道 header 这段信息的长度,然后接受固定长度的字节把 header 接过来。
然后从 header 解析出真实数据的长度, 再根据真实数据长度循环多次接收真实数据信息。
import socket
import subprocess
import struct
import json
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
phone.bind(('127.0.0.1', 8080))
phone.listen(2)
while True: #
conn, client_addr = phone.accept()
while True:
try:
cmd = conn.recv(8000) # 一般命令都不那么长
res = subprocess.Popen(cmd.decode('utf-8'), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
stdout = res.stdout.read()
stderr = res.stderr.read()
# 制作有更多信息的header
header_dict = {
'total_size': len(stdout)+len(stderr),
'file_name': 'test.txt'
}
# 字典序列化并编码转为bytes
header_bytes = json.dumps(header_dict).encode('utf-8')
# 此时是bytes类型,再用struct模块固定长度
header_bytes_lengh = struct.pack('i', len(header_bytes))
# 发送header长度
conn.send(header_bytes_lengh)
# 发送header_bytes
conn.send(header_bytes)
# 发真实数据
conn.send(stdout) # 紧接着发又会产生粘包问题,数据长度的消息和真实数据粘包在一起,需要客户端识别出长度和消息
conn.send(stderr)
except ConnectionResetError:
break
conn.close()
phone.close()
import socket
import subprocess
import struct
import json
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while True:
cmd = input('>>:').strip()
if cmd == 'q':
break
if not cmd:
continue
phone.send(cmd.encode('utf-8'))
# 先收4个字节,解析出header_bytes的长度
obj = phone.recv(4)
header_bytes_lengh = struct.unpack('i', obj)[0]
# 接收head_bytes
header_bytes = phone.recv(header_bytes_lengh)
# 解码反序列化出header_dict
header_dict = json.loads(header_bytes.decode('utf-8'))
print(header_dict)
# 从header_dict里找出真实数据长度
total_size = header_dict['total_size']
recv_size = 0
recv_data = b''
while recv_size < total_size:
data = phone.recv(1024)
recv_data += data
recv_size += len(data)
print(recv_data.decode('gbk'))
phone.close()
import socket
import subprocess
import struct
import json
down_dir =(设置客户端路径)
def get(phone,cmd): # 看完下面的run再看这个
obj = phone.recv(4)
header_bytes_lengh = struct.unpack('i', obj)[0]
# 接收head_bytes
header_bytes = phone.recv(header_bytes_lengh)
# 解码反序列化出header_dict
header_dict = json.loads(header_bytes.decode('utf-8'))
print(header_dict)
# 从header_dict里找出真实数据长度
total_size = header_dict['file_size']
file_name = header_dict['file_name']
with open('%s\%s' % (down_dir, file_name), 'wb') as f:
recv_size = 0
while recv_size < total_size:
line = phone.recv(1024)
f.write(line)
recv_size += len(line)
def run():
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while True:
cmd = input('>>:').strip()
if cmd == 'q':
break
if not cmd:
continue
phone.send(cmd.encode('utf-8'))
if cmd.split()[0] == 'get': # 解析命令
get(phone, cmd)
phone.close()
if __name__ == '__main__':
run()
import socket
import subprocess
import struct
import json
import os
share_dir =(设置服务端路径)
def get(conn, cmd): # 看完下面的run再看这个
filename = cmd[1]
# 制作有更多信息的header
header_dict = {
'file_size': os.path.getsize('%s\%s' % (share_dir, filename)),
'file_name': filename
}
# 字典序列化并编码转为bytes
header_bytes = json.dumps(header_dict).encode('utf-8')
# 此时是bytes类型,再用struct模块固定长度
header_bytes_lengh = struct.pack('i', len(header_bytes))
# 发送header长度
conn.send(header_bytes_lengh)
# 发送header_bytes
conn.send(header_bytes)
# 发真实数据
with open('%s\%s' % (share_dir, filename), 'rb') as f:
for line in f:
conn.send(line)
def run():
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
phone.bind(('127.0.0.1', 8080))
phone.listen(2)
while True:
conn, client_adr = phone.accept()
while True:
try:
res = conn.recv(8000) # get a.txt
cmd = res.decode('utf-8').split() # 解析['get','a.txt']
if cmd[0] == 'get':
get(conn, cmd)
except ConnectionResetError:
break
conn.close()
phone.close()
if __name__ == '__main__':
run()