python 网络编程
一、基本原理:
1.软件
客户端:CS架构,client --> server
浏览器:BS架构,borowser --> server
2.如何实现相互通信
1.相互通讯的本质是发送把01代码通过网线的高低电平的方式传输
2.交换机的作用
3.通过ipconfig查看自己的IP
4.公网IP (需要购买)
二、编写网络相关的程序 (socket模块 )
示例:基于socket模块实现网络通信
服务端.py
import socket #创建服务端socket对象 server = socket.socket() #绑定IP和端口 server.bind(('192.168.13.156',6060)) # 后边可以等5个人 server.listen(5) print('服务器端准备接收客户端的链接') #等待客户端来连接,如果没人来就继续等 #conn是客户端和服务端连接的对象,服务端以后要通过该对象收发数据 #addr是客户端的地址信息 #=====阻塞只有客户端进行链接,则获取客户端链接然后开始进行通信 conn,addr = server.accept() print('已经有人连上了,客户端信息:',conn,addr) # 通过对象去获取 # 1024表示,服务端通过获取数据时,一次性最多拿1024个字节 data = conn.recv(1024) print('已经有人发来了消息:',data) # 服务端通过连接对象给客户端回复一个消息 conn.send(b'stop') #与服务端断开链接 conn.close() #关闭服务端的服务 server.close()
客户端.py
import socket client = socket.socket() #客户端向服务端发起连接请求 #阻塞,去连接,直到连接成功才继续向下走 client.connect(('192.168.13.156',6060)) #连接上服务端后,向服务端发送消息 client.send(b'hello') #等待服务器发来的消息 data = client.recv(1024) print(data) #关闭自己 client.close()
为什么要网络通信发送的是字节?而不是字符串?
py3, send/recv 都是字节
py2, send/recv 都是字符串
服务端:
accept,阻塞:等待客户端来连接。
recv, 阻塞:等待客户端发来数据。
客户端:
connect,阻塞:一直在连接,直到连接成功才往下运行其他代码。
recv, 阻塞:等待服务端发来数据。
三、黏包
1.什么是黏包
同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包。
2.tcp协议的拆包机制
当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。 MTU是Maximum Transmission Unit的缩写。意思是网络上传送的最大数据包。MTU的单位是字节。 大部分网络设备的MTU都是1500。如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率,降低网络速度。
3.基于tcp协议特点的黏包现象成因
4.解决黏包的方法
struck模块
该模块可以把一个类型,如数字,转成固定长度的bytes
import struct res=struct.pack("i","") print(res) print(len(res)) obj=struct.unpack("i",res) print(obj[0])
例:简单的文件上传代码中解决黏包
服务端.py
import socket import struct server = socket.socket() server.bind(('192.168.13.156', 8005)) server.listen(5) while True: print("服务器等待连接") conn,addr = server.accept() while True: try: # 接收文件名长度: header_pack = conn.recv(4) # 接收压缩的4个字节(文件名的长度) data_length = struct.unpack('i',header_pack)[0] # 解压接收到的4个字节得到文件名的长度 # print("文件名长度",data_length) #取出文件名: ret = conn.recv(data_length) # 根据文件名长度取文件名 name = ret.decode('utf-8') #接收文件大小: size = conn.recv(4) # 接收压缩的4个字节(文件的大小) file_size = struct.unpack('i',size)[0] # 解压接收到的4个字节得到文件的大小 print("接收文件:%s,大小为%s字节" % (name,file_size)) recv_data_length = 0 #计数,初始为0 while recv_data_length < file_size: data = conn.recv(1024) # with open(name,mode='a',encoding='utf-8') as f1: # f1.write(data.decode('utf-8')) with open(name, mode='ab') as f1: f1.write(data) recv_data_length += len(data) #计数每次加1024 print("接收完成") conn.send('上传成功!'.encode('utf-8')) except ConnectionResetError as e: break conn.close()
客户端.py
import socket import os import struct client = socket.socket() client.connect(('192.168.13.156',8005)) while True: file_name = input(">>>>>") if file_name == 'exit': break if os.path.exists(file_name): filename = os.path.basename(file_name) #传文件名长度,和文件名 name = struct.pack('i',len(filename)) client.send(name) # print(len(name)) client.send(filename.encode('utf-8')) #传文件大小 size = os.path.getsize(filename) filesize = struct.pack('i',size) client.send(filesize) #传文件内容 # with open(filename,mode='r',encoding='utf-8') as f: # for line in f: # client.send(line.encode('utf-8')) with open(filename,mode='rb') as f: for line in f: client.send(line) ret = client.recv(1024) print(ret.decode('utf-8')) else: print('文件不存在,请重新选择!') client.close()
四、socketserver
创建一个socketserver的步骤:
首先,您必须通过对BaseReStHeuldLeCar类进行子类化并重写它的Hoad()方法来创建请求处理程序类;该方法将处理传入的请求。
其次,必须实例化服务器类中的一个,将其传递给服务器的地址和请求处理程序类。
然后调用Server对象的HealLeReestEnter(OrServEyPro)()方法来处理一个或多个请求。
最后,调用ServEnLoce()关闭套接字。
流程图
例:文件上传下载
import json import os import socketserver import struct class MyServer(socketserver.BaseRequestHandler): def handle(self): while True: try: print("服务器等待连接.....") self.read_dir() # 接收json的打包长度 file_info_length_pack = self.request.recv(4) file_info_length = struct.unpack('i', file_info_length_pack)[0] # 接收json字符串 file_info_json = self.request.recv(file_info_length).decode("utf8") file_info = json.loads(file_info_json) action = file_info['action'] filename = file_info['filename'] filesize = file_info['filesize'] if action == 'upload': self.receive_upload(filename, filesize) elif action == 'download': self.receive_downloads(filename) except ConnectionResetError as e: break def receive_upload(self,filename, filesize): recv_data_length = 0 while recv_data_length < filesize: data = self.request.recv(1024) with open('requestup/'+filename, mode='ab') as f1: f1.write(data) recv_data_length += len(data) print("用户上传已完成!") self.request.send('上传成功!'.encode('utf-8')) def receive_downloads(self,filename): with open('requestdown/'+filename, mode='rb') as f: for line in f: self.request.send(line) # 接收返回的结果 Result = self.request.recv(1024) print(Result.decode('utf-8')) def read_dir(self): """ 读取可供下载的文件夹中的所有文件,并发送给客户端 """ returnfilename ={ 'filename': [], 'filesize': [] } rootdir = r'E:\python\work\day29\File_up_down\Server_side\requestdown' list = os.listdir(rootdir) # 列出文件夹下所有的目录与文件 for i in range(0, len(list)): path = os.path.join(rootdir, list[i]) if os.path.isfile(path): returnfilename['filename'].append(os.path.basename(path)) returnfilename['filesize'].append(os.path.getsize(path)) allfilename = json.dumps(returnfilename).encode('utf-8') self.request.send(allfilename) # 返回文件夹所有文件名 Server = socketserver.ThreadingTCPServer(('192.168.13.156',8005), MyServer) Server.serve_forever()
import socket import os import struct import json class MYTCPClient: def __init__(self): self.client = socket.socket() def compression_transmission(self,updown,filename,filesize=None): """ 把文件信息序列化后打压发送 """ file_info = { "action": updown, "filename": filename, "filesize": filesize, } file_info_json = json.dumps(file_info).encode('utf-8') # 序列化 ret = struct.pack('i', len(file_info_json)) # 发送file_info_json打包的长度 self.client.send(ret) # 发送file_info_json字节串 self.client.send(file_info_json) def fileupload(self): """ 文件上传功能 """ file_path = input('请输入文件路径:').strip(" ") if os.path.exists(file_path): filename = os.path.basename(file_path) filesize = os.path.getsize(filename) self.compression_transmission('upload',filename,filesize) # 发送文件 with open(file_path, mode='rb') as f: for line in f: self.client.send(line) # 接收返回的结果 Result = self.client.recv(1024) print(Result.decode('utf-8')) else: print('文件不存在,请重新选择!') def filedownload(self,allfilename): """ 文件下载功能 """ print('可下载文件目录:') for i in range(len(allfilename['filename'])): print(" 序号:%s,文件名:%s,大小:%s字节" % (i + 1, allfilename['filename'][i], allfilename['filesize'][i])) file_path = int(input('请选择要下载的文件:')) filename = allfilename['filename'][file_path-1] filesize = allfilename['filesize'][file_path-1] self.compression_transmission('download', filename) if os.path.exists('user_download') == False: # 如果不存在则创建目录 os.makedirs('user_download') ##########接收文件############ recv_data_length = 0 while recv_data_length < filesize: data = self.client.recv(1024) with open('user_download/'+filename, mode='ab') as f1: f1.write(data) recv_data_length += len(data) print("下载完成") self.client.send('用户下载已完成!'.encode('utf-8')) def run(self): """ 主程序 """ self.client.connect(('192.168.13.156', 8005)) file_info_json = self.client.recv(1024).decode("utf8") allfilename = json.loads(file_info_json) while True: print("请选择功能:1.上传,2.下载") Functional_selection = input(">>>>>").strip() if Functional_selection == '1': self.fileupload() elif Functional_selection == '2': self.filedownload(allfilename) user = MYTCPClient() user.run()