Python——网络编程(二)socket进阶1

#单次消息多次收发C/S 小程序(含退出指令)

 1 from socket import *
 2 
 3 ip_port = ('222.195.137.208', 8000)
 4 back_log = 5
 5 buffer_size = 1024
 6 
 7 service = socket(AF_INET, SOCK_STREAM)
 8 service.bind(ip_port)
 9 service.listen(back_log)
10 print('客户端连接中...')
11 conn, addr = service.accept()
12 print('连接成功!')
13 while True:
14     msg = conn.recv(buffer_size)
15     recv_msg = msg.decode('utf8')
16 
17     print('客户端发来的信息:\n%s' %recv_msg)
18     if recv_msg == 'exit':
19         print('断开连接')
20         break
21     msg1 = input('>>')
22     if msg1 == 'exit':
23         print('断开连接')
24         conn.send('exit'.encode('utf8'))
25         break
26 
27     send_msg = msg1.encode('utf8')
28     conn.send(send_msg)
29 
30 conn.close()
31 service.close()
服务端
 1 from socket import *
 2 
 3 ip_port = ('222.195.137.208', 8000)
 4 buffer_size = 1024
 5 
 6 client = socket(AF_INET, SOCK_STREAM)
 7 print('连接中...')
 8 client.connect(ip_port)
 9 print('连接成功!')
10 
11 while True:
12     send_msg = input('请输入要发送内容:\n>>')
13     client.send(send_msg.encode('utf8'))
14 
15     msg = client.recv(buffer_size)
16     recv_msg = msg.decode('utf8')
17     if recv_msg == 'exit' or send_msg == 'exit':
18         print('断开连接')
19         break
20     print('收到消息:\n%s' %recv_msg)
21 
22 client.close()
客户端

 #socket收发消息原理

应用端 —> socket —> 用户态内存 —> 内核态内存(OS) <—> 内核态内存(OS) —> 用户态内存 —> socket —> 应用端 

                                 服务端                              用户端              

#堆栈

堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除。

要点:堆:顺序随意      栈:后进先出(Last-In/First-Out)

 #队列

①队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

②队列中没有元素时,称为空队列。

③建立顺序队列结构必须为其静态分配或动态申请一片连续的存储空间,并设置两个指针进行管理。一个是队头指针front,它指向队头元素;另一个是队尾指针rear,它指向下一个入队元素的存储位置。

④队列采用的FIFO(first in first out),新元素(等待进入队列的元素)总是被插入到链表的尾部,而读取的时候总是从链表的头部开始读取。每次读取一个元素,释放一个元素。所谓的动态创建,动态释放。因而也不存在溢出等问题。由于链表由结构体间接而成,遍历也方便。(先进先出)

#堆、栈、队列之间的区别是?

①堆是在程序运行时,而不是在程序编译时,申请某个大小的内存空间。即动态分配内存,对其访问和对一般内存的访问没有区别。

②栈就是一个桶,后放进去的先拿出来,它下面本来有的东西要等它出来之后才能出来。(后进先出)

③队列只能在队头做删除操作,在队尾做插入操作.而栈只能在栈顶做插入和删除操作。(先进先出)

#几个实际场景中的小问题:

1. 传输了空字符

无论是客户端还是服务端,输入空后加载到内核态内存里,会被认为没有信息而不发送出去

解决办法:

加入判断是否为空的语句,如if not msg:continue

2. 其中一方直接关闭了程序

相当于双向连接在没有四次挥手的情况下直接断了,在windows及linux下会报错;但某些系统如mac就不会报错,而是一直维持在接收空的状态,需要用if判断break出去

 如果是其中其中一端close掉了,那另一端会维持在接收空的状态(recv一直接收空)

 3. 多个用户同时连接同一个服务端

遵循队列的方式,谁先连接就先和谁建立连接;后面连接的放入半链接池中等待(等待建立连接),直到上一个客户端与服务端连接中断

 1 from socket import *
 2 
 3 ip_port = ('222.195.137.208', 8000)
 4 back_log = 5
 5 buffer_size = 1024
 6 
 7 service = socket(AF_INET, SOCK_STREAM)
 8 service.bind(ip_port)
 9 service.listen(back_log)
