异常处理与网络编程(八)

1.1 异常处理

1.1.1 什么是异常?

异常是错误发生的信号,一旦程序出错,就会产生一个异常,应用程序未处理该异常,异常便会抛出,程序随之终止python

 

1.1.2 常见异常类型

1.1.2.1  语法错误应该在程序运行前修正

if 1 >2

    print('xxxxx')

 

这种错误,根本过不了python解释器的语法检测,必须在程序执行前就改正linux

1.1.2.2  逻辑错误

x  #NameError

 

l=[]

l[10000] #IndexError

 

class Foo:

    pass

Foo.x #AttributeError:

 

k={'x':1}

k['y'] #KeyError

 

1/0 #ZeroDivisionError 没法完成计算

 

for i in 3: #TypeError: int类型不可迭代

    pass

 

age=input('>>: ') #ValueError

age=int(age)

 

1.1.3 如何处理异常

为了保证程序的健壮性与容错性,即在遇到错误时程序不会崩溃,咱们须要对异常进行处理,若是错误发生的条件是可预知的,咱们须要用if进行处理:在错误发生以前进行预防算法

若是错误发生的条件是不可预知的,则须要用到try...except:在错误发生以后进行处理shell

print('====>start<=====')

 

try:

    l=[]

    print(l[1111])

    print('====>1')

    print('====>2')

    print('====>3')

except IndexError:

    pass

 

print('====>end<=======')

 

1.1.3.1  异常类只能用来处理指定的异常状况,若是非指定异常则没法处理。

print('====>start<=====')

try:

    l=[]

    print(l[1111])

    print('====>1')

    print('====>2')

    print('====>3')

except IndexError as e: # 未捕获到异常,程序直接报错

    print('===>',e)

 

print('====>end<=======')

 

1.1.3.2  多分支

print('====>start<=====')

try:

    l=[]

    # print(l[1111])

    print('====>1')

    d={}

    d['k']

    print('====>2')

    print('====>3')

except IndexError as e:

    print('===>',e)

except KeyError as e:

    print('----',e)

 

print('====>end<=======')

  

1.1.3.3  万能异常Exception

print('====>start<=====')

try:

    l=[]

    # print(l[1111])

    print('====>1')

    d={}

    d['k']

    print('====>2')

    print('====>3')

except IndexError:

    pass

except KeyError:

    pass

except Exception as e:

    print('万能异常--->',e)

 

print('====>end<=======')

 

1.1.3.4  多分支异常与万能异常

若是你想要的效果是,不管出现什么异常,咱们统一丢弃,或者使用同一段代码逻辑去处理他们,那么骚年,大胆的去作吧,只有一个Exception就足够了。json

若是你想要的效果是,对于不一样的异常咱们须要定制不一样的处理逻辑,那就须要用到多分支了。浏览器

 

1.1.3.5  也能够在多分支后来一个Exception

print('====>start<=====')

try:

    l=[]

    print(l[1111])

    # print('====>1')

    d={}

    # d['k']

    # print('====>2')

    # print('====>3')

except IndexError:

    pass

except KeyError:

    pass

except Exception as e:

    print('万能异常--->',e)

else:

    print('没有异常发生的时候触发')

finally:

    print('有没有异常都触发')

 

 

print('====>end<=======')

 

1.1.4 自定义异常

'''

try:

    conn=connect('1.1.1.1',3306)

    conn.execute('select * from db1.t1')

finally:

    conn.close()

'''

stus=['egon','alex','wxxx']

ip_list=[

    # '1.1.1.1:8080',

    # '1.1.1.2:8081',

    # '1.1.1.3:8082',

]

 

if len(ip_list) == 0:

    raise TypeError

assert len(ip_list) > 0

 

print('从ip_list取出ip地址,验证可用性')

 

class MyException(BaseException):

    def __init__(self,msg):

        super(MyException,self).__init__()

        self.msg=msg

 

    def __str__(self):

        return '<%s>' %self.msg

 

raise MyException('类型错误') #异常的值:print(obj)

 

1.2 基于TCP协议实现简单的套接字协议

1.2.1 客户端/服务器架构

1.2.1.1  硬件C/S架构(打印机)

1.2.1.2  软件C/S架构

  互联网中到处是C/S架构缓存

  如网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种)服务器

  腾讯做为服务端为你提供视频,你得下个腾讯视频客户端才能看它的视频)cookie

