一、多态和封装
多态
#多态:python自带多态
#多态:在其他语言里:通过类的继承来解决
#在python中不依赖于继承
#有相同特点的类:
#list和tuple 鸭子类型
#1+2 'a'+'b' #比如加号的运算符不会考虑你传进来的是数字类型还是字符串类型,都会进行运算
# len(list)
# len(tuple)
# len这个内置函数不会考虑你传进来的是什么数据类型都会显示长度
#像Python里面这种不考虑数据类型的传参就称为多态
#举例
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
wangcai = Dog() #实例化一个对象
kitty = Cat()
def eat(obj): #但是在其他语言里要想把wangcai和kitty都可以传给obj,需要def eat(Animail obj)才可以,如果def eat(Dog obj),则只能传旺财,也就是需要依靠继承才能传两种
print(obj.name,'在吃')
eat(wangcai) #wangcai和kitty都是对象的数据类型,在python里面不用像其他语言一样指定数据类型,可以随意的传参,传wangcai和kitty都可以
eat(kitty)
封装
#封装就是把方法和属性扔到一个容器里装起来
#私有的:只能在类的内部使用,不能直接在类的外部使用
class A:
__COUNTRY = 'China' #加了双下划线就被定义成一个私有的静态属性
def func(self):
print(A.__COUNTRY)
#A.__COUNTRY #发现不能在外部被使用会报错
#a = A()
#a.func() #只能在内部调用func方法的时候使用
print(A.__dict__) #打印字典可以发现__COUNTRY这个属性被定义为_A__COUNTRY,注意A前面是一个下划线,在A后是两个下划线
print(A._A__COUNTRY) #只有写成这种格式才能在外部使用,但不合法
举例:商店里的商品都做了0.8的打折
class Goods:
__DISCONT = 0.8 #私有的静态属性
def __init__(self,org_price):
self.price = org_price * Goods.__DISCONT
apple = Goods(10) #实例化一个对象,就会调用类内的init方法,对象=self,传的参数10=org_price
print(apple.price)
定义私有的方法
class A:
def __func(self): #定义一个私有方法,在方法前面加双下划线
print(111)
a = A()
a._A__func() #要想在类外部使用,需要这种格式去调用
总结:只要是私有的,类内正常使用,类外不能直接使用,如果一定要用,格式为:_类名__属性名或者方法名
class A:
__B = 'haha' #定义一个私有的静态属性
class B(A):
pass
print(B.__B) #发现会报错
print(A.__dict__) #发现A的字典里有这个属性,并且变成 _A__B
print(B.__dict__) #发现B的字典里没有这个属性
总结:私有的属性和方法在子类中是不能被继承的
什时候用到私有的属性和方法
1、当我们隐藏一个变量不被外部调用的时候
2、当我们写在父类中并且不希望被子类继承的时候
二、类中的装饰器
#property
#classmethod
#staticmethod
class Circle: #定义一个类
def __init__(self,r): #类中有init方法
self.r=r
def area(self): #圆的面积方法
return self.r*self.r*3.14
c1=Circle(5) #格式化一个对象的时候就会调用类中的init方法,并且对象=self,传的参数5=r
print(c1.area()) #调用area方法
class Circle:
def __init__(self,r):
self.r=r
@property #增加这个装饰器
def area(self):
return self.r*self.r*3.14
c1=Circle(5)
print(c1.area) #调用类中的方法的时候就不用加括号了
class A:
def __init__(self,name):
self.__name = name
@property #加上这个装饰器后,在调用下面的方法的时候就可以不加括号了,看起来美观
def name(self):
return self.__name
a = A('alex')
print(a.name) #调用name方法不用加括号,相当于调用name这个属性
#a.name='alexsb' #发现name属性不可以修改了,会报错
#希望不从外部被修改,所以设置一个私有的属性
#但是又希望能从外部查看,所以使用一个property
如果要想让这个属性在外部能被修改需要写成如下
class A:
def __init__(self,name):
self.__name = name
@property #加上这个装饰器后,在调用下面的方法的时候就可以不加括号了,看起来美观
def name(self):
return self.__name
@name.setter #加上此装饰器后就可以在外部被修改了
def name(self,new_name):
if type(new_name) is str:
self.__name = new_name
a = A('alex')
print(a.name)
a.name = 'alex_sb' #调用的是setter下面的name方法,此时的new_name=alex_sb, self.__name=alex_sb
print(a.name) #再调用name时是调用property下面的name方法,返回的是alex_sb,因为此时的self.__name=alex_sb
总结:@property 的作用是把方法装饰成一个属性,调用时就不用像调用方法一样加括号了
#classmethod
class A:
country = 'china'
def func(self): #此种方法必须先先实例化一个对象,对象才能调用此方法
self.name = 'alex'
@classmethod
def c_method(cls):
print('in class methos')
print(cls.country)
@staticmethod
def s_method():
print('in static method')
A.c_method()
A.s_method()
发现类也可以调用类里面的方法了,如果不加装饰器,类是不能调用类内的方法的。必须要对象才能调用类中的方法
三、反射
#issubclass(子类,父类)
class A:pass
class B(A):pass
print(issubclass(B,A)) #判断B是否是A的子类
print(issubclass(B,object)) #objerct是类祖宗
print(issubclass(A,object))
#isinstance(对象,类名)
#判断一个对象是不是这个类的对象也就是实例
class A:pass
class B(A):pass
b = B()
print(isinstance(b,B))
print(isinstance(b,A)) #因为B继承A,所以b也是A的对象
执行结果:都是True
#什么叫反射
#使用字符串的形式去获取变量
a = 1
b = 2
name = input('变量名:')
if name == 'a':
print(a)
实现上面的功能就叫反射,输入的是变量名,打印的是变量的值
class Person:
role = 'person'
country = 'china'
if hasattr(Person,'role'): #判断类里面是否有role这个属性
print(getattr(Person,'role')) #如果有就打印属性的值
执行结果:person,这样就通过使用字符串的形式打印变量的值
class Person:
role = 'person'
country = 'china'
def __init__(self,name,age): #定义一个init方法
self.name = name #方法里面有name和age属性
self.age = age
alex = Person('alex',18) #实例化一个对象alex
if hasattr(alex,'name'): #判断alex对象里是否有name属性
print(getattr(alex,'name')) #如果有就打印name属性的值
执行结果:alex,使用字符串的形式打印变量的值
class Person:
role = 'person'
country = 'china'
def __init__(self,name,age): #定义一个init方法
self.name = name #方法里面有name和age属性
self.age = age
def func(self):
print('%s in func' %self.name)
alex = Person('alex',18) #实例化一个对象alex
if hasattr(alex,'func'): #判断alex对象里是否有func这个方法
func=(getattr(alex,'func')) #如果有就获取这个方法
func()
执行结果:alex in func,通过使用字符串的形式打印方法的执行结果
使用反射就可以获取类中的属性和对象中的属相和方法
注意:不能通过类获取类中的方法,只能通过对象获取类中的方法
#seattr:增加对象的属性和方法
给对象增加一个属性
class Person:
role = 'person'
country = 'china'
def __init__(self,name,age): #定义一个init方法
self.name = name #方法里面有name和age属性
self.age = age
def func(self):
print('%s in func' %self.name)
alex = Person('alex',18) #实例化一个对象alex
if hasattr(alex,'func'): #判断alex对象里是否有func这个方法
func=(getattr(alex,'func')) #如果有就获取这个方法
func()
setattr(alex,'sex','不详') #给alex对象增加一个sex属性
print(alex.sex)
给对象增加一个方法
class Person:
role = 'person'
country = 'china'
def __init__(self,name,age): #定义一个init方法
self.name = name #方法里面有name和age属性
self.age = age
def func(self):
print('%s in func' %self.name)
alex = Person('alex',18) #实例化一个对象alex
# if hasattr(alex,'func'): #判断alex对象里是否有func这个方法
# func=(getattr(alex,'func')) #如果有就获取这个方法
# func()
def func2(self): #定义一个函数
print('%s in func' % self.name)
setattr(alex,'func2',func2) #给alex增加一个func2方法,#setattr绑定方法是一个假的,在使用的时候必须要手动传self对象
alex.func2(alex)
在类里面变量称为属性,函数称为方法
#delattr:删除某个属性或者方法
delattr(alex,'name') #等于del alex.name
在模块中使用反射
#demo1.py
a = 'aaa'
b = 'bbb'
def qqxing():
print('qqxing')
import demo1
print(demo1.a) #不用反射时调用demo1模块下a属性
print(getattr(demo1,'a')) #使用反射调用模块中的属性
demo1.qqxing()
(getattr(demo1,'qqxing'))() #使用反射调用模块中的方法
反射当前模块
a = 'aaaa'
import sys
this_modules = sys.modules[__name__] #表示当前模块
print(getattr(this_modules,'a')) #表示打印当前模块下的a属性的值
反射的意义:a.b ---> getattr(a,’b’)
四、模块中的内置方法
# __call__方法
class A:
def __call__(self, *args, **kwargs):
print('aaa')
a = A() #实例化一个对象a
a() #就是在调用__call__方法
执行结果:aaa
# __new__方法
class A:
def __init__(self): #初始化方法
self.x = 1
print('in init function')
def __new__(cls, *args, **kwargs): #构造方法
print('in new function')
return object.__new__(A,*args, **kwargs)
a = A() #先构造一个对象,再初始化属性
执行结果:
in new function
in init function
#单例模式
#从头到尾只有一个实例
class Singleton:
def __new__(cls, *args, **kw):
if not hasattr(cls,'_instance'): #如果cls类里面没有_instance属性
cls._instance = object.__new__(cls,*args, **kw) #就创造一个属性=产生的对象
return cls._instance
one = Singleton() #实例化一个对象
two = Singleton() #此时再实例化一个对象的时候,因为cls类里面有_instance属性了,就不会再创造对象了,直接返回上一个对象
two.a = 3 #创建一个新的属性a=3
print(one.a) #因为one和two是同一个对象,所以one.a=two.a #调用one对象下的a属性
print(id(one))
print(id(two))
print(one == two)
print(one is two)
执行结果:
3
3279518516392
3279518516392
True
True
#__str__和__repr__方法
class B:
def __str__(self):
return 'str : class B'
def __repr__(self):
return 'repr : cl ass B'
b = B()
print('%s' % b) #%s打印的就是__str__的结果
print('%r' % b) #%r打印的是__repr__的结果
执行结果:
str : class B
repr : class B
#__hash__
class A:
def __init__(self):
self.a = 1
self.b = 2
def __hash__(self):
return hash(str(self.a)+str(self.b))
a = A()
print(hash(a))#就是打印__hash__方法的返回值
#__eq__
class A:
def __init__(self):
self.a = 1
self.b = 2
def __eq__(self,obj):
if self.a == obj.a and self.b == obj.b:
return True
a = A()
b = A()
print(a == b) #就是打印__eq__方法的返回值
面试题
#这个类有100个对象,只要name和sex相同,我就认为是相同的对象
#要求对100个对象去重
class Person:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def __hash__(self):
return hash(self.name+self.sex)
def __eq__(self, other):
if self.name == other.name and self.sex == other.sex:return True
p_lst = []
for i in range(100):
p_lst.append(Person('egon',i,'male'))
print(p_lst)
print(set(p_lst))
五、网络编程基础
osi七层协议
互联网协议按照功能不同分为osi七层或tcp/ip五层或tcp/ip四层