10 print('客户端连接中...')
11 
12 count = True
13 while count:
14     conn, addr = service.accept()
15     print('新用户连接成功!')
16     while True:
17         try:
18             msg = conn.recv(buffer_size)
19             recv_msg = msg.decode('utf8')
20             print('客户端发来的信息:\n%s' %recv_msg)
21 
22             msg1 = '我是复读机: %s' %recv_msg
23             send_msg = msg1.encode('utf8')
24             conn.send(send_msg)
25         except Exception as i:       #这里是为了防止其中某个客户端中断程序,服务端报错停止运行
26             print('用户中断')
27             break
28     conn.close()
29 service.close()      
服务端
 1 from socket import *
 2 
 3 ip_port = ('222.195.137.208', 8000)
 4 buffer_size = 1024
 5 
 6 client = socket(AF_INET, SOCK_STREAM)
 7 print('连接中...')
 8 client.connect(ip_port)
 9 print('连接成功!')
10 
11 while True:
12     send_msg = input('请输入要发送内容:\n>>')
13     client.send(send_msg.encode('utf8'))
14 
15     msg = client.recv(buffer_size)
16     recv_msg = msg.decode('utf8')
17     if send_msg == 'exit':
18         print('断开连接')
19         break
20     print('收到消息:\n%s' %recv_msg)
21 
22 client.close()
客户端(可以运行多个)

4. TCP中recv与send并不需要一一对应 ;一次recv只是接受指定字节大小的信息; UDP中则是一一对应

5. 重启服务端时可能会遇到

 

 这是由于你的服务端仍然存在四次挥手的time_wait状态在占用地址

1 #加入一条socket配置,重用ip和端口
2 
3 phone=socket(AF_INET,SOCK_STREAM)
4 phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
5 phone.bind(('127.0.0.1',8080))
解决方法1:设置重用addr
 1 发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决,
 2 vi /etc/sysctl.conf
 3 
 4 编辑文件,加入以下内容:
 5 net.ipv4.tcp_syncookies = 1
 6 net.ipv4.tcp_tw_reuse = 1
 7 net.ipv4.tcp_tw_recycle = 1
 8 net.ipv4.tcp_fin_timeout = 30
 9  
10 然后执行 /sbin/sysctl -p 让参数生效。
11  
12 net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
13 
14 net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
15 
16 net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
17 
18 net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间
解决方法2:快速清理掉占用的addr

6. send 的数据一次不要超过8196bytes 也就是8k,否则数据容易失真

因为网卡有一个最大的单次发送信息大小的值,若发送数据过大则会切片发送,到对端再组合;这样数据就容易组合错误而失真

7. TCP 中 sendall可以实现一直发送信息直到把这信息发送完毕

#基于Udp协议的客户端

易于实现并发,如QQ就是udp协议传输

 1 from socket import *
 2 
 3 ip_port = ('222.195.137.208', 8080)
 4 buffer_size = 1024
 5 
 6 udp_client = socket(AF_INET, SOCK_DGRAM)
 7 
 8 while True:
 9     msg = input('>>').strip()
10     if msg == 'exit':
11         break
12     udp_client.sendto(msg.encode('utf8'), ip_port)
13     msg_recv, ip_port_from = udp_client.recvfrom(buffer_size)
14     print(msg_recv.decode('utf8'))
udp客户端
 1 from socket import *
 2 
 3 ip_port = ('222.195.137.208', 8080)
 4 buffer_size = 1024
 5 
 6 #SOCK_DGRAM代表以数据报的套接字
 7 udp_service = socket(AF_INET, SOCK_DGRAM)
 8 udp_service.bind(ip_port)
 9 
10 while True:
11     recv_msg, ip_port_from = udp_service.recvfrom(buffer_size)
12     print(recv_msg.decode('utf8'))
13     send_msg = input('>>').encode('utf8').strip()
14     udp_service.sendto(send_msg, ip_port_from)
udp服务端

 一个小问题,tcp协议中收发消息如果为空,则会一直停留在缓冲区不会发送;但是udp中可以收发空消息(其实不是收发空消息,后续会解释)

即recv与recvfrom两个函数的区别

#ntp

NTP是网络时间同步协议,就是用来同步网络中各个计算机的时间的协议

以下利用udp协议简单的实现以下(不包含实现同步本地时间的功能)