1.2.1.3  C/S架构与socket的关系:

咱们学习socket就是为了完成C/S架构的开发网络

1.2.2 osi七层

   一个完整的计算机系统是由硬件、操做系统、应用软件三者组成,具有了这三个条件,一台计算机系统就能够本身跟本身玩了(打个单机游戏,玩个扫雷啥的),若是你要跟别人一块儿玩,那你就须要上网了,什么是互联网?

  互联网的核心就是由一堆协议组成,协议就是标准,好比全世界人通讯的标准是英语,若是把计算机比做人,互联网协议就是计算机界的英语。全部的计算机都学会了互联网协议,那全部的计算机都就能够按照统一的标准去收发信息从而完成通讯了。

  人们按照分工不一样把互联网协议从逻辑上划分了层级,

  互联网协议按照功能不一样分为osi七层或tcp/ip五层或tcp/ip四层

 

 

图1-1  

每层运行常见物理设备

 

 

图1-2  

 

1.2.3 tcp/ip五层模型

咱们将应用层,表示层,会话层并做应用层,从tcp/ip五层协议的角度来阐述每层的由来与功能,搞清楚了每层的主要协议,就理解了整个互联网通讯的原理。

1.2.3.1  物理层

物理层由来:

上面提到,孤立的计算机之间要想一块儿玩,就必须接入internet,言外之意就是计算机之间必须完成组网

物理层功能:

主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0

1.2.3.2  数据链路层

数据链路层由来:

单纯的电信号0和1没有任何意义,必须规定电信号多少位一组,每组什么意思

数据链路层的功能:

定义了电信号的分组方式

  早期的时候各个公司都有本身的分组方式,后来造成了统一的标准,即以太网协议ethernet,ethernet规定:

一组电信号构成一个数据包,叫作‘帧’,每一数据帧分红:报头head和数据data两部分。

head包含:(固定18个字节),发送者/源地址,6个字节,数据类型,6个字节。

data包含:(最短46字节,最长1500字节),数据包的具体内容

head长度+data长度=最短64字节,最长1518字节,超过最大限制就分片发送

mac地址:

head中包含的源和目标地址由来:ethernet规定接入internet的设备都必须具有网卡,发送端和接收端的地址即是指网卡的地址,即mac地址,mac地址:每块网卡出厂时都被烧制上一个世界惟一的mac地址,长度为48位2进制,一般由12位16进制数表示(前六位是厂商编号,后六位是流水线号)

广播:

有了mac地址,同一网络内的两台主机就能够通讯了(一台主机经过arp协议获取另一台主机的mac地址),ethernet采用最原始的方式,广播的方式进行通讯,即计算机通讯基本靠吼

1.2.3.3  网络层

网络层由来:

有了ethernet、mac地址、广播的发送方式,世界上的计算机就能够彼此通讯了,问题是世界范围的互联网是由

一个个彼此隔离的小的局域网组成的,那么若是全部的通讯都采用以太网的广播方式,那么一台机器发送的包全世界都会收到,这就不只仅是效率低的问题了,这会是一种灾难

必须找出一种方法来区分哪些计算机属于同一广播域,哪些不是,若是是就采用广播的方式发送,若是不是,就采用路由的方式(向不一样广播域/子网分发数据包),mac地址是没法区分的,它只跟厂商有关

网络层功能:

引入一套新的地址用来区分不一样的广播域/子网,这套地址即网络地址

IP协议:

规定网络地址的协议叫ip协议,它定义的地址称之为ip地址,普遍采用的v4版本即ipv4,它规定网络地址由32位2进制表示,范围0.0.0.0-255.255.255.255,一个ip地址一般写成四段十进制数,例:172.16.10.1

ip地址分红两部分

  网络部分:标识子网

  主机部分:标识主机

注意:单纯的ip地址段只是标识了ip地址的种类,从网络部分或主机部分都没法辨识一个ip所处的子网,例:172.16.10.1与172.16.10.2并不能肯定两者处于同一子网

子网掩码:

所谓”子网掩码”,就是表示子网络特征的一个参数。它在形式上等同于IP地址,也是一个32位二进制数字,它的网络部分所有为1,主机部分所有为0。好比,IP地址172.16.10.1,若是已知网络部分是前24位,主机部分是后8位,那么子网络掩码就是11111111.11111111.11111111.00000000,写成十进制就是255.255.255.0。

