基于TCP协议使用socket分发大文件
案例:电影上传
思路:客户端端循环一行一行读文件并一行一行传输,服务端循环接收并写入文件
客户端代码
import socket,json,os,struct
client = cocket.socket()
client.connet(('127.0.0.1',8080))
while True:
# 获取电影类表,循环展示
MOVIE_DIR = r'D:\路径\视频'
movie_list = os.listdir(MOVIE_DIR)
for i,movie in enumerate(movie_list,1):
print(i,movie)
# 用户选择
choice = input('please choice movie to upload>>>:')
# 判断是否是数字
if choice.isdigit():
# 将字符串转为int
choice = in(choice) - 1
# 判断用户选择在不在列表范围内
if choice in range(0,len(movie_list))
# 获取用户想上传的文件路径
path = movie_list[choice]
# 拼接文件的绝对路径
file_path = os.path.join(MOVIE_DIR,path)
# 获取文件的大小
file_size = os.path.getsize(file_path)
# 定义一个字典
res_d = {
'file_name':'性感荷官在线发牌.,mp4',
'file_size':file_size,
'msg':'注意身体,多喝营养快线'
}
# 序列话字典
json_d = json.dunps(res_d)
json_bytes = json_d.encode('utf-8')
# 1.先制作字典格式的报头
header = struct.pack('i',len(json_bytes))
# 2.发送字典的报头
client.send(header)
# 3.再发字典
client.send(json_bytes)
# 4.再发文件数据(打开文件循环发送)
with open(file_path,r'b') as f:
for line in f:
client.send(line)
else:
print('not in rang')
else:
print('must be a number')
服务端代码
import socket,os,json,struct
server = socket,socket()
sever.bind(('127.0.0.1',8080))
server.listen(5)
while True:
conn,addr = server.accept()
while True:
try:
header_len = conn.recv(4)
# 解析字典报头
header_len = struct.unpack('i',hendaer_len)[0]
# 再接收字典数据
header_dic = conn.recv(header_len)
real_dic = json.loads(header_dic.decode('utf-8'))
# 获取数据长度
total_size = real_dic.get('file_size')
# 循环接受并写入文件
recv_size = 0
with open(real_dic.get('file_size'),'wb;) as f:
while recv_size < total_size:
data = conn.recv(1024)
f.write(data)
recv_size += len(data)
print('上传成功!')
except ConnetionResetError as e:
print(e)
break
conn.close()
UDP协议
UDP是一个简单的传输层协议。和TCP相比,UDP有下面几个显著特性:
1.UDP缺乏可靠性
UDP 本身不提供确认,序列号,超时重传等机制。UDP 数据报可能在网络中被复制,被重新排序。即 UDP 不保证数据报会到达其最终目的地,也不保证各个数据报的先后顺序,也不保证每个数据报只到达一次
2.UDP 数据报是有长度的
每个 UDP 数据报都有长度,如果一个数据报正确地到达目的地,那么该数据报的长度将随数据一起传递给接收方。而 TCP 是一个字节流协议,没有任何(协议上的)记录边界。
3.UDP 是无连接的
UDP 客户和服务器之前不必存在长期的关系。UDP 发送数据报之前也不需要经过握手创建连接的过程。
4.UDP 支持多播和广播
基于UDP协议的socket
dp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接
server端
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)
udp_sk.sendto(b'hi',addr) # 对话(接收与发送)
udp_sk.close() # 关闭服务器套接字
client端
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)
UDP不会发生黏包
UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。
不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
对于空消息:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp协议会帮你封装上消息头发送过去。
不可靠不黏包的udp协议:udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y;x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠。
粘包现象只发生在tcp协议中
1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。
2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
udp和tcp一次发送数据长度的限制
用UDP协议发送时,用sendto函数最大能发送数据的长度为:65535- IP头(20) – UDP头(8)=65507字节。用sendto函数发送数据时,如果发送数据长度大于该值,则函数会返回错误。(丢弃这个包,不进行发送)
用TCP协议发送时,由于TCP是数据流协议,因此不存在包大小的限制(暂不考虑缓冲区的大小),这是指在用send函数时,数据长度参数不受限制。而实际上,所指定的这段数据并不一定会一次性发送出去,如果这段数据比较长,会被分段发送,如果比较短,可能会等待和下一次数据一起发送。
socketserver
server端
import socketserver
class Myserver(socketserver.BaseRequestHandler):
def handle(self):
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
self.request.sendall(self.data.upper())
if __name__ == "__main__":
HOST, PORT = "127.0.0.1", 9999
# 设置allow_reuse_address允许服务器重用地址
socketserver.TCPServer.allow_reuse_address = True
# 创建一个server, 将服务地址绑定到127.0.0.1:9999
server = socketserver.TCPServer((HOST, PORT),Myserver)
# 让server永远运行下去,除非强制停止程序
server.serve_forever()
client端
import socket
HOST, PORT = "127.0.0.1", 9999
data = "hello"
# 创建一个socket链接,SOCK_STREAM代表使用TCP协议
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((HOST, PORT)) # 链接到客户端
sock.sendall(bytes(data + "\n", "utf-8")) # 向服务端发送数据
received = str(sock.recv(1024), "utf-8")# 从服务端接收数据
print("Sent: {}".format(data))
print("Received: {}".format(received))