通信的几个程序

1.大文件的上传与下载

  • 基于tcp协议的通信
  • 客户端只用传输信息,把信息装入字典,通过序列化将字典传给服务端,服务端实现功能;
  • 服务端每次接受数据都是向操作系统要,而不是一个recv对应一个send
  • 所以对于大文件可以按字节读,每次读取一个固定字节,通过os模块得到文件大小,文件大小累减到0,文件读完;

 

#客户端client
import socket
import os
import json

sk = socket.socket()
sk.connect(("127.0.0.1",8001))

#让用户输入选项选择功能
menu = {"1":"upload","2":"download"}
for k,v in menu.items():
    print(k,v)
num = input("请输入功能选项:")

#上传功能  用户只管输入一些信息,存放在字典中,通过序列化发给服务端
#功能都是由服务端实现
if num == "1":
    dic = {"opt":menu.get(num),"filename":None,"filesize":None}
    file_path = input("请输入一个绝对路径:") # 文件的绝对路径
    filename = os.path.basename(file_path) # 文件名字
    filesize = os.path.getsize(file_path)  # 获取用户输入的路径中文件的大小
                                          #用于服务端接收的时候也要知道接收到多大才行

    dic["filename"] = filename
    dic["filesize"] = filesize
    str_dic = json.dumps(dic)
    sk.send(str_dic.encode("utf-8"))# 将被填充完成的字典先发送给服务器

    sk.recv(1024)# 为什么要有一个recv?
    #  因为上边send字典时,如果程序执行过快,可能会马上执行到下边的send(content)
    #  此时有可能会发生粘包,所以在此中间加一个recv,为了避免粘包
    with open(file_path,"rb") as f:
        while filesize:
            content = f.read(1024)
            sk.send(content)
            filesize -= len(content)

elif num == "2":
    pass

 

 

#服务端
import socket
import json
sk = socket.socket()
sk.bind(("127.0.0.1",8001))
sk.listen()
conn,addr = sk.accept()

str_dic = conn.recv(100).decode("utf-8")
#对于tcp读取多少字节都没有限制,而且对于单一的一个文件传输没有粘包现象
因为在传输过程中,每一段数据都是有编号的,缓存区会自动帮你拆包组合成数据;

conn.send(b'ok')#为什么需要一个send,防止与下面的recv发生粘包


# str_dic = {"opt":menu.get(num),"filename":None,"filesize":None}
dic = json.loads(str_dic)
if dic["opt"] == "upload":
    filename = "1"+ dic["filename"] #避免文件名重复

with open(filename,"ab") as f: while dic['filesize']: content = conn.recv(1024) f.write(content) dic['filesize'] -= len(content) elif dic["opt"] == "download": pass conn.close() sk.close()
  • 通过上面的代码可以看出,在客户端分别传输字典和数据时会容易发生粘包现象;
  • 解决方法,如果知道字典有多长,直接可以先按长度接收了字典,再接收数据,引入struct模块

 

import struct

a = {'a':'p','o':'a'}
sa = len(a)#最大的长度是21亿

s = struct.pack('i',sa)     #这里会将数据的长度打包成一个固定的值,一个四位点进制;
print(s,len(s))        #这个会打包成一个bytes类型的数据


print(struct.unpack('i',s)) #拆包的时候,得到是一个元组,第一位是你数据的长度;

#输出结果
b'\x02\x00\x00\x00' 4
(2,)

 

#第二版上传下载

#服务器
import
socket import json import os import struct sk = socket.socket() sk.bind(('192.168.12.13',8090)) sk.listen() coon,addr = sk.accept() #通过struct模块将字典长度取出,再按长度接收字典 len_dic = coon.recv(4)#先将字典的长度取出 byte_dic = struct.unpack('i',len_dic)[0] str_dic = coon.recv(byte_dic).decode('utf-8') dic = json.loads(str_dic) print(dic) if dic['opt'] == 'uplaod': filename ='1' + dic['filename'] with open(filename,'ab')as f: while dic['filesize']: content = coon.recv(1024) f.write(content) dic['filesize'] -= len(content) elif dic['opt'] == 'download': dic1 = {'tuzhi.py':None,'120180817_095545.mp4':None} filese1 = os.path.getsize('tuzhi.py') filese2 = os.path.getsize('120180817_095545.mp4') dic1['tuzhi.py'] = filese1 dic1['120180817_095545.mp4'] = filese2 str_dic1 = json.dumps(dic1).encode('utf-8') coon.send(str_dic1) num = coon.recv(1024).decode('utf-8') ls = ['tuzhi.py','120180817_095545.mp4'] file1_size = os.path.getsize(ls[int(num)-1]) with open(ls[int(num)-1],'rb')as f1: while file1_size: s = f1.read(1024) coon.send(s) file1_size -= len(s) coon.close() sk.close()
#客户端
import socket
import json
import os
import struct

