socket接收大数据
#server端
import socket,os
server = socket.socket()
server.bind(('localhost',9999))
server.listen()
while True:
conn,addr = server.accept()
print ('new conn:',addr)
while True:
print ("等待新指令")
data = conn.recv(1024)
if not data: #接收数据为0,则断开
print ('客户端已断开!')
break
print ('执行指令:',data)
cmd_res = os.popen(data.decode()).read()
print("before send", len(cmd_res)) # 在发送数据前查看数据是否为空
conn.send(cmd_res.encode('utf-8'))
print ("send done")
server.close()
#client端
import socket
client = socket.socket()
client.connect(('localhost',9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0:continue
client.send(cmd.encode('utf-8'))
cmd_res = client.recv(1024)
print (cmd_res)
client.close()
先执行server,在执行client
client执行结果:
>>:pwd
server执行结果:
new conn: ('127.0.0.1', 57790)
等待新指令
执行指令: b'pwd'
'pwd' �����ڲ����ⲿ���Ҳ���ǿ����еij���
���������ļ���
before send 0
send done
等待新指令
#可以看到server端发送的数据长度为0(pwd用来显示当前路径)
#那么此时就卡到client的cmd_res = client.recv(1024)这里,因为client端收不到数据,当前就会这么一直卡着。
#server
import socket,os
server = socket.socket()
server.bind(('localhost',9999))
server.listen()
while True:
conn,addr = server.accept()
print ('new conn:',addr)
while True:
print ("等待新指令")
data = conn.recv(1024)
if not data:
print ('客户端已断开!')
break
print ('执行指令:',data)
cmd_res = os.popen(data.decode()).read()
print("before send", len(cmd_res))
if len(cmd_res) == 0: #避免数据为0,,修改cmd_res进行提示
cmd_res = 'cmd has no output......'
conn.send(cmd_res.encode('utf-8'))
print ("send done")
server.close()
#client
import socket
client = socket.socket()
client.connect(('localhost',9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0:continue
client.send(cmd.encode('utf-8'))
cmd_res = client.recv(1024)
print (cmd_res)
client.close()
client执行结果:
>>:pwd
b'cmd has no output......'
>>:
#server ()
import socket,os
server = socket.socket()
server.bind(('localhost',9999))
server.listen()
while True:
conn,addr = server.accept()
print ('new conn:',addr)
while True:
print ("等待新指令")
data = conn.recv(1024)
if not data: #接收数据为0,则断开
print ('客户端已断开!')
break
print ('执行指令:',data)
cmd_res = os.popen(data.decode()).read()
print("before send", len(cmd_res)) # 在发送数据前查看数据是否为空
if len(cmd_res) == 0:
cmd_res = 'cmd has no output......'
conn.send(cmd_res.encode('gbk'))
#client发送ipconfig命令来从Windows获取信息,但当前是在Windows环境,所以使用gbk才能将数据正常的封装过去
print ("send done")
server.close()
#client
import socket
client = socket.socket()
client.connect(('localhost',9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0:continue
client.send(cmd.encode('utf-8'))
cmd_res = client.recv(1024)
print (cmd_res.decode('gbk'))
#client将发送ipconfig命令给server端,server返回gbk格式的内容给client
client.close()
client执行结果:
>>:ipconfig
Windows IP 配置
无线局域网适配器 WLAN:
媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :
无线局域网适配器 本地连接* 1:
媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :
以太网适配器 以太网:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::141a:83:bbf0:3c7%2
IPv4 地址 . . . . . . . . . . . . : 10.213.36.2
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . : 10.213.36.250
以太网适配器 VMware Network Adapter VMnet1:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::e08f:e633:7d98:5a39%20
IPv4 地址 . . . . . . . . . . . . : 192.168.52.1
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . :
以太网适配器 VMware Network Adapter VMnet8:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::318f:38e3:25d9:c249%11
IPv4 地址 . . . . . .
>>:
上图中在cmd里也使用ipconfig命令,但是可以看到的是显示的内容比通过python显示的内容要多,这是因为我们设置接收长度为1024,所以client一次最多只能接收1024,但实际内容数据超过了1024。
剩余的内容当前已经被缓存到缓冲区了(buffer)。
server端:
import socket,os
server = socket.socket()
server.bind(('localhost',9999))
server.listen()
while True:
conn,addr = server.accept()
print ('new conn:',addr)
while True:
print ("等待新指令")
data = conn.recv(1024)
if not data:
print ('客户端已断开!')
break
print ('执行指令:',data)
cmd_res = os.popen(data.decode()).read()
print("before send", len(cmd_res))
if len(cmd_res) == 0:
cmd_res = 'cmd has no output......'
conn.send(str(len(cmd_res)).encode('utf-8'))
#发送数据长度给client,这样client就可以判断(根据1024)需要接收多少次数据。
#但是这里要注意的是,整数是不可以被encode,只有字符串才可以被encode,所以这里要使用str将其改为字符串格式
conn.send(cmd_res.encode('gbk')) #发送实际数据给client,注意实际数据是从Windows获取的,所以数据格式是gbk
print ("send done")
server.close()
client端:
import socket
client = socket.socket()
client.connect(('localhost',9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0:continue
client.send(cmd.encode('utf-8'))
cmd_res_size = client.recv(1024) #接收命令结果的长度
# cmd_res = client.recv(1024)
print ('接收结果大小:',cmd_res_size.decode()) #打印接收数据的总大小
received_size = 0
while received_size != int(cmd_res_size.decode()):
#接收来的数据大小不等于数据的总大小,说明数据还没接收完,就会一直循环接收数据;
#由于cmd_res_size发送过来的是bytes类型,所以需要先decode,在int将字符串改为整数。
data = client.recv(1024)
received_size += len(data) #每次接收数据可能小于1024,所以需要len判断
print (data.decode('gbk')) #发送过来的Windows数据是gbk
else:
print ("cmd res receive done....",received_size)
client.close()
client执行结果:
>>:ipconfig
接收结果大小: 1185
Windows IP 配置
无线局域网适配器 WLAN:
媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :
无线局域网适配器 本地连接* 1:
媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :
以太网适配器 以太网:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::141a:83:bbf0:3c7%2
IPv4 地址 . . . . . . . . . . . . : 10.213.36.2
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . : 10.213.36.250
以太网适配器 VMware Network Adapter VMnet1:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::e08f:e633:7d98:5a39%20
IPv4 地址 . . . . . . . . . . . . : 192.168.52.1
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . :
以太网适配器 VMware Network Adapter VMnet8:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::318f:38e3:25d9:c249%11
IPv4 地址 . . . . . .
. . . . . . : 192.168.142.1
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . :
以太网适配器 蓝牙网络连接:
媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :
隧道适配器 本地连接* 12:
媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :
server执行结果:
new conn: ('127.0.0.1', 60035)
等待新指令
执行指令: b'ipconfig'
before send 1185
send done
等待新指令
当前client卡在上图的位置就不动了
print (“cmd res receive done…”,received_size)这行代码没有被打印出来(说明卡住了)
server端:
import socket,os
server = socket.socket()
server.bind(('localhost',9999))
server.listen()
while True:
conn,addr = server.accept()
print ('new conn:',addr)
while True:
print ("等待新指令")
data = conn.recv(1024)
if not data:
print ('客户端已断开!')
break
print ('执行指令:',data)
cmd_res = os.popen(data.decode()).read()
print("before send", len(cmd_res))
if len(cmd_res) == 0:
cmd_res = 'cmd has no output......'
conn.send(str(len(cmd_res)).encode('utf-8'))
conn.send(cmd_res.encode('gbk'))
print ("send done")
server.close()
client端:
import socket
client = socket.socket()
client.connect(('localhost',9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0:continue
client.send(cmd.encode('utf-8'))
cmd_res_size = client.recv(1024)
# cmd_res = client.recv(1024)
print ('接收结果总大小:',cmd_res_size.decode())
received_size = 0
while received_size != int(cmd_res_size.decode()):
data = client.recv(1024)
received_size += len(data)
# print (data.decode('gbk'))
print ('数据实际大小:',received_size) #先不打印数据,来打印大小看看
else:
print ("cmd res receive done....",received_size)
client.close()
client执行结果:
接收结果总大小: 1185
数据实际大小: 1024
数据实际大小: 1390
#可以看到server端发送数据的总大小和实际接收数据的总大小不一致,然后根据while循环两个数据不相等时client就会一直等待接收数据,所以导致当前程序卡在client等待接收数据这里。
修改client端:
import socket
client = socket.socket()
client.connect(('localhost',9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0:continue
client.send(cmd.encode('utf-8'))
cmd_res_size = client.recv(1024)
# cmd_res = client.recv(1024)
print ('接收结果总大小:',cmd_res_size.decode())
received_size = 0
while received_size < int(cmd_res_size.decode()):
#这里讲!=修改为<(只要小于就接收)
data = client.recv(1024)
received_size += len(data)
# print (data.decode('gbk')) #发送过来的Windows数据是gbk
print ('数据实际大小:',received_size)
else:
print ("cmd res receive done....",received_size)
client.close()
client执行结果:
>>:ipconfig
接收结果总大小: 1185
数据实际大小: 1024 #循环一次打印一次(第一次打印)
数据实际大小: 1390 #循环一次打印一次(第二次打印)
cmd res receive done.... 1390
#当设置只要received_size小于cmd_res_size时,就会接收数据,所以client这里现在就不会卡在接收数据这里了。
修改client端:
import socket
client = socket.socket()
client.connect(('localhost',9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0:continue
client.send(cmd.encode('utf-8'))
cmd_res_size = client.recv(1024)
# cmd_res = client.recv(1024)
print ('接收结果总大小:',cmd_res_size.decode())
received_size = 0
received_data = b'' #设置一个空的数据内容变量
while received_size < int(cmd_res_size.decode()):
data = client.recv(1024)
received_size += len(data)
received_data += data #将每次接收过来的数据内容相加
else:
print ("cmd res receive done....",received_size)
print (received_data.decode('gbk')) #在这里打印接收到的数据
client.close()
client执行结果:
>>:ipconfig
接收结果总大小: 1185
cmd res receive done.... 1390
#这里长度不一致的原因是中文导致的,因为在server端的conn.send(str(len(cmd_res)).encode('utf-8'))这行代码中(len(cmd_res)这里是先进行len的操作后才去.encode; 然后发送给client的数据长度是.encode后的数据长度; .encode之前判断的是字符的长度,但.encode之后计算的长度就不一样了。
Windows IP 配置
无线局域网适配器 WLAN:
媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :
无线局域网适配器 本地连接* 1:
媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :
以太网适配器 以太网:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::141a:83:bbf0:3c7%2
IPv4 地址 . . . . . . . . . . . . : 10.213.36.2
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . : 10.213.36.250
以太网适配器 VMware Network Adapter VMnet1:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::e08f:e633:7d98:5a39%20
IPv4 地址 . . . . . . . . . . . . : 192.168.52.1
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . :
以太网适配器 VMware Network Adapter VMnet8:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::318f:38e3:25d9:c249%11
IPv4 地址 . . . . . . . . . . . . : 192.168.142.1
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . :
以太网适配器 蓝牙网络连接:
媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :
隧道适配器 本地连接* 12:
媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :
修改server端:
import socket,os
server = socket.socket()
server.bind(('localhost',9999))
server.listen()
while True:
conn,addr = server.accept()
print ('new conn:',addr)
while True:
print ("等待新指令")
data = conn.recv(1024)
if not data:
print ('客户端已断开!')
break
print ('执行指令:',data)
cmd_res = os.popen(data.decode()).read()
print("before send", len(cmd_res))
if len(cmd_res) == 0:
cmd_res = 'cmd has no output......'
conn.send(str(len(cmd_res.encode())).encode('utf-8'))
#这里我们在cmd_res后面加了个.encode,封装后就将中文长度也计算了,然后在通过.encode('utf-8')封装成bytes数据类型传输。
#第一个encode是为了能够正确len计算大小,第二次encode是为了将字符串编程bytes类型(字符串不能被send)
conn.send(cmd_res.encode('utf-8'))
#这里可以看到封装使用了utf-8,这是因为client端使用了received_data = b'',client端将数据改为bytes类型后,client就可以通过received_data.decode()正常解码了。
print ("send done")
server.close()
修改client端:
import socket
client = socket.socket()
client.connect(('localhost',9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0:continue
client.send(cmd.encode('utf-8'))
cmd_res_size = client.recv(1024)
# cmd_res = client.recv(1024)
print ('接收结果总大小:',cmd_res_size.decode())
received_size = 0
received_data = b'' #设置一个空的数据内容变量
while received_size < int(cmd_res_size.decode()):
data = client.recv(1024)
received_size += len(data)
received_data += data
else:
print ("cmd res receive done....",received_size)
print (received_data.decode())
#因为received_data = b'',所以解码的时候就不要在decode为gbk了。
client.close()
client执行结果:
>>:ipconfig
接收结果总大小: 1595
cmd res receive done.... 1595
#可以看到这会数据长度就一致了。
Windows IP 配置
无线局域网适配器 WLAN:
媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :
无线局域网适配器 本地连接* 1:
媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :
以太网适配器 以太网:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::141a:83:bbf0:3c7%2
IPv4 地址 . . . . . . . . . . . . : 10.213.36.2
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . : 10.213.36.250
以太网适配器 VMware Network Adapter VMnet1:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::e08f:e633:7d98:5a39%20
IPv4 地址 . . . . . . . . . . . . : 192.168.52.1
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . :
以太网适配器 VMware Network Adapter VMnet8:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::318f:38e3:25d9:c249%11
IPv4 地址 . . . . . . . . . . . . : 192.168.142.1
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . :
以太网适配器 蓝牙网络连接:
媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :
隧道适配器 本地连接* 12:
媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :
粘包
使用同样的代码在linux中运行,结果就报错了; 图中可以看到在打印长度后面紧跟着数据内容。
server端:
conn.send(str(len(cmd_res.encode())).encode('utf-8'))
conn.send(cmd_res.encode('utf-8'))
#之所以在linux报错,是因为上面两行代码粘在一起发送了(数据长度和数据内容一起发送了),这种情况俗称socket粘包。
#造成粘包的原因是因为这两行代码上下邻着的,缓冲区会将这两行代码当做一条代码一起给发送出去。
修改server端:
import socket,os,time
server = socket.socket()
server.bind(('localhost',9999))
server.listen()
while True:
conn,addr = server.accept()
print ('new conn:',addr)
while True:
print ("等待新指令")
data = conn.recv(1024)
if not data:
print ('客户端已断开!')
break
print ('执行指令:',data)
cmd_res = os.popen(data.decode()).read()
print("before send", len(cmd_res))
if len(cmd_res) == 0:
cmd_res = 'cmd has no output......'
conn.send(str(len(cmd_res.encode())).encode('utf-8'))
time.sleep(0.01) #将两个代码之间用其他代码隔离开就解决粘包的问题了
conn.send(cmd_res.encode('utf-8'))
print ("send done")
server.close()
client执行结果:
>>:ifconfig
接收结果总大小: 969
cmd res receive done.... 969
ens32 Link encap:以太网 硬件地址 00:0c:29:c2:d0:8c
inet 地址:192.168.142.128 广播:192.168.142.255 掩码:255.255.255.0
inet6 地址: fe80::a734:95ce:c197:a382/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 跃点数:1
接收数据包:495949 错误:0 丢弃:0 过载:0 帧数:0
发送数据包:210917 错误:0 丢弃:0 过载:0 载波:0
碰撞:0 发送队列长度:1000
接收字节:560538040 (560.5 MB) 发送字节:13121792 (13.1 MB)
lo Link encap:本地环回
inet 地址:127.0.0.1 掩码:255.0.0.0
inet6 地址: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 跃点数:1
接收数据包:1619 错误:0 丢弃:0 过载:0 帧数:0
发送数据包:1619 错误:0 丢弃:0 过载:0 载波:0
碰撞:0 发送队列长度:1
接收字节:130666 (130.6 KB) 发送字节:130666 (130.6 KB)
>>:
#从执行结果中可以看到没有在出现粘包的问题。
#不过不建议使用sleep,假如在某些时时要求比较高的环境下,这样会影响程序的运行;而且使用sleep的话,如果时间过短还是可能会出现粘包的现象。
修改server端:
import socket,os,time
server = socket.socket()
server.bind(('localhost',9999))
server.listen()
while True:
conn,addr = server.accept()
print ('new conn:',addr)
while True:
print ("等待新指令")
data = conn.recv(1024)
if not data:
print ('客户端已断开!')
break
print ('执行指令:',data)
cmd_res = os.popen(data.decode()).read()
print("before send", len(cmd_res))
if len(cmd_res) == 0:
cmd_res = 'cmd has no output......'
conn.send(str(len(cmd_res.encode())).encode('utf-8'))
client_ack = conn.recv(1024) #等待客户端发送确认信息
#在这里我们多加一个server与client的交互,当server端send后,client端接收到数据后再发送一个确认信息给server端。
print (client_ack.decode())
conn.send(cmd_res.encode('utf-8'))
print ("send done")
server.close()
修改client端:
import socket
client = socket.socket()
client.connect(('localhost',9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0:continue
client.send(cmd.encode('utf-8'))
cmd_res_size = client.recv(1024)
print ('接收结果总大小:',cmd_res_size.decode())
client.send("Client ACK".encode('utf-8')) #这里定义发送确认信息
received_size = 0
received_data = b''
while received_size < int(cmd_res_size.decode()):
data = client.recv(1024)
received_size += len(data)
received_data += data
else:
print ("cmd res receive done....",received_size)
print (received_data.decode())
client.close()
socket发送文件
ftp server
1.读取文件名
2.检测文件是否存在
3.打开文件
4.检测文件大小
5.发送文件大小和md5给客户端
6.等客户端确认
7.开始边读边发数据
8.MD5
server端:
import socket,os,hashlib
server = socket.socket()
server.bind(('localhost',9999))
server.listen()
while True:
conn,addr = server.accept()
print ('new conn:',addr)
while True:
print ("等待新指令")
data = conn.recv(1024)
if not data:
print ('客户端已断开!')
break
cmd,filename = data.decode().split() #将客户端发送的指令get 和 文件名分开
# 假如客户端输入的是 get test.txt,那么当前filename就等于test.txt
print (filename)
if os.path.isfile(filename): #判断test.txt如果是一个文件
f = open(filename,"rb") #通过rb打开,接下来就不用在给encode成byte格式了
file_size = os.stat(filename).st_size #获取 文件大小
conn.send(str(file_size).encode('utf-8')) #发送文件大小(需要先转成字符串,才能encode)
conn.recv(1024) #等待收取确认包(ACK)
m = hashlib.md5()
for line in f:
conn.send(line) #一行一行的将数据发送出去
m.update(line)
# 将一行一行的数据进行MD5
# update的数据必须是byte格式才能进行MD5,不过当前f本身就是通过rb来读取的,所以当前就是byte格式
print ("file md5",m.hexdigest()) # 打印MD5值
# 当前m已经对每一行数据进行MD5了,所以当前发送的是最终的MD5值
# 通过hexdigest就是通过的字符串格式来显示MD5值
f.close()
print("send done")
server.close()
client端:
import socket
client = socket.socket()
client.connect(('localhost',9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0:continue
if cmd.startswith("get"):
client.send(cmd.encode('utf-8'))
server_response = client.recv(1024) #数据大小
print ("server response:",server_response)
client.send(b"ready to recv file") #回复确认包,防止粘包
file_total_size = int(server_response.decode()) #先decode,再int
received_size = 0
filename = cmd.split()[1]
#将get和文件名分开,然后将文件名赋值给filename
f = open(filename + ".new","wb") #将读取过来的数据,存储到一个新文件中(用于区别server和client的文件)
while received_size < file_total_size:
data = client.recv(1024) #一行一行数据接收
received_size += len(data)
f.write(data) #一行一行数据写入
print (file_total_size,received_size)
else:
print ("file recv done")
f.close()
client.close()
cient执行结果:
>>:get test.txt
server response: b'44688'
44688 1024
44688 2048
44688 3072
44688 4096
44688 5120
44688 6144
44688 7168
44688 8192
44688 9216
44688 10240
44688 11264
44688 12288
44688 13312
44688 14336
44688 15360
44688 16384
44688 17408
44688 18432
44688 19456
44688 20480
44688 21504
44688 22528
44688 23552
44688 24576
44688 25600
44688 26624
44688 27648
44688 28672
44688 29696
44688 30720
44688 31744
44688 32768
44688 33792
44688 34816
44688 35840
44688 36864
44688 37888
44688 38912
44688 39936
44688 40960
44688 41984
44688 43008
44688 44032
44688 44688
file recv done
>>:
server执行结果:
new conn: ('127.0.0.1', 64667)
等待新指令
test.txt
file md5 926394ca9dc106ff006da5027f45ebd3
send done
等待新指令
可以看到源文件与新文件大小相同
下面让客户端也计算MD5
server端:
import socket,os,hashlib
server = socket.socket()
server.bind(('localhost',9999))
server.listen()
while True:
conn,addr = server.accept()
print ('new conn:',addr)
while True:
print ("等待新指令")
data = conn.recv(1024)
if not data:
print ('客户端已断开!')
break
cmd,filename = data.decode().split()
print (filename)
if os.path.isfile(filename):
f = open(filename,"rb")
file_size = os.stat(filename).st_size
conn.send(str(file_size).encode('utf-8'))
conn.recv(1024)
m = hashlib.md5()
for line in f:
conn.send(line) #这里和下面send MD5容易粘包
m.update(line)
md5_value = m.hexdigest()
#print ("file md5",md5_value)
f.close()
conn.send(md5_value.encode('utf-8')) #发送MD5
#这里和上面send(line)容易粘包(因为临近)
print ('已发送MD5,请验证!')
print("send done")
server.close()
client端:
import socket,hashlib
client = socket.socket()
client.connect(('localhost',9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0:continue
if cmd.startswith("get"):
client.send(cmd.encode('utf-8'))
server_response = client.recv(1024)
print ("server response:",server_response)
client.send(b"ready to recv file")
file_total_size = int(server_response.decode())
received_size = 0
filename = cmd.split()[1]
f = open(filename + ".new","wb")
m = hashlib.md5()
while received_size < file_total_size:
data = client.recv(1024)
received_size += len(data)
m.update(data)
f.write(data)
# print (file_total_size,received_size)
else:
print ("file recv done",received_size,file_total_size)
f.close()
new_file_md5 = m.hexdigest()
server_file_md5 = client.recv(1024)
print ("server file md5:",server_file_md5.decode())
print ("client file md5:",new_file_md5)
client.close()
client执行结果:
注意:received_size和file_total_size值不一致
可以看到当前client端已经卡住了,卡在server_file_md5 = client.recv(1024)这里,这是因为server端粘包了,将MD5信息提前发送给了client;
这里可能认为通过client端的while received_size < file_total_size:这个条件,received_size小于file_total_size时就不会在接收了,也的确是这样,但是要注意的是,在client最后一次循环接收数据时,received_size也是小于file_total_size的,可是server是将粘包的数据一起发送过来的,刚好就大于了file_total_size。
比如:client正常接收的数据是5000,server端此时已经发送了480还剩200正常数据没发,此时client最后一次循环接收数据,可是server端因为粘包的问题将最后正常的200数据+300的MD5数据一起发给了client,此时client接收的数据总数达到了5300,超了300。
最后因为MD5是通过粘包的情况已经发送给client并写入新的文档中了,所以client端就一直卡在server_file_md5 = client.recv(1024)接收数据这里。
上图中可以看到因为粘包的问题,client已经将MD5这个值写入到新文件中了,所以后面client不会再接收到MD5数据了。
修改client端:
import socket,hashlib
client = socket.socket()
client.connect(('localhost',9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0:continue
if cmd.startswith("get"):
client.send(cmd.encode('utf-8'))
server_response = client.recv(1024)
print ("server response:",server_response)
client.send(b"ready to recv file")
file_total_size = int(server_response.decode())
received_size = 0
filename = cmd.split()[1]
f = open(filename + ".new","wb")
m = hashlib.md5()
while received_size < file_total_size:
if file_total_size - received_size > 1024: #符合此条件说明还要接收不止一次数据
size = 1024
else:
size = file_total_size - received_size #符合此条件表示正常数据还剩余多少
data = client.recv(size) #这里就通过size来判断还要接收多少正常的数据
received_size += len(data)
m.update(data)
f.write(data)
# print (file_total_size,received_size)
else:
print ("file recv done",received_size,file_total_size)
f.close()
new_file_md5 = m.hexdigest()
server_file_md5 = client.recv(1024)
print ("server file md5:",server_file_md5.decode())
print ("client file md5:",new_file_md5)
client.close()
执行结果:
>>:get test.txt
server response: b'44688'
file recv done 44688 44688
server file md5: 926394ca9dc106ff006da5027f45ebd3
client file md5: 926394ca9dc106ff006da5027f45ebd3
>>:
#可以看到这回就没有出现粘包的情况,client端正常接收数据。
当前存在一个问题就是MD5拖慢了整个进程的速度,只要使用MD5的话就会耗费一定的时间,而且在这里我们是每读取一行数据,就进行一次MD5的计算。
有时会出现address is in use的报错情况,可以在server上使用下面代码解决
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
不使用的话,就需要等待1分钟左右的时间才会恢复。