每层运行常见物理设备

我们将应用层,表示层,会话层并作应用层,从tcp/ip五层协议的角度来阐述每层的由来与功能,搞清楚了每层的主要协议就理解了整个互联网通信的原理。
首先,用户感知到的只是最上面一层应用层,自上而下每层都依赖于下一层,所以我们从最下一层开始切入,比较好理解,每层都运行特定的协议,越往上越靠近用户,越往下越靠近硬件
1、物理层
物理层由来:上面提到,孤立的计算机之间要想一起玩,就必须接入internet,言外之意就是计算机之间必须完成组网
物理层功能:主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0
2、 数据链路层
数据链路层由来:单纯的电信号0和1没有任何意义,必须规定电信号多少位一组,每组什么意思
数据链路层的功能:定义了电信号的分组方式
以太网协议:
早期的时候各个公司都有自己的分组方式,后来形成了统一的标准,即以太网协议ethernet
ethernet规定
一组电信号构成一个数据包,叫做‘帧’,每一数据帧分成:报头head和数据data两部分

head包含:(固定18个字节)
发送者/源地址,6个字节
接收者/目标地址,6个字节
数据类型,6个字节
data包含:(最短46字节,最长1500字节)
数据包的具体内容
head长度+data长度=最短64字节,最长1518字节,超过最大限制就分片发送
mac地址:
head中包含的源和目标地址由来:ethernet规定接入internet的设备都必须具备网卡,发送端和接收端的地址便是指网卡的地址,即mac地址
mac地址:每块网卡出厂时都被烧制上一个世界唯一的mac地址,长度为48位2进制,通常由12位16进制数表示(前六位是厂商编号,后六位是流水线号)
广播:
有了mac地址,同一网络内的两台主机就可以通信了(一台主机通过arp协议获取另外一台主机的mac地址)
ethernet采用最原始的方式,广播的方式进行通信,即计算机通信基本靠吼
3、网络层
网络层功能:引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址
IP协议:
规定网络地址的协议叫ip协议,它定义的地址称之为ip地址,广泛采用的v4版本即ipv4,它规定网络地址由32位2进制表示
范围0.0.0.0-255.255.255.255
一个ip地址通常写成四段十进制数,例:172.16.10.1
ip数据包
ip数据包也分为head和data部分,无须为ip包定义单独的栏位,直接放入以太网包的data部分
head:长度为20到60字节
data:最长为65,515字节。
而以太网数据包的”数据”部分,最长只有1500字节。因此,如果IP数据包超过了1500字节,它就需要分割成几个以太网数据包,分开发送了。