sk = socket.socket()
sk.connect_ex(('192.168.12.13',8090))
#创建一个字典用来实现功能
menu = {'1':'uplaod','2':"download"}
for k,v in menu.items():
    print(k,v)
cc = input('输入序号选择>>>:')
#上传大文件
if cc == '1':
    #创建字典用来把用户的想要实现的功能装在字典中发给服务端
    dic = {'opt':menu.get(cc),'filename':None,'filesize':None}
    filename = input("输入文件路径>>>:")
    file_path = os.path.basename(filename)
    file_size = os.path.getsize(filename)
    #填充字典
    dic['filename'] = file_path
    dic['filesize'] = file_size
    #把字典作为字符串传输,通过struct模块避免粘包现象
    str_dic = json.dumps(dic)
    len_dic = len(str_dic)
    byte_dic = struct.pack('i',len_dic)
    sk.send(byte_dic+str_dic.encode('utf-8'))
    #按字节读取文件
    with open(filename,'rb')as f:
        while file_size:
            content = f.read(1024)        #这里可以少不可以多
            sk.send(content)
            file_size -= len(content)
elif cc == '2':
    dic = {'opt': menu.get(cc), 'filename': None, 'filesize': None}
    str_dic = json.dumps(dic)
    len_dic = len(str_dic)
    byte_dic = struct.pack('i', len_dic)
    sk.send(byte_dic + str_dic.encode('utf-8'))

    dic_str = sk.recv(1024).decode('utf-8')
    dic1 = json.loads(dic_str)
    ls = []
    for k,v in enumerate(dic1.keys(),1):
        ls.append(v)
        print(k,v)
    num = input("请用序号选择文件>>>:")
    filesize = dic1[ls[int(num)-1]]
    file_name = '666'
    sk.send(num.encode('utf-8'))
    with open(file_name,'ab')as f1:
        while filesize:
            filestr = sk.recv(1024)
            f1.write(filestr)
            filesize -= len(filestr)
else:
    print('输入错误!')
sk.close()

2.执行系统命令 

1)在py中如何执行系统命令

  • 在工作中有时会让查看当前服务器的状态,服务器一般都是租用大公司的,所以通过一个模块就能了解到当前服务器的状态
  • 模块subprocess中的Popen方法,编码以当前系统为准,如果是windows,就用jbk解码编码,且只能从管道中读取一次结果;

#subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)的一些参数:

  • cmd:代表系统命令
  • shell=True代表这条命令是系统命令,告诉操作系统将cmd当做系统命令去执行
  • subprocess.stdout:执行系统命令后返回的一条正确结果;
  • subprocess.stderr:执行系统命令后返回的一条错误的结果;

 

#服务器
sk = socket.socket() sk.bind(('127.0.0.1',8090)) sk.listen() coon,addr = sk.accept() while 1: mes = coon.recv(1024).decode('utf-8') result = subprocess.Popen(mes,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) #这两个结果总会有一个为空 byte_out = result.stdout.read() byte_err = result.stderr.read() if not byte_err: coon.send(byte_out) else: coon.send(byte_err) coon.close() sk.close()

 

#客户端
sk = socket.socket()
sk.connect_ex(('127.0.0.1',8090))
while 1:
    dir_out = input(">>>:")
    sk.send(dir_out.encode('utf-8'))

    #因为读取的是当前操作系统的内容,所以编码格式为gbk
    result = sk.recv(1023)
    print(result.decode('gbk'))

sk.close()

3.切换目录

  • 主要思想:将客户端输入的第一次绝对路径做为基础路径,在此上面做一些截取或则添加路径;
  • 问题是:在创建encode编码解码类时,tcp的客户端和服务器的对象不一样,所以要分开写方法
  • os模块中的listdir方法可以获取到当前路径下的所有文件和文件夹
  • 在客户端输入cd进入下一层时,要判断下一层是否是文件夹;

 

#编码重复写编码和解码的过程
import socket