其实就是服务端返回一个时间

 1 from socket import *
 2 import time
 3 ip_port = ('222.195.137.208', 8080)
 4 buffer_size = 1024
 5 
 6 #SOCK_DGRAM代表以数据报的套接字
 7 udp_service = socket(AF_INET, SOCK_DGRAM)
 8 udp_service.bind(ip_port)
 9 
10 while True:
11     recv_msg, ip_port_from = udp_service.recvfrom(buffer_size)
12     ndp_time = time.strftime('%Y-%m-%d %X')
13     send_msg = 'Ndp服务端标准时间为: %s'%ndp_time
14     udp_service.sendto(send_msg.encode('utf8'), ip_port_from)
ndp 服务端
 1 from socket import *
 2 
 3 ip_port = ('222.195.137.208', 8080)
 4 buffer_size = 1024
 5 
 6 udp_client = socket(AF_INET, SOCK_DGRAM)
 7 
 8 while True:
 9     msg = input('>>').strip()
10     if msg == 'exit':
11         break
12     udp_client.sendto(msg.encode('utf8'), ip_port)
13     msg_recv, ip_port_from = udp_client.recvfrom(buffer_size)
14     print(msg_recv.decode('utf8'))
ndp 客户端

 #基于TCP实现远程执行命令

其实还使用的是最基本的TCP框架,只是多了一步: 发送过去的消息执行后把信息传递回来

需要注意如果执行的命令没有返回值,发送为空则又会被卡主,需要判断处理

 1 from socket import *
 2 
 3 ip_port = ('222.195.137.208', 8000)
 4 buffer_size = 1024
 5 
 6 client = socket(AF_INET, SOCK_STREAM)
 7 print('连接中...')
 8 client.connect(ip_port)
 9 print('连接成功: %s!' %ip_port[0])
10 
11 while True:
12     cmd = input('请输入指令:\n>>')
13     #防止发送空消息导致客户端阻塞住
14     if not cmd: continue
15     if cmd == 'exit': break
16 
17     client.send(cmd.encode('utf8'))
18     msg = client.recv(buffer_size)
19     #注意对面执行命令如果是windows系统则执行后的结果是gbk编码过的字节形式,所以这里解码得用gbk
20     recv_msg = msg.decode('gbk')
21     print('执行结果:\n%s' %recv_msg)
22 
23 client.close()
客户端
 1 from socket import *
 2 import subprocess
 3 
 4 ip_port = ('222.195.137.208', 8000)
 5 back_log = 5
 6 buffer_size = 1024
 7 
 8 service = socket(AF_INET, SOCK_STREAM)
 9 service.bind(ip_port)
10 service.listen(back_log)
11 print('客户端连接中...')
12 
13 count = True
14 while count:
15     conn, addr = service.accept()
16     print('新用户连接成功: %s!' %addr[0])
17     while True:
18         try:
19             cmd = conn.recv(buffer_size)
20             if not cmd:
21                 print('用户中断')
22                 break
23             recv_cmd = cmd.decode('utf8')
24             print('收到客户端命令:\n%s' %recv_cmd)
25 
26             res = subprocess.Popen(recv_cmd, shell=True,
27                                    stdout=subprocess.PIPE,
28                                    stdin=subprocess.PIPE,
29                                    stderr=subprocess.PIPE)
30             err = res.stderr.read()
31             if err:
32                 send_msg = err
33             else:
34                 send_msg = res.stdout.read()
35             if not send_msg:
36                 send_msg = '此命令没有返回值'.encode('gbk')
37             conn.send(send_msg)
38         except Exception as i:
39             print('用户中断')
40             break
41     conn.close()
42 service.close()
服务端

但是还有个问题,你可以试试先用dir命令,然后在用ipconfig看看收到的值是什么?

这就是粘包现象

#粘包现象

 什么是粘包:

发送的字节流超出设定的一次信息携带的最大字节数,会留给下一个包携带;导致命令的结果缺失或多余。

注意只有TCP才会发生粘包现象,其原因在于:

首先我们来重新看一下UDP与TCP的定义:

  1. TCP(transport control protocol,传输控制协议)是面向连接的,面向流的(也就是字节流:只包含发送端发送的消息),提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
  2. UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的(也就是数据报:发送的信息中包含消息头),提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
  3. tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头

udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠

tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

粘包原因:

发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。

例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

 