知道”子网掩码”,咱们就能判断,任意两个IP地址是否处在同一个子网络。方法是将两个IP地址与子网掩码分别进行AND运算(两个数位都为1,运算结果为1,不然为0),而后比较结果是否相同,若是是的话,就代表它们在同一个子网络中,不然就不是。

好比,已知IP地址172.16.10.1和172.16.10.2的子网掩码都是255.255.255.0,请问它们是否在同一个子网络?二者与子网掩码分别进行AND运算,

172.16.10.1:10101100.00010000.00001010.000000001

255255.255.255.0:11111111.11111111.11111111.00000000

AND运算得网络地址结果:10101100.00010000.00001010.000000001->172.16.10.0

 

172.16.10.2:10101100.00010000.00001010.000000010

255255.255.255.0:11111111.11111111.11111111.00000000

AND运算得网络地址结果:10101100.00010000.00001010.000000001->172.16.10.0

结果都是172.16.10.0,所以它们在同一个子网络。

总结一下,IP协议的做用主要有两个,一个是为每一台计算机分配IP地址,另外一个是肯定哪些地址在同一个子网络。

ip数据包:

ip数据包也分为head和data部分,无须为ip包定义单独的栏位,直接放入以太网包的data部分:

head:长度为20到60字节

data:最长为65,515字节。

而以太网数据包的”数据”部分,最长只有1500字节。所以,若是IP数据包超过了1500字节,它就须要分割成几个以太网数据包,分开发送了。

ARP协议

arp协议由来:计算机通讯基本靠吼,即广播的方式,全部上层的包到最后都要封装上以太网头,而后经过以太网协议发送,在谈及以太网协议时候,我门了解到

通讯是基于mac的广播方式实现,计算机在发包时,获取自身的mac是容易的,如何获取目标主机的mac,就须要经过arp协议

arp协议功能:广播的方式发送数据包,获取目标主机的mac地址

协议工做方式:每台主机ip都是已知的,首先经过ip地址和子网掩码区分出本身所处的子网,目标主机ip同一子网,目标主机mac,目标主机ip,若是不是同一网络,那么目标ip经过arp获取的是网关的mac,这个包会以广播的方式在发送端所处的自网内传输,全部主机接收后拆开包,发现目标ip为本身的,就响应,返回本身的mac

1.2.3.4  传输层

传输层的由来:

网络层的ip帮咱们区分子网,以太网层的mac帮咱们找到主机,而后你们使用的都是应用程序,你的电脑上可能同时开启qq,暴风影音,等多个应用程序,那么咱们经过ip和mac找到了一台特定的主机,如何标识这台主机上的应用程序,答案就是端口,端口即应用程序与网卡关联的编号。

传输层功能:创建端口到端口的通讯

补充:端口范围0-65535,0-1023为系统占用端口

tcp协议:

可靠传输,TCP数据包没有长度限制,理论上能够无限长,可是为了保证网络的效率,一般TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包没必要再分割。

udp协议:

不可靠传输,”报头”部分一共只有8个字节,总长度不超过65,535字节,正好放进一个IP数据包。

tcp报文

 

 

图1-3  

tcp三次握手和四次挥手

 

 

图1-4  

 

1.2.3.5  应用层:

应用层由来:用户使用的都是应用程序,均工做于应用层,互联网是开发的,你们均可以开发本身的应用程序,数据多种多样,必须规定好数据的组织形式

应用层功能:规定应用程序的数据格式。

TCP协议能够为各类各样的程序传递数据,好比Email、WWW、FTP等等。那么,必须有不一样协议规定电子邮件、网页、FTP数据的格式,这些应用程序协议就构成了”应用层”。

 

 

图1-5  

1.2.4 socket

咱们知道两个进程若是须要进行通信最基本的一个前提能可以惟一的标示一个进程,在本地进程通信中咱们可使用PID来惟一标示一个进程,但PID只在本地惟一,网络中的两个进程PID冲突概率很大,这时候咱们须要另辟它径了,咱们知道IP层的ip地址能够惟一标示主机,而TCP层协议和端口号能够惟一标示主机的一个进程,这样咱们能够利用ip地址+协议+端口号惟一标示网络中的一个进程。

