我的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程序

要求:

  1. 用户加密认证
  2. 允许同时多用户登录
  3. 每个用户有自己的家目录 ,且只能访问自己的家目录
  4. 对用户进行磁盘配额,每个用户的可用空间不同
  5. 允许用户在ftp server上随意切换目录
  6. 允许用户查看当前目录下文件
  7. 允许上传和下载文件,保证文件一致性
  8. 文件传输过程中显示进度条
  9. 附加功能:支持文件的断点续传

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()
View Code

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()
View Code

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))
'''
View Code

一个测试端口的小脚本

#!/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)

 

posted @ 2017-03-21 16:48  saynobody  阅读(244)  评论(0编辑  收藏  举报