基于udp不会发生粘包现象,如下:

 1 from socket import *
 2 
 3 ip_port = ('222.195.137.208', 8080)
 4 buffer_size = 2048
 5 
 6 udp_client = socket(AF_INET, SOCK_DGRAM)
 7 
 8 while True:
 9     msg = input('>>').strip()
10     if msg == 'exit':
11         break
12     udp_client.sendto(msg.encode('utf8'), ip_port)
13     msg_recv, ip_port_from = udp_client.recvfrom(buffer_size)
14     print(msg_recv.decode('gbk'))
客户端
 1 from socket import *
 2 import subprocess
 3 ip_port = ('222.195.137.208', 8080)
 4 buffer_size = 2048
 5 
 6 #SOCK_DGRAM代表以数据报的套接字
 7 udp_service = socket(AF_INET, SOCK_DGRAM)
 8 udp_service.bind(ip_port)
 9 
10 while True:
11     cmd, ip_port_from = udp_service.recvfrom(buffer_size)
12 
13     if not cmd:
14         print('用户中断')
15         break
16     recv_cmd = cmd.decode('utf8')
17     print('收到客户端命令:\n%s' % recv_cmd)
18 
19     res = subprocess.Popen(recv_cmd, shell=True,
20                            stdout=subprocess.PIPE,
21                            stdin=subprocess.PIPE,
22                            stderr=subprocess.PIPE)
23     err = res.stderr.read()
24     if err:
25         send_msg = err
26     else:
27         send_msg = res.stdout.read()
28     if not send_msg:
29         send_msg = '此命令没有返回值'.encode('gbk')
30     udp_service.sendto(send_msg, ip_port_from)
31 
32 udp_service.close()
服务端

在windows中,如果一次性发送的消息超过了单次消息的最大尺寸(也就是buffer_size)会报错如下:

 但是在linux中,不会报错,只是消息被截断了只传过去buffer_size的大小,其他消息全丢了;这也就是为什么说UDP不可靠,udp发出去的消息没人接收那就丢了。不信可以直接运行udp客户端,就算没服务端接收它照样能把消息发出去,只是没有人回应它罢了。

 可以通过设置比单次消息的尺寸大的buffer_size来解决 (小声说: TCP的粘包现象好像也能用这个简单解决,但是并非好的解决办法)

# 解决粘包

方法一(低端版):通过判断反复接收直到消息接收完毕

 1 from socket import *
 2 import subprocess
 3 
 4 ip_port = ('192.168.1.101', 8000)
 5 back_log = 5
 6 buffer_size = 1024
 7 
 8 service = socket(AF_INET, SOCK_STREAM)
 9 service.bind(ip_port)
10 service.listen(back_log)
11 print('客户端连接中...')
12 
13 count = True
14 while count:
15     conn, addr = service.accept()
16     print('新用户连接成功: %s!' %addr[0])
17     while True:
18         try:
19             cmd = conn.recv(buffer_size)
20             if not cmd:
21                 print('用户中断')
22                 break
23             recv_cmd = cmd.decode('utf8')
24             print('收到客户端命令:\n%s' %recv_cmd)
25 
26             res = subprocess.Popen(recv_cmd, shell=True,
27                                    stdout=subprocess.PIPE,
28                                    stdin=subprocess.PIPE,
29                                    stderr=subprocess.PIPE)
30             err = res.stderr.read()
31 
32             if err:
33                 send_msg = err
34             else:
35                 send_msg = res.stdout.read()
36             if not send_msg:
37                 send_msg = '此命令没有返回值'.encode('gbk')
38 
39             #解决粘包
40             conn.send(str(len(send_msg)).encode('utf8'))
41             recv_ready = conn.recv(buffer_size)
42             if recv_ready == b'ready':
43                 conn.send(send_msg)
44 
45         except Exception as i:
46             print('用户中断')
47             break
48     conn.close()
49 service.close()
服务端
 1 from socket import *
 2 
 3 ip_port = ('192.168.1.101', 8000)
 4 buffer_size = 1024
 5 
 6 client = socket(AF_INET, SOCK_STREAM)
 7 print('连接中...')
 8 client.connect(ip_port)
 9 print('连接成功: %s!' %ip_port[0])