arp协议功能:广播的方式发送数据包,获取目标主机的mac地址
工作过程
主机A的IP地址为192.168.1.1,MAC地址为0A-11-22-33-44-01;
主机B的IP地址为192.168.1.2,MAC地址为0A-11-22-33-44-02;
当主机A要与主机B通信时,地址解析协议可以将主机B的IP地址(192.168.1.2)解析成主机B的MAC地址,以下为工作流程:
第1步:根据主机A上的路由表内容,IP确定用于访问主机B的转发IP地址是192.168.1.2。然后A主机在自己的本地ARP缓存中检查主机B的匹配MAC地址。
第2步:如果主机A在ARP缓存中没有找到映射,它将询问192.168.1.2的硬件地址,从而将ARP请求帧广播到本地网络上的所有主机。源主机A的IP地址和MAC地址都包括在ARP请求中。本地网络上的每台主机都接收到ARP请求并且检查是否与自己的IP地址匹配。如果主机发现请求的IP地址与自己的IP地址不匹配,它将丢弃ARP请求。
第3步:主机B确定ARP请求中的IP地址与自己的IP地址匹配,则将主机A的IP地址和MAC地址映射添加到本地ARP缓存中。
第4步:主机B将包含其MAC地址的ARP回复消息直接发送回主机A。
第5步:当主机A收到从主机B发来的ARP回复消息时,会用主机B的IP和MAC地址映射更新ARP缓存。本机缓存是有生存期的,生存期结束后,将再次重复上面的过程。主机B的MAC地址一旦确定,主机A就能向主机B发送IP通信了。
总结:ip地址+MAC地址可以定位到世界上独一无二的一台机器,arp协议地址解析协议,是根据IP地址获取物理地址的一个TCP/IP协议
4、 传输层
传输层的由来:网络层的ip帮我们区分子网,以太网层的mac帮我们找到主机,然后大家使用的都是应用程序,你的电脑上可能同时开启qq,暴风影音,等多个应用程序,那么我们通过ip和mac找到了一台特定的主机,如何标识这台主机上的应用程序,答案就是端口,端口即应用程序与网卡关联的编号。
传输层功能:建立端口到端口的通信
补充:端口范围0-65535,0-1023为系统占用端口
tcp协议:
可靠传输,TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包不必再分割。

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

