我的python之路【第八章】网络编程
1.最基础版本的socket编程:
import socket #socket服务端: phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机 phone.bind(('127.0.0.1',8080)) #插入手机卡 phone.listen(5) #开机 等待连接的连接数(5) 半连接池 conn,addr=phone.accept() #接电话 conn连接的线路 addr 对方ip print('tcp的连接: ',conn) print('客户端的地址:',addr) data=conn.recv(1024) #接收的大小 1024 print('from client msg: %s' %data) conn.send(data.upper()) #发消息 conn.close() #挂电话 phone.close() #关手机
import socket timeout=2 #这里对整个socket层设置超时时间。后续文件中如果再使用到socket,不必再设置 socket.setdefaulttimeout(timeout) #socket 客户端 client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(('127.0.0.1',8080)) client.send('hello'.encode('utf-8')) data=client.recv(1024) print(data) client.close()
简单的循环实现-socketserver和socketclient一直进行数据交互
import socket #服务端 #创建socket对象,指定家族簇,指定协议TCP=SOCK_STREAM phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #设置断开连接时不出错 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #绑定ip和port,注意传入一个参数元组类型 phone.bind(('127.0.0.1',8080)) phone.listen(5) #开机 等待连接的连接数(5) 半连接池 while True: #链接循环 conn,addr=phone.accept() #conn是client连接成功创建的对象,之后的数据交互都是通过conn print('tcp的连接: ',conn) print('客户端的地址:',addr) while True: #通讯循环 try: data=conn.recv(1024) #接收的大小 1024 #不能收空,空默认为没有收到 if not data:break #解决客户端断开,一直打印空。 print('from client msg: %s' %data) conn.send(data.upper()) #发消息 将client传入的数据转化为大写发送回去 except Exception: # break conn.close() #挂电话 phone.close() #关手机
import socket #客户端 #创建一个socket对象 client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #利用socket对象进行连接server client.connect(('127.0.0.1',8080)) while True: msg=input('>>:') if not msg:continue #如果输入的数据是空,返回 client.send(msg.encode('utf-8')) #进行发送数据,socket之间数据交互必须是bytes类型的 data=client.recv(1024) #进行接收数据 print(data) client.close()
利用socket,远程执行命令并将命令执行的结果返回
服务端:
#远程执行命令server端 import socket,subprocess,struct phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) phone.bind(('127.0.0.1',8080)) phone.listen(5) #开机 等待连接的连接数(5) 半连接池 while True: #链接循环 print('等待连接....') conn,addr=phone.accept() #接电话 conn连接的线路 addr 对方ip print('tcp的连接: ',conn) print('客户端的地址:',addr) while True: #通讯循环 try: cmd=conn.recv(1024) #接收的大小 1024 #不能收空,空默认为没有收到 if not cmd:break #解决客户端断开,一直打印空。 print('from client msg: %s' %cmd) res=subprocess.Popen(cmd.decode('utf-8'), #通过subprocess获取命令的执行结果 shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) err=res.stderr.read() #获取错误信息 if err: #如果错误信息存在 back_msg=err #返回信息为错误信息 else: back_msg=res.stdout.read() #否则返回信息为标准输出 except Exception: # break #解决服务端粘包问题 conn.send(struct.pack('i',len(back_msg))) #'i':指定struct 返回的为4个字节 conn.sendall(back_msg) #sendall 。循环将所有的数据全部发送完 conn.close() #挂电话 phone.close() #关手机
客户端
import socket,struct #创建一个socket对象 client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #进行连接服务器 client.connect(('127.0.0.1',8080)) #进入循环开始和服务器进行交互 while True: cmd=input('>>:').strip() if not cmd:continue #如果输入的是空,返回 client.send(cmd.encode('utf-8')) data_head=client.recv(4) #接收服务端用struct封装以后的数据,定长的为 4个字节 data_size=struct.unpack('i',data_head)[0] #利用struct获取到数据包的头部,获取到源数据的大小 recv_size=0 #用来保存接收到的数据大小 recv_bytes=b'' #用来保存接收到的数据 while recv_size <data_size: #判断有没有收完数据 res=client.recv(1024) #接收数据 recv_bytes+=res #拼接bytes字符 recv_size+=len(res) #计算接收到的数据的大小 print(res.decode('gbk')) #打印接收到的数据 client.close()
解决粘包问题: 发送json数据
import socket,subprocess,struct,json phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) phone.bind(('127.0.0.1',8080)) #插入手机卡 phone.listen(5) #开机 等待连接的连接数(5) 半连接池 while True: #链接循环 print('等待连接....') conn,addr=phone.accept() #接电话 conn连接的线路 addr 对方ip print('tcp的连接: ',conn) print('客户端的地址:',addr) while True: #通讯循环 try: cmd=conn.recv(1024) #接收的大小 1024 #不能收空,空默认为没有收到 if not cmd:break #解决客户端断开,一直打印空。 print('from client msg: %s' %cmd) res=subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) err=res.stderr.read() if err: back_msg=err else: back_msg=res.stdout.read() except Exception: # break # conn.send(back_msg) #解决服务端粘包问题 #conn.send(struct.pack('i',json.dumps(len(back_msg)))) #第一阶段,制作报头 head_dict={ 'data_size':len(back_msg) } head_json=json.dumps(head_dict) head_bytes=head_json.encode('utf-8') #第二阶段:发送报头的长度 conn.send(struct.pack('i',len(head_bytes))) #第三阶段:发送报头 conn.send(head_bytes) #第四阶段:发送真实数据 conn.sendall(back_msg) conn.close() #挂电话 phone.close() #关手机
客户端:
import socket,struct,json client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(('127.0.0.1',8080)) while True: cmd=input('>>:').strip() if not cmd:continue client.send(cmd.encode('utf-8')) #收报头的长度 data_head=client.recv(4) # data_size=json.load(struct.unpack('i',data_head)[0]) head_size=struct.unpack('i',data_head)[0] #收报头(根据报头长度) head_json=client.recv(head_size) head_dict=json.loads(head_json.decode('utf-8')) #获取真实数据长度 data_size=head_dict['data_size'] recv_size=0 recv_bytes=b'' while recv_size <data_size: res=client.recv(1024) recv_bytes+=res recv_size+=len(res) print(res.decode('gbk')) client.close()
socketserver解决只能一个客户端连接到server上的问题
服务端:
import socketserver #BaseRequestHandler 处理通讯 class FtpServer(socketserver.BaseRequestHandler): def handle(self): #必须实现handle方法,负责与客户端通讯 print(self.request) print(self.client_address) while True: try: data=self.request.recv(1024) self.request.send(data.upper()) except Exception: break if __name__ == '__main__': #ThreadingTCPServer 处理链接 s=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer) s.serve_forever() #链接循环--有了
客户端
from socket import * client=socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8080)) while True: msg=input('>>:') client.send(msg.encode('utf-8')) data=client.recv(1024) print(data)
异常处理
try: names=['alex','liuhao'] names[sasa] except IndexError as e: print(e) except Exception as e: print(e) exit() else: print('什么错都没有') finally: print('无论有没有错都执行') print('keep going')
自定义异常处理
class WupeiqiException(Exception): def __init__(self, msg): self.message = msg try: raise WupeiqiException('我的异常') except WupeiqiException as e: print (e)
作业:开发一个支持多用户在线的FTP程序
要求:
- 用户加密认证
- 允许同时多用户登录
- 每个用户有自己的家目录 ,且只能访问自己的家目录
- 对用户进行磁盘配额,每个用户的可用空间不同
- 允许用户在ftp server上随意切换目录
- 允许用户查看当前目录下文件
- 允许上传和下载文件,保证文件一致性
- 文件传输过程中显示进度条
- 附加功能:支持文件的断点续传
FTPManager
import json,os,sys,hashlib def run(): #user_database = os.path.dirname(__file__)+'/'+'users'+'/' user_database=os.path.dirname(os.path.abspath(sys.argv[0]))+'/'+'users'+'/' print(user_database) while True: md5=hashlib.md5() name=input('create your acount:') if len(name)==0:continue elif os.path.isfile(user_database+name): print('用户已经存在') continue passwd=input('create your passwd:') if len(passwd)==0:continue passwd=passwd.encode(encoding='utf-8') md5.update(passwd) quota_size=input('enter your quota_size[b]:') if len(quota_size)==0:continue user_dir = os.path.dirname(os.path.abspath(sys.argv[0]))+'/'+'user_file/'+name os.mkdir(user_dir) user_dict={ 'name':name, 'passwd':md5.hexdigest(), 'quota_size':int(quota_size), 'use_quota':0 } with open(user_database+name,'w',encoding='utf-8') as f: f.write(json.dumps(user_dict)) break if __name__ == '__main__': run()
FTPserver
#!/usr/bin/env python # -*- coding:utf-8 -*- #liuhao import socketserver,json,struct,os,sys,hashlib,subprocess,socket class FTPServer(socketserver.BaseRequestHandler): def handle(self): self.base_dir=os.path.dirname(os.path.abspath(sys.argv[0])) print('连接socket信息: ',self.request) print('连接来自于: ',self.client_address) while True: try: if self.auth()['status'] is False:continue except Exception:break while True: try: head_dict=self.recv_head() if hasattr(self,head_dict['cmd']): func=getattr(self,head_dict['cmd']) func(head_dict) except Exception: break def auth(self): #验证用户登录 md5=hashlib.md5() head_dict=self.recv_head() #这里只会接收到用户名和密码 self.user_database = os.path.dirname(os.path.abspath(sys.argv[0])) + '/users' + '/' + head_dict['name'] #获得用户数据信息 if os.path.isfile(self.user_database): with open(self.user_database,'r') as f: user_dict=json.load(f) if head_dict['name']==user_dict['name'] and head_dict['passwd']==user_dict['passwd']: head_dict=user_dict head_dict['status']=True self.send_head(head_dict) #返回用户登录信息 self.user_dir = os.path.dirname(os.path.abspath(sys.argv[0])) + '/user_file' + '/' + head_dict['name'] #ftp用户家目录 self.root_dir = self.user_dir # root_dict 用户临时目录集合 return head_dict head_dict['status']=False self.send_head(head_dict) #返回用户登录失败信息 return head_dict def recv_head(self): #接收client传来的报头 data=self.request.recv(4) head_len = struct.unpack('i',data)[0] #print(head_len) head_bytes = self.request.recv(head_len) head_json = head_bytes.decode('utf-8') head_dict = json.loads(head_json) print('接收:',head_dict) return head_dict def send_head(self, head_dict):#向服务端发送 报头 head_json = json.dumps(head_dict) head_bytes = head_json.encode('utf-8') print('发送:',head_dict) self.request.send(struct.pack('i', len(head_bytes))) self.request.send(head_bytes) def put(self,head_dict):#save 来自客户端的文件 md5=hashlib.md5() self.user_files = os.path.dirname(os.path.abspath(sys.argv[0])) + '/user_file' + '/' + head_dict['name']+'/'+head_dict['file_name'] file_path = self.user_files data_size = head_dict['file_size'] print('in the server put') recv_size=0 print(file_path) with open(file_path,'wb') as f: while True: if recv_size <data_size: res=self.request.recv(1024) f.write(res) recv_size+=len(res) else:break with open(file_path,'rb') as f: #获取md5值 for i in f: md5.update(i) if head_dict['md5_key'] ==md5.hexdigest(): print('md5验证一致性:通过') self.send_head(head_dict) else: print('md5验证一致性:未通过') head_dict.pop('md5_key') self.send_head(head_dict) # print(md5.hexdigest()) def get(self,head_dict): #推送消息到 md5=hashlib.md5() file_path=self.user_dir+'/'+head_dict['file_name'] if os.path.exists(file_path) and os.path.isfile(file_path): file_name=os.path.basename(file_path) file_size=os.path.getsize(file_path) #print('in the server_get:',head_dict) with open(file_path,'rb') as f: #获取本地md5_key for i in f: md5.update(i) head_dict['file_size']=file_size head_dict['md5_key']=md5.hexdigest() head_dict['get_status']=True #print(head_dict) self.send_head(head_dict) with open(file_path,'rb') as f: for line in f: self.request.send(line) def cmd(self,head_dict): msg='false' #self.user_dir = os.path.dirname(os.path.abspath(sys.argv[0])) + '/user_file' + '/' + head_dict['name'] #print(head_dict) cmd_list=head_dict['cmd_str'].split() print(cmd_list) #cmd_list = ['ls', 'mkdir', 'cd','dir'] if len(cmd_list)==1 and cmd_list[0] in ['ls','dir','pwd']: if cmd_list[0]=='pwd': msg=self.root_dir elif cmd_list[0]=='dir': os.chdir(self.root_dir) cmd_str='dir' else: cmd_str=cmd_list[0]+' '+self.root_dir elif len(cmd_list)==2 : if cmd_list[0] in ['mkdir','cd']: #isidentifier 合法的标识符 isalnum 数字或字符 if cmd_list[1].isidentifier() or cmd_list[1].isalnum(): cmd_str=cmd_list[0]+' '+self.root_dir+'/'+cmd_list[1] if cmd_list[0]=='cd' and os.path.isdir(self.root_dir+'/'+cmd_list[1]): self.root_dir+='/'+cmd_list[1] elif cmd_list[0]=='cd' and cmd_list[1]=='..': #返回上一级目录 self.root_dir=os.path.dirname(self.root_dir) if self.root_dir in self.user_dir: #判断上级目录是不是家目录 self.root_dir=self.user_dir cmd_str=head_dict['cmd_str'] elif head_dict['cmd_str'] =='ls -l': cmd_str=head_dict['cmd_str'] print('消息:',msg) if msg =='false': res = subprocess.Popen(cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) err=res.stderr.read() if err: msg=err else:msg=res.stdout.read() self.request.send(struct.pack('i',len(msg))) self.request.sendall(msg) else: self.request.send(struct.pack('i',len(msg))) self.request.sendall(msg.encode('gbk')) if __name__ == '__main__': s=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FTPServer) s.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) s.serve_forever()
FTPClient
#!/usr/bin/env python # -*- coding:utf-8 -*- #liuhao from socket import * from time import sleep import os,json,struct,hashlib,sys,random import time class FTPClient(): def __init__(self,ip,port,AF_INET=AF_INET,SOCK_STREAM=SOCK_STREAM): self.client=socket(AF_INET,SOCK_STREAM) self.client.connect((ip,port)) def login(self): while True: md5_passwd = hashlib.md5() name=input('enter your name:').strip() if len(name)==0:continue passwd=input('enter your passwd:') if len(passwd)==0:continue md5_passwd.update(passwd.encode(encoding='utf-8')) login_dict={'name':name, 'passwd':md5_passwd.hexdigest() } self.send_head(login_dict) head_dict=self.recv_head() if head_dict['status']: print('验证通过') return head_dict else: print('验证失败') return head_dict def recv_head(self): data = self.client.recv(4) head_len = struct.unpack('i',data)[0] head_bytes = self.client.recv(head_len) head_json = head_bytes.decode('utf-8') head_dict = json.loads(head_json) #print('接收',head_dict) return head_dict def run(self,user_dict): while True: cmd_list = ['ls', 'mkdir', 'cd','dir','pwd'] cmd_str=input('enter your action: ').strip() if not cmd_str:continue if len(cmd_str.split(' '))==2: cmd,file_path=cmd_str.split() if hasattr(self,cmd): #判断get,put func=getattr(self,cmd) func(file_path,user_dict) if cmd in cmd_list: # 判断cmd cmd='cmd' if hasattr(self, cmd): func = getattr(self, cmd) func(cmd_str,user_dict) elif len(cmd_str.split(' '))==1: if cmd_str.split()[0] in ['mkdir','cd']:continue if cmd_str.split()[0] in cmd_list: cmd = 'cmd' if hasattr(self, cmd): func = getattr(self, cmd) func(cmd_str, user_dict) else:continue def send_head(self,head_dict): #print('发送',head_dict) head_json = json.dumps(head_dict) head_bytes = head_json.encode('utf-8') self.client.send(struct.pack('i', len(head_bytes))) self.client.sendall(head_bytes) def speed(self,a, b,seed): # 传入a,b输出进度,需要在外部调用做循环 output = sys.stdout if a < b: c = a / b * 100 count = int(c) str_count = ('#' * count).ljust(100, ) output.write('\r完成进度 >:[%s]%.0f%% %s' % (str_count, c,seed)) output.flush() elif a == b: c = a / b * 100 count = int(c) str_count = ('#' * count).ljust(100, ) output.write('\r完成进度 >:[%s]%.0f%%' % (str_count, c)) print() else: print('传送完毕') def put(self,file_path,user_dict): self.md5 = hashlib.md5() if os.path.exists(file_path) and os.path.isfile(file_path): file_name=os.path.basename(file_path) file_size=os.path.getsize(file_path) with open(file_path,'rb') as f: for i in f: self.md5.update(i) #封装头部字典,json保存字典, user_dict['cmd']='put' user_dict['file_name']=file_name user_dict['file_size']=file_size user_dict['md5_key']=self.md5.hexdigest() # print(user_dict) self.send_head(user_dict) lang=0 with open(file_path,'rb') as f: for line in f: lang+=len(line) if file_size >10000000: #当文件大于10M时 for i in range(2): current = random.randrange(0,100) if i ==current: self.speed(lang, user_dict['file_size']) else: self.speed(lang, user_dict['file_size']) self.client.sendall(line) #if lang==user_dict['file_size']:self.speed(lang, user_dict['file_size']) print() md5_dict=self.recv_head() if 'md5_key' in md5_dict: print('md5验证:通过') else:print('md5验证:未通过') def get(self,file_name,user_dict): md5=hashlib.md5() #print(file_name,user_dict) user_dict['cmd']='get' user_dict['file_name']=file_name self.send_head(user_dict) get_dict=self.recv_head() # print('get_dict',get_dict) if get_dict['get_status']: file_size=get_dict['file_size'] recv_size=0 sped=0 with open(get_dict['file_name'],'wb') as f: #接收文件,并打印进度条 while True: if recv_size< file_size: if file_size > 10000000: # 当文件大于10M时 for i in range(2): #取随机数,输出进度条 current = random.randrange(0,100) if i == current: self.speed(recv_size, get_dict['file_size'],sped) else: self.speed(recv_size, get_dict['file_size'],sped) time_start = time.time() recv_data=self.client.recv(1024) print(time_start) time.sleep(1) # sped=len(recv_data)/(time_stop-time_start) f.write(recv_data) recv_size += len(recv_data) md5.update(recv_data) else:break #print(md5.hexdigest()) print() if get_dict['md5_key']==md5.hexdigest(): print('MD5一致性验证:通过') else:print('MD5一致性验证:未通过') def cmd(self,cmd_str,user_dict): print(cmd_str) user_dict['cmd']='cmd' user_dict['cmd_str']=cmd_str self.send_head(user_dict) head=self.client.recv(4) data_size = struct.unpack('i', head)[0] recv_size = 0 # 用来保存接收到的数据大小 recv_bytes = b'' # 用来保存接收到的数据 while recv_size < data_size: # 判断有没有收完数据 res = self.client.recv(1024) # 接收数据 recv_bytes += res # 拼接bytes字符 recv_size += len(res) # 计算接收到的数据的大小 print(recv_bytes.decode('gbk')) # 打印接收到的数据 def run_main(): while True: try: ip = input('enter ftp server_IP port:').strip() ip_port = ip.split() if len(ip_port) == 2: a = FTPClient(ip_port[0], int(ip_port[1])) # 正式使用 user_dict = a.login() if user_dict['status']: a.run(user_dict) else: print('请重新登陆') else: continue except Exception: continue def test_main(): # a = FTPClient('172.16.50.91',8080) #测试 a = FTPClient('127.0.0.1', 8080) # 测试 user_dict = a.login() if user_dict['status']: a.run(user_dict) else: print('请重新登陆') if __name__ == '__main__': #run_main() #正式使用 test_main() #测试使用 ''' c=socket(AF_INET,SOCK_STREAM) c.connect(('127.0.0.1',8080)) '''
一个测试端口的小脚本
#!/usr/bin/env python # -*- coding:utf-8 -*- import subprocess import socket import time timeout=2 #这里对整个socket层设置超时时间。后续文件中如果再使用到socket,不必再设置 socket.setdefaulttimeout(timeout) f = open('ip_file','r') ip_list =[] for ip in f: real_ip=ip.strip('\n').strip() client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) try: client.connect((ip,port)) client.close() print(real_ip,'is ok') #time.sleep(1) except Exception as e: print(real_ip,port,e)