#创建一个类基于socket类
class My_socket(socket.socket):
    def __init__(self, encond_ing='utf-8'):
        #先定义好一个默认值参数encond_ing,再调用父类方法
        self.encond_ing = encond_ing
        super().__init__()
    #服务器的连接对象方法
    def r_accept(self):
        coon, addr = self.accept()
        self.coon = coon
    #服务器通过链接对象接收和发送
    def s(self, msg):
        return self.coon.send(msg.encode(self.encond_ing))
    def r(self, num):
        str_msg = self.coon.recv(num).decode(self.encond_ing)
        return str_msg
    #服务器的关闭方法
    def cllo(self):
        return self.coon.close()
    #客户端的接收和发送方法
    def rc(self, num):
        str_msg = self.recv(num).decode(self.encond_ing)
        return str_msg
    def sc(self, msg):
        return self.send(msg.encode(self.encond_ing))

 

#服务器
from my_tcp import My_socket
import os
import json
import struct

sk = My_socket()
sk.bind(('127.0.0.1',8090))
sk.listen()
sk.r_accept()    #等待连接,

#接收到客户端第一次输入的绝对路径
str_msg = sk.r(1024)

def send_data(path):
    '''
    向客户端发送目录列表
    主要用到lisdir(绝对路径)下的所有目录
    '''
    lis_dir = os.listdir(path)
    #将列表序列化为字符串
    str_dir = json.dumps(lis_dir)
    sk.s(str_dir)
    return lis_dir
def up_file(path):
    '''
    向客户端发送..上一层目录的情况
    切割客户端输入的绝对路径,因为在开始的时候加了一个/,所以列表中会有一个空字符
    然后往上一层的话直接就取切割掉最后一位的绝对路径,再拼接为字符串再加一个/
    '''
    split_path = path.split('\\')[:-2]
    split_path = '/'.join(split_path)+'/'
    lst = send_data(split_path)
    return split_path,lst
def down_file(path,path_list,num):
    '''
    通过客户端输入序号进入下一级目录
    :param path: str_dir
    :return: None
    '''
    down_path = path +path_list[int(num)-1]+'/'
    list_dir = send_data(down_path)
    return down_path,list_dir

#在返回上一层目录中,如果C:不加/,则进入到当前文件的目录下,所以避免这种情况
#则在开始的时候就加上/

current_dir = str_msg + '/'
ls = send_data(current_dir)
while 1:
    #struct模块避免发生粘包
    len_menu = sk.r(4).encode('utf-8')
    len_menu = struct.unpack('i',len_menu)[0]
    menu = sk.r(len_menu)
    if menu == '..':
        current_dir,ls = up_file(current_dir)
    elif menu == 'cd':
        str_num = sk.r(1024)
        if os.path.isdir(current_dir + ls[int(str_num)-1]):
            current_dir, ls = down_file(current_dir,ls,str_num)
            continue
        else:
            s1 = json.dumps("这不是文件夹")
            sk.s(s1)
            continue
    else:
        sk.cllo()
        break


sk.close()
#客户端
from my_tcp import My_socket
import os
import json
import struct

sk = My_socket()
sk.connect_ex(('127.0.0.1',8090))

#先让客户端输入一个基础路径
#切换目录过程中都是以此为进行的
abs_path = input(">>>:")
abs_path = os.path.abspath(abs_path)
sk.sc(abs_path)
str_dir = sk.rc(1024)
list_dir = json.loads(str_dir)

for k,v in enumerate(list_dir,1):
            print(str(k)+ ':' + v,end=' ')
print()
while 1:
    menu = input("选择功能>>>:")
    #避免发生粘包
    len_menu = len(menu)
    slen = struct.pack('i',len_menu)
    sk.send(slen)
    sk.sc(menu)
    if menu.upper() == 'Q':
        break
    if menu == '..':
        str_dir = sk.rc(1024)
        list_dir = json.loads(str_dir)
        for k, v in enumerate(list_dir, 1):
            print(str(k)+ ':' + v, end=' ')
        print()
    elif menu == 'cd':
        num = input("按序号选择>>>:")
        sk.sc(num)
        str_dir = sk.rc(1024)
        list_dir = json.loads(str_dir)
        if type(list_dir) is str:
            print(list_dir)
            continue
        else:
            for k, v in enumerate(list_dir, 1):
                print(str(k)+ ':' + v, end=' ')
            print()
    else:
        print("输入错误!")
        continue

sk.close()

 

 

 

 

posted @ 2018-08-16 22:08  pythonZhou  阅读(376)  评论(0)    收藏  举报