可以惟一标示网络中的进程后,它们就能够利用socket进行通讯了,什么是socket呢?咱们常常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操做抽象为几个简单的接口供应用层调用已实现进程在网络中通讯。

 

图1-6  

socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在创建链接打开后,能够向本身文件写入内容供对方读取或者读取对方内容,通信结束时关闭文件。

1.2.4.1  服务端套接字函数

s.bind()    绑定(主机,端口号)到套接字

s.listen()  开始TCP监听

s.accept()  被动接受TCP客户的链接,(阻塞式)等待链接的到来

 

1.2.4.2  客户端套接字函数

s.connect()     主动初始化TCP服务器链接

s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

 

1.2.4.3  公共用途的套接字函数

s.recv()            接收TCP数据

s.send()            发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)

s.sendall()         发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)

s.recvfrom()        接收UDP数据

s.sendto()          发送UDP数据

s.getpeername()     链接到当前套接字的远端的地址

s.getsockname()     当前套接字的地址

s.getsockopt()      返回指定套接字的参数

s.setsockopt()      设置指定套接字的参数

s.close()           关闭套接字

用打电话的流程快速描述socket通讯:

1.2.4.4  基于TCP的套接字

tcp服务端:

ss = socket() #建立服务器套接字

ss.bind()      #把地址绑定到套接字

ss.listen()      #监听连接

inf_loop:      #服务器无限循环

    cs = ss.accept() #接受客户端连接

    comm_loop:         #通信循环

        cs.recv()/cs.send() #对话(接收与发送)

    cs.close()    #关闭客户端套接字

ss.close()        #关闭服务器套接字(可选)

 

tcp客户端:

cs = socket()    # 建立客户套接字

cs.connect()    # 尝试链接服务器

comm_loop:        # 通信循环

     cs.send()/cs.recv()    # 对话(发送/接收)

cs.close()            # 关闭客户套接字

 

socket通讯流程与打电话流程相似,咱们就以打电话为例来实现一个low版的套接字通讯

1.2.5 客户端:

import socket

 

#一、买手机

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp协议

 

#二、拨电话

phone.connect(('127.0.0.1',8081)) #0-65535

 

#三、发收消息

phone.send('hello'.encode('utf-8'))

 

data=phone.recv(1024)

print(data)

 

#四、挂电话

phone.close()

 

1.2.6 服务端:

import socket

 

#一、买手机

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp协议

 

#二、绑定手机

# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

phone.bind(('127.0.0.1',8081)) #0-65535

 

#三、开机

phone.listen(5)

 

#四、等待电话链接

print('starting...')

conn,client_addr=phone.accept() #(conn,client_addr)

print(conn,client_addr)

 

#五、收\发消息

data=conn.recv(1024) #1024bytes?

 

conn.send(data.upper())

 

#六、挂电话链接

conn.close()

 

#七、关机

phone.close()

 

 

1.3 加上连接循环与通讯循环

1.3.1 服务端:

import socket

 

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp协议

# print(phone)

# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

phone.bind(('127.0.0.1',8083)) #0-65535

phone.listen(5)

 

print('starting...')

while True: #连接循环

    conn,client_addr=phone.accept() #(conn,client_addr)

    # print(conn,client_addr)

    print(client_addr)

 

    while True: #通讯循环

        try:

            data=conn.recv(1024) #1024bytes?

            if not data:break #针对的是linux系统

            print('客户端消息',data)

            conn.send(data.upper())

            # print('====has send')

        except ConnectionResetError:

            break

    conn.close()

 

phone.close()

 

1.3.2 客户端:

1.3.2.1  客户端1:

import socket

 

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp协议

phone.connect(('127.0.0.1',8083)) #0-65535

 

while True:

    msg=input('>>: ') #msg=' '

    if not msg:continue

 

    phone.send(msg.encode('utf-8'))

    # print('has send===>')

    data=phone.recv(1024)

    # print('has recv===>')

    print(data)

 

phone.close()

 

1.3.2.2  客户端2:

import socket

 

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp协议

phone.connect(('127.0.0.1',8083)) #0-65535

 

while True:

    msg=input('>>: ').strip()

    if not msg:continue

 

    phone.send(msg.encode('utf-8'))

    # print('has send===>')

    data=phone.recv(1024)

    # print('has recv===>')

    print(data)

 