tcp三次握手和四次挥手

5、 应用层
应用层由来:用户使用的都是应用程序,均工作于应用层,互联网是开发的,大家都可以开发自己的应用程序,数据多种多样,必须规定好数据的组织形式
应用层功能:规定应用程序的数据格式。
例:TCP协议可以为各种各样的程序传递数据,比如Email、WWW、FTP等等。那么,必须有不同协议规定电子邮件、网页、FTP数据的格式,这些应用程序协议就构成了”应用层”。

总结:
1、ip+port可以定位全世界范围内独一无二的一个应用程序
2、协议的形式都是:报头+数据
其中报头必须是固定长度
六、Socket套接字
我们知道两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。
能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。
七、基于tcp协议的简单套接字通信




#服务端.py
import socket
#买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM代表TCP协议,tcp协议又称为流式协议
#绑定手机卡
phone.bind(('127.0.0.1',8080))
#开机
phone.listen(5) #挂起的连接数,也就是你在通讯的时候别人也会给你拨号打电话,就会挂起,等你处理完当前通讯后就从挂起的里面去找,然后接着处理
#等电话链接
print('starting...')
conn,client_addr=phone.accept() #(套接字链接,客户端的ip和port)
print(conn)
print(client_addr)
#收消息
data=conn.recv(1024) # 1024最大的限制
print('客户端数据: ',data)
#发消息
conn.send(data.upper()) #客户端发一个消息,我就给回一个大写的消息,基于conn这个套接字链接
#挂电话
conn.close()
#关机
phone.close()
#客户端.py
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM代表TCP协议
#发起电话链接
phone.connect(('127.0.0.1',8080))
#发消息
phone.send('hello'.encode('utf-8'))
#收消息
data=phone.recv(1024)
print(data)
#关机
phone.close()
执行时先执行服务端,再执行客户端
八、加上通讯循环与链接循环的简单套接字通讯
#服务端.py
import socket
#买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM代表TCP协议
# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加
#绑定手机卡
phone.bind(('127.0.0.1',8080))
#开机
phone.listen(5)
#等电话链接
print('starting...')
while True: #链接循环
conn,client_addr=phone.accept() #(套接字链接,客户端的ip和port)
print(client_addr)
while True: #通信循环
#收消息
try:
data=conn.recv(1024) # 1024最大的限制
print('客户端数据: ',data)
if not data:break #针对linux系统,客户端单方面断开连接后。不会报下面的异常,而是会一直收到数据,只不过是空数据,如果不加if判断服务端就会进入死循环
#发消息
conn.send(data.upper())
except ConnectionResetError: #try和except之间是捕捉异常,当之间的代码出现 ConnectionResetError时就跳出while循环,这个异常是当客户端单方面断开的时候出现的异常
break
#挂电话
conn.close() #挂完电话之后进入下一次循环,继续等待另外一个客户端的连接
#关机
phone.close()
#客户端.py
import socket
#买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM代表TCP协议
#发起电话链接
phone.connect(('127.0.0.1',8080))
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.decode('utf-8'))
#关机
phone.close()
九、实现ssh远程执行命令1
subprocess模块
import subprocess #此模块的作用是开一个子进程去执行命令
#
obj=subprocess.Popen('dir',shell=True,
stdout=subprocess.PIPE, #把标准正确的输出丢给管道
stderr=subprocess.PIPE) #把标准错误的输出丢给管道来处理
# 相当于把命令的执行结果丢给管道,然后再从管道中去取
stdout_res1=obj.stdout.read() #从管道中读命令的执行结果
print(stdout_res1.decode('gbk'))
stdout_res2=obj.stdout.read() #在第一次读时,管道就空了
print('========>',stdout_res2.decode('gbk'))
#输入的是错误命令
import subprocess
obj=subprocess.Popen('diasdfasdfasr',shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout_res1=obj.stdout.read() #命令执行错误会把结果送到stderr管道,就要从stderr管道中去读
print(stdout_res1.decode('gbk'))
stdout_res2=obj.stderr.read() #命令执行错误会把结果送到stderr管道
print(stdout_res2.decode('gbk'))
#执行命令
import subprocess
obj=subprocess.Popen('tasklist | findstr pycharm',shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
print(obj.stdout.read().decode('gbk'))
#stdin标准输入
import subprocess
obj1=subprocess.Popen('tasklist',shell=True,
stdout=subprocess.PIPE,)
obj2=subprocess.Popen('findstr pycharm',shell=True,
stdin=obj1.stdout, #标准输入,来自上一个命令的执行结果
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
print(obj2.stdout.read().decode('gbk'))
十、粘包

#服务端.py
from socket import *
server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen()
conn,addr=server.accept()
data1=conn.recv(1024) #客户端先发5个bytes,再发5个bytes,服务端一次就可以接收完,服务端打印的时候就会发生粘包现象,要解决可以把1024改为5
print('data1:',data1)
data2=conn.recv(1024)
print('data2:',data2)
#客户端.py
from socket import *
client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))
client.send('hello'.encode('utf-8'))
client.send('world'.encode('utf-8'))
先执行服务端,再执行客户端,服务端的执行结果:
data1: b'helloworld' #发现粘包现象
data2: b''
因为tcp协议是流式协议,为了节省网络IO,客户端会把发送字节比较小的并且间隔时间比较短的内容粘在一起发送。这样客户端如果一次接收的bytes比较大。就会把粘包一次接收
十一、 struct模块
将整形数字转化为Bytes类型进行tcp的流式传输,因为只有转化为Bytes类型才能通过网络传输

import struct
res1=struct.pack('i',23322) #i代表打包后的结果是4个Bytes,打包的目标是整型数字23322,并且无论这个整形数字是多少位,都会打包成4个Bytes,
print(res1)
print(len(res1))
#
res2=struct.unpack('i',res1) #解包,转化回整形数字
print(res2) #发现在一个元组里面
print(res2[0]) #取出传输的整形数字
执行结果:
b'\x1a[\x00\x00'
4
(23322,)
23322
res3=struct.pack('q',21222222222222222) #q代表的整数类型更长
如何解决粘包
实现远程执行ssh命令功能(low版)
#服务端.py
import socket
import subprocess
import struct
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM代表TCP协议
phone.bind(('127.0.0.1',8080))
phone.listen(5)
while True:
conn,client_addr=phone.accept() #(套接字链接,客户端的ip和port)
print(client_addr)
while True: #通信循环
#收消息
try:
cmd=conn.recv(1024) # 1024最大的限制
if not cmd:break #针对linux系统
#执行,拿到执行结果
obj = subprocess.Popen(cmd.decode('gbk'), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout_res=obj.stdout.read()
stderr_res=obj.stderr.read()
#先发报头
total_size=len(stderr_res)+len(stdout_res) #命令的执行结果一共有多少个字节
conn.send(struct.pack('i',total_size)) #将整形数字打包为4个字节的bytes类型发给客户端
#再发真实的数据
conn.send(stdout_res)
conn.send(stderr_res) #报头和真实数据会粘在一起发给客户端
except ConnectionResetError:
break
#挂电话
conn.close()
#关机
phone.close()
#客户端.py
import socket
import struct
#买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM代表TCP协议
#发起电话链接
phone.connect(('127.0.0.1',8080))
while True:
#发消息
cmd=input('>>: ').strip()
if not cmd:continue
phone.send(cmd.encode('gbk'))
#先收报头
header_struct=phone.recv(4) #规定只能接收4个字节
total_size=struct.unpack('i',header_struct)[0] #解包后就知道这个命令的执行结果一共有多少个字节
#再收消息
cmd_res=b'' #定义命令的执行结果刚开始是空的bytes
recv_size=0
while recv_size < total_size:
recv_data=phone.recv(1024) #表示一次最大收1024个bytes,所以每次不一定收正好是1024个字节
cmd_res+=recv_data #命令的执行结果等于每次接收到的数据字符串进行拼接
recv_size+=len(recv_data)
print(cmd_res.decode('gbk'))
#关机
phone.close()
总结:解决粘包的关键就是客户端知道命令的执行结果有多少个字节,然后收的时候利用while循环,每次直到收完为止
十二、实现远程执行ssh命令最终版本
前提准备
#制作报头
import json
header_dic={
'filename':'a.txt',
'total_size':123111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112312111111111,
'md5':'xxxxxxxxx'
}
head_json=json.dumps(header_dic) #序列化为json格式的字符串
head_bytes=head_json.encode('utf-8') #转发为bytes类型才能通过网络发送
print(head_bytes)
print(len(head_bytes))
#服务端.py
import socket
import subprocess
import struct
import json
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM代表TCP协议
phone.bind(('127.0.0.1',8080))
phone.listen(5)
while True:
conn,client_addr=phone.accept() #(套接字链接,客户端的ip和port)
print(client_addr)
while True: #通信循环
#收消息
try:
cmd=conn.recv(1024) # 1024最大的限制
if not cmd:break #针对linux系统
#执行,拿到执行结果
obj = subprocess.Popen(cmd.decode('gbk'), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout_res=obj.stdout.read()
stderr_res=obj.stderr.read()
# 制作报头
header_dic = {
'filename': 'a.txt',
'total_size': len(stdout_res)+len(stderr_res),
'md5': 'xxxxxxxxx'
}
head_json = json.dumps(header_dic)
head_bytes = head_json.encode('utf-8')
#先发报头长度
conn.send(struct.pack('i',len(head_bytes)))
#先发报头
conn.send(head_bytes)
#再发真是的数据
conn.send(stdout_res)
conn.send(stderr_res)
except ConnectionResetError:
break
#挂电话
conn.close()
#关机
phone.close()
#客户端.py
import socket
import struct
import json
#买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM代表TCP协议
#发起电话链接
phone.connect(('127.0.0.1',8080))
while True:
#发消息
cmd=input('>>: ').strip()
if not cmd:continue
phone.send(cmd.encode('gbk'))
#先收报头长度
struct_res=phone.recv(4)
header_size=struct.unpack('i',struct_res)[0]
#再收报头
head_bytes=phone.recv(header_size)
head_json=head_bytes.decode('utf-8')
head_dic=json.loads(head_json) #反序列化我字典
print(head_dic)
#最收消息
cmd_res=b''
recv_size=0
total_size=head_dic['total_size']
while recv_size < total_size:
recv_data=phone.recv(1024)
cmd_res+=recv_data
recv_size+=len(recv_data)
print(cmd_res.decode('gbk'))
#关机
phone.close()
网友评论