10 
11 while True:
12     cmd = input('请输入指令:\n>>')
13 
14     if not cmd: continue
15     if cmd == 'exit': break
16     client.send(cmd.encode('utf8'))
17 
18     # 解决粘包
19     length = int(client.recv(buffer_size).decode('utf8'))
20     client.send(b'ready')
21 
22     recv_msg = b''
23     while len(recv_msg) < length:
24         recv_msg += client.recv(buffer_size)
25 
26 
27     msg = recv_msg.decode('gbk')
28     print('执行结果:\n%s' %msg)
29 
30 client.close()
客户端

 方法二: 通过struct模块封装消息头

 1 from socket import *
 2 import subprocess
 3 import struct
 4 ip_port = ('222.195.137.208', 8000)
 5 back_log = 5
 6 buffer_size = 1024
 7 
 8 service = socket(AF_INET, SOCK_STREAM)
 9 service.bind(ip_port)
10 service.listen(back_log)
11 print('客户端连接中...')
12 
13 count = True
14 while count:
15     conn, addr = service.accept()
16     print('新用户连接成功: %s!' %addr[0])
17     while True:
18         try:
19             cmd = conn.recv(buffer_size)
20             if not cmd:
21                 print('用户中断')
22                 break
23             recv_cmd = cmd.decode('utf8')
24             print('收到客户端命令:\n%s' %recv_cmd)
25 
26             res = subprocess.Popen(recv_cmd, shell=True,
27                                    stdout=subprocess.PIPE,
28                                    stdin=subprocess.PIPE,
29                                    stderr=subprocess.PIPE)
30             err = res.stderr.read()
31 
32             if err:
33                 send_msg = err
34             else:
35                 send_msg = res.stdout.read()
36             if not send_msg:
37                 send_msg = '此命令没有返回值'.encode('gbk')
38 
39             #解决粘包
40             length = len(send_msg)
41             #利用struct封装消息头
42             msg_struct = struct.pack('i', length)
43             conn.send(msg_struct)
44             conn.send(send_msg)
45 
46         except Exception as i:
47             print('用户中断')
48             break
49     conn.close()
50 service.close()
服务端
 1 from socket import *
 2 import struct
 3 
 4 ip_port = ('222.195.137.208', 8000)
 5 buffer_size = 1024
 6 
 7 client = socket(AF_INET, SOCK_STREAM)
 8 print('连接中...')
 9 client.connect(ip_port)
10 print('连接成功: %s!' %ip_port[0])
11 
12 while True:
13     cmd = input('请输入指令:\n>>')
14 
15     if not cmd: continue
16     if cmd == 'exit': break
17     client.send(cmd.encode('utf8'))
18 
19     # 解决粘包
20 
21     #获取消息头,也就是消息的长度
22     length_struct = client.recv(4)
23     length = struct.unpack('i', length_struct)[0]
24 
25     recv_msg = b''
26     while len(recv_msg) < length:
27         recv_msg += client.recv(buffer_size)
28 
29     msg = recv_msg.decode('gbk')
30     print('执行结果:\n%s' %msg)
31 
32 client.close()
客户端

#利用struct封装json字典作为报头,可以用作文件传输

 1 import json,struct
 2 #假设通过客户端上传1T:1073741824000的文件a.txt
 3 
 4 #为避免粘包,必须自定制报头
 5 header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T数据,文件路径和md5值
 6 
 7 #为了该报头能传送,需要序列化并且转为bytes
 8 head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并转成bytes,用于传输
 9 
10 #为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节
11 head_len_bytes=struct.pack('i',len(head_bytes)) #这4个字节里只包含了一个数字,该数字是报头的长度
12 
13 #客户端开始发送
14 conn.send(head_len_bytes) #先发报头的长度,4个bytes
15 conn.send(head_bytes) #再发报头的字节格式
16 conn.sendall(文件内容) #然后发真实内容的字节格式
17 
18 #服务端开始接收
19 head_len_bytes=s.recv(4) #先收报头4个bytes,得到报头长度的字节格式
20 x=struct.unpack('i',head_len_bytes)[0] #提取报头的长度
21 
22 head_bytes=s.recv(x) #按照报头长度x,收取报头的bytes格式
23 header=json.loads(json.dumps(header)) #提取报头
24 
25 #最后根据报头的内容提取真实的数据,比如
26 real_data_len=s.recv(header['file_size'])
27 s.recv(real_data_len)

 

 

 

posted @ 2019-11-26 19:27  Matrixssy  阅读(231)  评论(0编辑  收藏  举报