phone.close()

在重启服务端时可能会遇到

 

 

图1-7  

  这个是因为你的服务端仍然存在四次挥手的time_wait状态在占用地址(若是不懂,请深刻研究1.tcp三次握手,四次挥手 2.syn洪水攻击 3.服务器高并发状况下会有大量的time_wait状态的优化方法)

解决方法:

方法一:

#加入一条socket配置,重用ip和端口

phone=socket(AF_INET,SOCK_STREAM)

phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加

phone.bind(('127.0.0.1',8080))

 

方法二:

发现系统存在大量TIME_WAIT状态的链接,经过调整linux内核参数解决,

vi /etc/sysctl.conf

 

编辑文件,加入如下内容:

net.ipv4.tcp_syncookies = 1

net.ipv4.tcp_tw_reuse = 1

net.ipv4.tcp_tw_recycle = 1

net.ipv4.tcp_fin_timeout = 30

而后执行 /sbin/sysctl -p 让参数生效。

net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少许SYN攻击,默认为0,表示关闭;

net.ipv4.tcp_tw_reuse = 1 表示开启重用。容许将TIME-WAIT sockets从新用于新的TCP链接,默认为0,表示关闭;

net.ipv4.tcp_tw_recycle = 1 表示开启TCP链接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间

 

1.4 实现ssh远程执行命令

1.4.1 服务端:

from socket import *

import subprocess

 

server=socket(AF_INET,SOCK_STREAM)

server.bind(('127.0.0.1',8090))

server.listen(5)

 

while True:

    conn,client_addr=server.accept()

    print(client_addr)

 

    while True:

        try:

            cmd=conn.recv(1024)

            if not cmd:break

 

            #ls -l;sadfasdf;pwd;echo 123

            obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,

                             stdout=subprocess.PIPE,

                             stderr=subprocess.PIPE

                             )

            stdout=obj.stdout.read()

            stderr=obj.stderr.read()

 

            cmd_res=stdout+stderr

            print(len(cmd_res))

            conn.send(cmd_res)

        except ConnectionResetError:

            break

    conn.close()

 

server.close()

 

1.4.2 客户端:

from socket import *

 

client=socket(AF_INET,SOCK_STREAM)

client.connect(('127.0.0.1',8090))

 

while True:

    cmd=input('>>: ').strip()

    if not cmd:continue

    client.send(cmd.encode('utf-8'))

    data=client.recv(1024)

    print(data.decode('gbk'))

 

client.close()

 

1.5 粘包现象

只有TCP有粘包现象,UDP永远不会粘包,所谓粘包问题主要仍是由于接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所形成的。此外,发送方引发的粘包是由TCP协议自己形成的,TCP为提升传输效率,发送方每每要收集到足够多的数据后才发送一个TCP段。若连续几回须要send的数据都不多,一般TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

TCP(transport control protocol,传输控制协议)是面向链接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,所以,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将屡次间隔较小且数据量小的数据,合并成一个大的数据块,而后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通讯是无消息保护边界的。

tcp是基于数据流的,因而收发的消息不能为空,这就须要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即使是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头。

1.5.1 服务端:

from socket import *

import time

 

server=socket(AF_INET,SOCK_STREAM)

server.bind(('127.0.0.1',8091))

server.listen(5)

 

conn,addr=server.accept()

 

#b'hello'

res1=conn.recv(5) #b'h'

print('res1: ',res1)

 

# b'elloworld'

time.sleep(6)

res2=conn.recv(5)

print('res2: ',res2)

 

conn.close()

server.close()

 

1.5.2 客户端:

from socket import *

import time

 

client=socket(AF_INET,SOCK_STREAM)

client.connect(('127.0.0.1',8091))

 

client.send('hello'.encode('utf-8')) #b'hello'

time.sleep(5)

client.send('world'.encode('utf-8')) #b'world'

 

client.close()

 

1.6 解决粘包现象版本1

问题的根源在于,接收端不知道发送端将要传送的字节流的长度,因此解决粘包的方法就是围绕,如何让发送端在发送数据前,把本身将要发送的字节流总大小让接收端知晓,而后接收端来一个死循环接收完全部数据

low版本的解决方法

1.6.1 struct模块的使用

import struct

 

headers=struct.pack('i',132333)

# print(headers,len(headers))

 

res=struct.unpack('i',headers)

