Python socket网络编程
TCP和UDP对比
TCP(Transmission Control Protocol)
可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;文件传输程序。
UDP(User Datagram Protocol)
不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文(数据包),尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。
socket 模块
Python 提供了两个基本的 socket 模块。
- 第一个是 Socket,它提供了标准的 BSD Sockets API。
- 第二个是 SocketServer, 它提供了服务器中心类,可以简化网络服务器的开发。
socket 类型
socket类型 | 说明 |
---|---|
socket.AF_UNIX | 只能够用于单一的Unix系统进程间通信 |
socket.AF_INET | 服务器之间网络通信 |
socket.AF_INET6 | IPv6 |
socket.SOCK_STREAM | 流式socket,应用于TCP |
socket.SOCK_DGRAM | 数据报式socket,应用于UDP |
socket.SOCK_RAW | 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。 |
socket.SOCK_SEQPACKET | 可靠的连续数据包服务 |
创建 socket 方法
创建TCP socket | s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) |
---|---|
创建UDP socket | s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) |
socket 函数
注意点:
- TCP发送数据时,已建立好TCP连接,所以不需要指定地址。UDP是面向无连接的,每次发送都要指定发给谁。
- 服务端与客户端不能直接发送列表,元组,字典。需要字符串化 repr(data)。
服务端 socket 函数
socket 函数 | 说明 |
---|---|
s.bind(address) | 将套接字绑定到地址,在AF_INET下,以元组(host, port)的形式表示地址。 |
s.listen(backlog) | 开始监听TCP传入连接。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。 |
s.accept() | 接受TCP连接并返回(conn, address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。 |
客户端 socket 函数
socket 函数 | 说明 |
---|---|
s.connect(address) | 连接到address处的套接字。一般address的格式为元组(hostname, port),如果连接出错,返回socket.error错误。 |
s.connect_ex(adddress) | 功能与connect(address)相同,但是成功返回0,出错时返回出错码,而不是抛出异常。 |
公共 socket 函数
socket 函数 | 说明 |
---|---|
s.recv(bufsize[,flag]) | 接受TCP套接字的数据。数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。 |
s.send(string[,flag]) | 发送TCP数据。将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。 |
s.sendall(string[,flag]) | 完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。 |
s.recvfrom(bufsize[,flag]) | 接受UDP套接字的数据。与recv()类似,但返回值是(data, address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。 |
s.sendto(string[,flag],address) | 发送UDP数据。将数据发送到套接字,address是形式为(ipaddr, port)的元组,指定远程地址。返回值是发送的字节数。 |
s.close() | 关闭套接字。 |
s.getpeername() | 返回连接套接字的远程地址。返回值通常是元组(ipaddr, port)。 |
s.getsockname() | 返回套接字自己的地址。通常是一个元组(ipaddr, port)。 |
s.setsockopt(level,optname,value) | 设置给定套接字选项的值。 |
s.getsockopt(level,optname[,buflen]) | 返回套接字选项的值。 |
s.settimeout(timeout) | 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如:connect())。 |
s.gettimeout() | 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。 |
s.fileno() | 返回套接字的文件描述符。 |
s.setblocking(flag) | 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。 |
s.makefile() | 创建一个与该套接字相关连的文件。 |
socket 编程(TCP)
流程
TCP服务端:
- 创建套接字,绑定套接字到本地IP与端口 #socket.socket(socket.AF_INET,socket.SOCK_STREAM) , s.bind()
- 开始监听连接 #s.listen()
- 进入循环,不断接受客户端的连接请求 #s.accept()
- 然后接收传来的数据,并发送给对方数据 #s.recv() , s.sendall()
- 传输完毕后,关闭套接字 #s.close()
TCP客户端:
- 创建套接字,连接远端地址 # socket.socket(socket.AF_INET,socket.SOCK_STREAM) , s.connect()
- 连接后发送数据和接收数据 # s.sendall(), s.recv()
- 传输完毕后,关闭套接字 #s.close()
案例1:连接循环通信
# TCP服务端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.bind(('127.0.0.1', 8080)) # 127.0.0.1是每一台电脑的默认ip地址,代表着本机地址
phone.listen(5)
conn, client_addr = phone.accept()
print(conn, client_addr, sep='\n')
while 1:
try:
from_client_data = conn.recv(1024)
print('来自客户端的消息:', from_client_data.decode('utf-8'))
server_data = input('请输入要回复的消息:')
conn.send(server_data.encode('utf-8'))
except ConnectionResetError:
break
conn.close()
phone.close()
# TCP客户端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while 1:
try:
client_data = input('>>> ')
phone.send(client_data.encode('utf-8'))
from_server_data = phone.recv(1024)
print('来自服务器的回应', from_server_data.decode('utf-8'))
except ConnectionResetError:
break
phone.close()
案例2:远程连接执行命令
模拟类似ssh连接远程服务器
# 服务器
import socket
import subprocess
SEVER = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
SEVER.bind(('127.0.0.1', 8080))
SEVER.listen(5)
while 1: # 循环连接客户端
conn, client_addr = SEVER.accept()
print(client_addr)
while 1:
try:
cmd = conn.recv(1024) # 接受客户输入的命令
ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
correct_msg = ret.stdout.read()
error_msg = ret.stderr.read()
conn.send(correct_msg + error_msg) # 将标准输出和错误输出发送给客户端
except ConnectionResetError:
break
conn.close()
SEVER.close()
说明:subprocess.Popen类
通过调用:subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)
创建并返回一个子进程,并在这个进程中执行指定的程序。
实例化 Popen 可以通过许多参数详细定制子进程的环境,但是只有一个参数是必须的,即位置参数 args 。
参数介绍:
- args:需要执行的系统命令,可为字符串序列(列表或元组,shell为默认值False即可,建议为序列),也可为字符串(使用字符串时,需将shell赋值为True);
- shell:默认为False,若args为序列时,shell=False;若args为字符串时,shell=True,表示通过shell执行命令;
- stdout、stdin、stderr:分别表示子程序标准输出、标准输入、标准错误,可为subprocess.PIPE、一个有效的文件描述符、文件对象或None。
若为subprocess.PIPE:代表打开通向标准流的管道,创建一个新的管道;
若为None:表示没有任何重定向,子进程会继承父进程;
stderr也可为subprocess.STDOUT:表示将子程序的标准错误输出重定向到了标准输出 - bufsize:指定缓冲策略,0表示不缓冲,1表示行缓冲,其它整数表示缓冲区大小,负数表示使用系统默认值0;
- cwd:默认值为None;若非None,则表示将会在执行这个子进程之前改变当前工作目录;
- env:用于指定子进程的环境变量。若env为None,那么子进程的环境变量将从父进程中继承;若env非None,则表示子程序的环境变量由env值来设置,它的值必须是一个映射对象。
- universal_newlines: 不同系统的换行符不同。若True,则该文件对象的stdin,stdout和stderr将会以文本流方式打开;否则以二进制流方式打开。
# 客户端
import socket
SEVER = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
SEVER.connect(('127.0.0.1', 8080)) # 与客户端建立连接
while 1:
client_order = input('>>>') # 将输入的命令存到client_order
SEVER.send(client_order.encode('utf-8'))
if client_order == 'q':
break
from_server_data = SEVER.recv(1024)
print(from_server_data.decode('gbk')) # windows命令行下默认字符编码为gbk
SEVER.close()
socket 编程(UDP)
流程
udp是无连接的,先启动哪一端都不会报错
服务器端先初始化Socket,然后与端口绑定(bind),recvform接收消息,这个消息有两项,消息内容和对方客户端的地址,然后回复消息时也要带着你收到的这个客户端的地址,发送回去,最后关闭连接,一次交互结束
案例1:简单的接受和发送
# 服务端
import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM) # 创建一个服务器的套接字
udp_sk.bind(('127.0.0.1', 9000)) # 绑定服务器套接字
msg, addr = udp_sk.recvfrom(1024)
print(msg.decode('utf-8'))
udp_sk.sendto(b'hi', addr) # 对话(接收与发送)
udp_sk.close() # 关闭服务器套接字
# 客户端
import socket
ip_port = ('127.0.0.1', 9000)
udp_sk = socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello', ip_port)
back_msg, addr = udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'), addr)
案例2:循环通信
类似于QQ聊天
# 服务端
import socket
ip_port = ('127.0.0.1', 8081)
udp_server_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # DGRAM:datagram 数据报文的意思,象征着UDP协议的通信方式
udp_server_sock.bind(ip_port) # 你对外提供服务的端口就是这一个,所有的客户端都是通过这个端口和你进行通信的
while True:
qq_msg, addr = udp_server_sock.recvfrom(1024) # 阻塞状态,等待接收消息
print('来自[%s:%s]的一条消息:\033[1;34;43m%s\033[0m' % (addr[0], addr[1], qq_msg.decode('utf-8')))
back_msg = input('回复消息: ').strip()
udp_server_sock.sendto(back_msg.encode('utf-8'), addr)
# 客户端
import socket
BUFSIZE = 1024
udp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
qq_name_dic = {
'xiaohan': ('127.0.0.1', 8081),
'ali': ('127.0.0.1', 8081),
'hengheng': ('127.0.0.1', 8081),
}
for i in qq_name_dic:
print(i)
while True:
# 选择聊天对象
while 1:
qq_name = input('请选择聊天对象: ').strip()
if qq_name not in qq_name_dic:
print('没有这个聊天对象')
continue
break
# 聊天过程
while True:
msg_receiver = '发给' + qq_name + ': '
input_msg = input('请输入消息,回车发送,输入q结束和他的聊天: ').strip()
if input_msg == 'q':
break
if not input_msg:
continue
udp_client_socket.sendto(msg_receiver.encode('utf-8'), qq_name_dic[qq_name]) # 必须带着自己的地址,这就是UDP
# 与TCP不一样的地方:不需要建立连接,但是要带着自己的地址给服务端,否则服务端无法判断是谁给我发的消息,并且不知道该把消息回复到什么地方,因为我们之间没有建立连接通道
back_msg, addr = udp_client_socket.recvfrom(BUFSIZE) # 同样也是阻塞状态,等待接收消息
print('来自[%s:%s]的一条消息:\033[1;34;43m%s\033[0m' % (addr[0], addr[1], back_msg.decode('utf-8')))
udp_client_socket.close()
案例3:自制查询当前日期服务器
# 服务端
from socket import *
from time import strftime
import time
ip_port = ('127.0.0.1', 9000)
bufsize = 1024
tcp_server = socket(AF_INET, SOCK_DGRAM)
tcp_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
tcp_server.bind(ip_port)
while True:
msg, addr = tcp_server.recvfrom(bufsize)
print('===>', msg.decode('utf-8'))
stru_time = time.localtime() # 当前的结构化时间
if not msg:
time_fmt = '%Y-%m-%d %X'
else:
time_fmt = msg.decode('utf-8')
back_msg = strftime(time_fmt, stru_time)
print(back_msg, type(back_msg))
tcp_server.sendto(back_msg.encode('utf-8'), addr)
tcp_server.close()
# 客户端
from socket import *
ip_port = ('127.0.0.1', 9000)
bufsize = 1024
tcp_client = socket(AF_INET, SOCK_DGRAM)
while True:
msg = input('请输入日期格式(例%Y %m %d)>>: ').strip()
tcp_client.sendto(msg.encode('utf-8'), ip_port)
data = tcp_client.recv(bufsize)
print('当前日期:', str(data, encoding='utf-8'))
粘包
什么是粘包?
粘包:多个数据包被连续存储于连续的缓存中,在对数据包进行读取时由于无法确定发生方的发送边界,而采用某一估测值大小来进行数据读出,若双方的size不一致时就会使指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。
write()/send() 并不会立即向网络中传输数据,而是先将数据写入缓冲区中,等到数据达到一定量时,再由TCP协议将数据从缓冲区发送到目标机器,这样做可以防止浪费资源。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些就是TCP协议负责的事情。
TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。
read()/recv() 函数也是如此,也是从输入缓冲区中读取数据,而不是直接从网络中读取数据。
这些I/O缓冲区特性可整理如下:
- I/O缓冲区在每个TCP套接字中单独存在;
- I/O缓冲区在创建套接字时自动生成;
- 即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
- 关闭套接字将丢失输入缓冲区中的数据。
两种情况下会产生粘包
- 接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还会从缓冲区拿上次遗留的数据,产生粘包)
示例1
# 服务端
import socket
import subprocess
SEVER = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
SEVER.bind(('127.0.0.1', 8080))
SEVER.listen(5)
while 1: # 循环连接客户端
conn, client_addr = SEVER.accept()
print(client_addr)
while 1:
try:
cmd = conn.recv(1024)
ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
correct_msg = ret.stdout.read()
error_msg = ret.stderr.read()
conn.send(correct_msg + error_msg)
except ConnectionResetError:
break
conn.close()
SEVER.close()
# 客户端
import socket
SEVER = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
SEVER.connect(('127.0.0.1', 8080)) # 与客户端建立连接
while 1:
cmd = input('>>>')
SEVER.send(cmd.encode('utf-8'))
from_server_data = SEVER.recv(1024)
print(from_server_data.decode('gbk'))
SEVER.close()
客户端按下回车键可以继续接收缓冲区里的包。
- 发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据也很小,会粘合到一起,产生粘包)
示例2
# 服务端
import socket
SEVER = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
SEVER.bind(('127.0.0.1', 8080))
SEVER.listen(5)
conn, client_addr = SEVER.accept()
frist_data = conn.recv(1024)
print('1:', frist_data.decode('utf-8')) # 1: helloworld
second_data = conn.recv(1024)
print('2:', second_data.decode('utf-8')) # 2:
conn.close()
SEVER.close()
# 客户端
import socket
SEVER = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
SEVER.connect(('127.0.0.1', 8080))
SEVER.send(b'hello')
SEVER.send(b'world')
SEVER.close()
客户端第一次发送“hello”,第二次发送“world”,但是操作系统将客户端两次发送的数据粘合到一起发给服务端了,导致服务端第一次接收到了“helloworld”,第二次没有数据接可以收了。
粘包的解决方案
方案一:利用 struct 模块
struct 模块简介
该模块可以把一个类型,如数字、字符串、json等,转成固定长度的bytes。
import struct
# 将一个数字转化成等长度的bytes类型。
ret = struct.pack('i', 183346) # i表示integer
print(ret, type(ret), len(ret))
# 通过unpack反解回来
ret1 = struct.unpack('i', ret)[0] # 返回一个元组,0索引是这个数字
print(ret1, type(ret1))
示例
# 服务端
import socket
import subprocess
import struct
SEVER = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
SEVER.bind(('127.0.0.1', 8080))
SEVER.listen(5)
while 1:
conn, client_addr = SEVER.accept()
print(client_addr)
while 1:
try:
cmd = conn.recv(1024)
ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
correct_msg = ret.stdout.read()
error_msg = ret.stderr.read()
# 1 制作固定报头
total_size = len(correct_msg) + len(error_msg)
header = struct.pack('i', total_size)
# 2 发送报头
conn.send(header)
# 3 发送真实数据
conn.send(correct_msg)
conn.send(error_msg)
except ConnectionResetError:
break
conn.close()
SEVER.close()
# 客户端
import socket
import struct
SEVER = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
SEVER.connect(('127.0.0.1', 8080))
while 1:
cmd = input('>>>').strip()
if not cmd: continue
SEVER.send(cmd.encode('utf-8'))
# 1 接收固定报头
header = SEVER.recv(4)
# 2 解析报头
total_size = struct.unpack('i', header)[0]
# 3 根据报头信息,接收真实数据
recv_size = 0
res = b''
# 循环接收,一次接收1024长度。如果1024长度不够达到数据长度,再次接收,直到数据都接收完为止
while recv_size < total_size:
recv_data = SEVER.recv(1024)
res += recv_data
recv_size += len(recv_data)
print(res.decode('gbk'))
SEVER.close()
通过struct模块直接处理数据,不可以处理太大的数据。
方案二:可自定制报头
整个流程的大致解释:
我们可以把报头做成字典,字典里包含将要发送的真实数据的描述信息,例如大小等;然后json序列化,并用struct将序列化后的数据长度打包成4个字节。
我们在网络上传输的所有数据都叫做数据包,数据包里的所有数据都叫做报文,报文里面不止有你的数据,还有ip地址、mac地址、端口号等信息。其实所有的报文都有报头,这个报头是协议规定的。
示例
# 服务端
import socket
import subprocess
import struct
import json
SEVER = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
SEVER.bind(('127.0.0.1', 8080))
SEVER.listen(5)
while 1:
conn, client_addr = SEVER.accept()
print(client_addr)
while 1:
try:
cmd = conn.recv(1024)
ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
correct_msg = ret.stdout.read()
error_msg = ret.stderr.read()
# 1 制作固定报头
total_size = len(correct_msg) + len(error_msg)
header_dict = {
'md5': 'fdsaf2143254f',
'file_name': 'f1.txt',
'total_size': total_size,
}
header_dict_json = json.dumps(header_dict) # 序列化为json
bytes_headers = header_dict_json.encode('utf-8')
header_size = len(bytes_headers)
header = struct.pack('i', header_size) # 把报头pack成固定的4个长度的内容
# 2 发送报头长度
conn.send(header)
# 3 发送报头数据
conn.send(bytes_headers)
# 4 发送真实数据
conn.send(correct_msg)
conn.send(error_msg)
except ConnectionResetError:
break
conn.close()
SEVER.close()
# 客户端
import socket
import struct
import json
SEVER = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
SEVER.connect(('127.0.0.1', 8080))
while 1:
cmd = input('>>>').strip()
if not cmd: continue
SEVER.send(cmd.encode('utf-8'))
# 1 接收固定长度的报头的内容,unpack出报头的长度
header_size = struct.unpack('i', SEVER.recv(4))[0]
# 2 接收报头对应长度的数据
header_bytes = SEVER.recv(header_size)
header_dict = json.loads(header_bytes.decode('utf-8'))
# 3 得到报头中写的数据总长度的信息
total_size = header_dict['total_size']
# 4 根据报头信息,接收报头中写的长度的真实数据
recv_size = 0
res = b''
while recv_size < total_size:
recv_data = SEVER.recv(1024)
res += recv_data
recv_size += len(recv_data)
print(res.decode('gbk'))
SEVER.close()
应用:FTP上传下载文件(简单版)
# 服务端
import socket
import struct
import json
sk = socket.socket()
# buffer = 4096 # 当双方的这个接收发送的大小比较大的时候,就像这个4096,就会丢数据。
buffer = 1024 # 每次接收数据的大小
sk.bind(('127.0.0.1', 8090))
sk.listen()
conn, addr = sk.accept()
# 接收
head_len = conn.recv(4)
head_len = struct.unpack('i', head_len)[0] # 解包
json_head = conn.recv(head_len).decode('utf-8') # 反序列化
head = json.loads(json_head)
filesize = head['filesize']
with open(head['filename'], 'wb') as f:
while filesize:
if filesize >= buffer: # >=是因为如果刚好等于的情况出现也是可以的。
content = conn.recv(buffer)
f.write(content)
filesize -= buffer
else:
content = conn.recv(buffer)
f.write(content)
break
conn.close()
sk.close()
# 客户端
import os
import json
import socket
import struct
sk = socket.socket()
sk.connect(('127.0.0.1', 8090))
buffer = 1024 # 读取文件的时候,每次读取的大小
head = {
'filepath': r'C:\Users\86158\Desktop\新建文件夹', # 需要下载的文件路径,也就是文件所在的文件夹
'filename': 'config.txt', # 改成上面filepath下的一个文件
'filesize': None,
}
file_path = os.path.join(head['filepath'], head['filename'])
filesize = os.path.getsize(file_path)
head['filesize'] = filesize
# json_head = json.dumps(head,ensure_ascii=False) #字典转换成字符串
json_head = json.dumps(head) # 字典转换成字符串
bytes_head = json_head.encode('utf-8') # 字符串转换成bytes类型
print(json_head)
print(bytes_head)
# 计算head的长度,因为接收端要先接收我们自己定制的报头
head_len = len(bytes_head) # 报头长度
pack_len = struct.pack('i', head_len)
print(head_len)
print(pack_len)
sk.send(pack_len) # 先发送报头长度
sk.send(bytes_head) # 再发送bytes类型的报头
# 即便是视频文件,也是可以按行来读取的,也可以readline,也可以for循环,但是读取出来的数据大小就不固定了,影响效率,有可能读的比较小,也可能很大,像视频文件一般都是一行的二进制字节流。
# 所有我们可以用read,设定一个一次读取内容的大小,一边读一边发,一边收一边写
with open(file_path, 'rb') as f:
while filesize:
if filesize >= buffer: # >=是因为如果刚好等于的情况出现也是可以的。
content = f.read(buffer) # 每次读取出来的内容
sk.send(content)
filesize -= buffer # 每次减去读取的大小
else: # 那么说明剩余的不够一次读取的大小了,那么只要把剩下的读取出来发送过去就行了
content = f.read(filesize)
sk.send(content)
break
sk.close()
上面的代码就是将C:\Users\86158\Desktop\新建文件夹中的config.txt上传到当前文件夹下。