print(res[0])

1.6.2 服务端:

from socket import *

import subprocess

import struct

 

server=socket(AF_INET,SOCK_STREAM)

server.bind(('127.0.0.1',8093))

server.listen(5)

 

while True:

    conn,client_addr=server.accept()

    print(client_addr)

 

    while True:

        try:

            cmd=conn.recv(8096)

            if not cmd:break

 

            #ls -l;sadfasdf;pwd;echo 123

            obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,

                             stdout=subprocess.PIPE,

                             stderr=subprocess.PIPE

                             )

            stdout=obj.stdout.read()

            stderr=obj.stderr.read()

 

            #一、制做固定长度的报头

            total_size = len(stdout) + len(stderr)

            headers=struct.pack('i',total_size)

 

            #二、先发送命令长度

            conn.send(headers)

 

            #三、发送命令的执行结果

            conn.send(stdout)

            conn.send(stderr)

        except ConnectionResetError:

            break

    conn.close()

 

server.close()

 

1.6.3 客户端:

from socket import *

import struct

 

client=socket(AF_INET,SOCK_STREAM)

client.connect(('127.0.0.1',8093))

 

while True:

    cmd=input('>>: ').strip()

    if not cmd:continue

    client.send(cmd.encode('utf-8'))

 

    #一、先接收命令长度

    headers=client.recv(4)

    total_size = struct.unpack('i', headers)[0]

 

    #二、再收命令的结果

    recv_size=0

    data=b''

    while recv_size < total_size:

        recv_data=client.recv(1024)

        data+=recv_data

        recv_size+=len(recv_data)

 

    print(data.decode('gbk'))

 

client.close()

 

1.7 解决粘包现象版本2

为字节流加上自定义固定长度报头,报头中包含字节流长度,而后一次send到对端,对端在接收时,先从缓存中取出定长的报头,而后再取真实数据

咱们能够把报头作成字典,字典里包含将要发送的真实数据的详细信息,而后json序列化,而后用struck将序列化后的数据长度打包成4个字节(4个本身足够用了)发送时:先发报头长度,再编码报头内容而后发送,最后发真实内容。接收时:先手报头长度,用struct取出来,根据取出的长度收取报头内容,而后解码,反序列化,从反序列化的结果中取出待取数据的详细信息,而后去取真实的数据内容

1.7.1 struct模块的使用

import struct

 

 

# headers=struct.pack('q',13233322222222222)

# print(headers,len(headers))

 

# res=struct.unpack('i',headers)

# print(res[0])

 

import json

 

headers={

    'filepath' : 'a.txt',

    'md5' : '123sxd123x123',

    'total_size' : 11111111111111111111111111111111111

}

 

headers_json=json.dumps(headers)

headers_bytes=headers_json.encode('utf-8')

 

# print(len(headers_bytes))

res=struct.pack('i',len(headers_bytes))

print(res,len(res))

 

 

1.7.2 服务端:

from socket import *

import subprocess

import struct

import json

 

server=socket(AF_INET,SOCK_STREAM)

server.bind(('127.0.0.1',8093))

server.listen(5)

 

while True:

    conn,client_addr=server.accept()

    print(client_addr)

 

    while True:

        try:

            cmd=conn.recv(8096)

            if not cmd:break

 

            #ls -l;sadfasdf;pwd;echo 123

            obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,

                             stdout=subprocess.PIPE,

                             stderr=subprocess.PIPE

                             )

            stdout=obj.stdout.read()

            stderr=obj.stderr.read()

 

            #一、制做报头

            headers = {

                'filepath': 'a.txt',

                'md5': '123sxd123x123',

                'total_size': len(stdout) + len(stderr)

            }

 

            headers_json = json.dumps(headers)

            headers_bytes = headers_json.encode('utf-8')

 

            #二、先发报头的长度

            conn.send(struct.pack('i',len(headers_bytes)))

 

            #三、发送报头

            conn.send(headers_bytes)

 

            #四、发送命令的执行结果

            conn.send(stdout)

            conn.send(stderr)

        except ConnectionResetError:

            break

    conn.close()

 

server.close()

 

1.7.3 客户端:

from socket import *

import struct

import json

 

client=socket(AF_INET,SOCK_STREAM)

client.connect(('127.0.0.1',8093))

 

while True:

    cmd=input('>>: ').strip()

    if not cmd:continue

    client.send(cmd.encode('utf-8'))

 

    #一、先接收报头的长度

    headers_size=struct.unpack('i',client.recv(4))[0]

 

    #二、再收报头

    headers_bytes=client.recv(headers_size)

    headers_json=headers_bytes.decode('utf-8')

    headers_dic=json.loads(headers_json)

    print('========>',headers_dic)

    total_size=headers_dic['total_size']

 

    #三、再收命令的结果

    recv_size=0

    data=b''

    while recv_size < total_size:

        recv_data=client.recv(1024)

        data+=recv_data

        recv_size+=len(recv_data)

 

    print(data.decode('gbk'))

 

client.close()

 

1.8 FTP上传下载文件

文件目录:share

上传目录:DOWNLOAD

1.8.1 客户端:

import socket

import struct

import json

import os

 

DOWNLOAD_DIR=r'F:\Python周末20期\day8\08 上传下载文件\DOWNLOAD'

 

class FtpClient:

    def __init__(self,host,port):

        self.host=host

        self.port=port

        self.client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

        self.client.connect((self.host,self.port))

 

    def interactive(self):

        while True:

            data=input('>>: ').strip() #get a.txt

            if not data:continue

            params=data.split() #parmas=['get','a.txt']

            cmd=params[0] #cmd='get'

            if hasattr(self,cmd):

                func=getattr(self,cmd)

                func(params) #func(['get','a.txt'])

 

    def get(self,params):

        params_json=json.dumps(params)

        self.client.send(params_json.encode('utf-8'))

 

        # 一、先接收报头的长度

        headers_size = struct.unpack('i', self.client.recv(4))[0]

 

        # 二、再收报头

        headers_bytes = self.client.recv(headers_size)

        headers_json = headers_bytes.decode('utf-8')

        headers_dic = json.loads(headers_json)

        print('========>', headers_dic)

        filename = headers_dic['filename']

        filesize = headers_dic['filesize']

        filepath = os.path.join(DOWNLOAD_DIR, filename)

 

        # 三、再收真实的数据

        with open(filepath, 'wb') as f:

            recv_size = 0

            while recv_size < filesize:

                line = self.client.recv(1024)

                recv_size += len(line)

                f.write(line)

            print('===>下载成功')

 

if __name__ == '__main__':

    client=FtpClient('127.0.0.1',8081)

    client.interactive()

 

 

1.8.2 服务端:

import socket

import os

import json

import struct

 

SHARE_DIR=r'F:\Python周末20期\day8\08 上传下载文件\SHARE'

 

class FtpServer:

    def __init__(self,host,port):

        self.host=host

        self.port=port

        self.server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

        self.server.bind((self.host,self.port))

        self.server.listen(5)

 

    def serve_forever(self):

        print('server starting...')

        while True:

            self.conn,self.client_addr=self.server.accept()

            print(self.client_addr)

 

            while True:

                try:

                    data=self.conn.recv(1024)  #params_json.encode('utf-8')

                    if not data:break

                    params=json.loads(data.decode('utf-8')) #params=['get','a.txt']

                    cmd=params[0] #

                    if hasattr(self,cmd):

                        func=getattr(self,cmd)

                        func(params)

                    else:

                        print('\033[45mcmd not exists\033[0m')

                except ConnectionResetError:

                    break

            self.conn.close()

        self.server.close()

 

    def get(self,params): #params=['get','a.txt']

        filename=params[1] #filename='a.txt'

        filepath=os.path.join(SHARE_DIR,filename) #

        if os.path.exists(filepath):

            #一、制做报头

            headers = {

                'filename': filename,

                'md5': '123sxd123x123',

                'filesize': os.path.getsize(filepath)

            }

 

            headers_json = json.dumps(headers)

            headers_bytes = headers_json.encode('utf-8')

 

            #二、先发报头的长度

            self.conn.send(struct.pack('i',len(headers_bytes)))

 

            #三、发送报头

            self.conn.send(headers_bytes)

 

            #四、发送真实的数据

            with open(filepath,'rb') as f:

                for line in f:

                    self.conn.send(line)

 

    def put(self):

        pass

 

if __name__ == '__main__':

    server=FtpServer('127.0.0.1',8081)

    server.serve_forever()
相关文章
相关标签/搜索