一,接口与归一化设计
为什么要用接口?
接口提取了一群类共同的函数,可以把接口当做一个函数的集合,然后让子类去实现接口中的函数,这么做的意义在于归一化。
什么叫归一化?
就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化的好处:
1.归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
2.归一化使得高层的外部使用者也可以不加区分的处理所有接口兼容的对象集合
(1):就好像linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存,磁盘,网络还是屏幕(当然,对底层设计者,当然也可以区分出‘字符设备’和‘块设备’,然后做出针对性的设计,细致到什么程度,视需求而定)
(2):再比如,我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,等等,他们都实现了汽车接口,这样就好办了,大家只要学会怎么开就行!操作手法(函数调用)都一样。
抽象类((abc.abstractmethod定义父类有read,和write,子类也必须有))
import abc class Interface(metaclass=abc.ABCMeta): all_type=‘file‘ @abc.abstractclassmethod def read(self): pass @abc.abstractclassmethod def write(self): pass class Text(Interface): def read(self): pass def write(self): pass t=Text() print(t.all_type)
多态与多态性
多态:同一种事物的多种形态
多态性:可以在不考虑实例类型的前提下使用实例
多态性的好处:增加了程序的扩展性
class Animal: #同一类事物:动物 def talk(self): pass class People(Animal): #动物的形态之一:人 def talk(self): print(‘say hello‘) class Dog(Animal): #动物的形态之二:狗 def talk(self): print(‘say wangwang‘) class Pig(Animal): #动物的形态之三:猪 def talk(self): print(‘say aoao‘) class Cat(Animal): def talk(self): print(‘say miaomiao‘) class Bird: def talk(self): print(‘jijiji‘) #多态性:可以在不考虑实例类型的前提下使用实例 p1=People() d=Dog() p2=Pig() c=Cat() b=Bird() # p1.talk() # d.talk() # p2.talk() # c.talk() # b.talk() def Talk(animal): animal.talk() #p1.talk() Talk(p1) Talk(d) Talk(p2) Talk(c) Talk(b) #多态性的好处
封装
___隐藏
1.#先看如何隐藏 class Foo: __N=111111 #_Foo__N def __init__(self,name): self.__Name=name #self._Foo__Name=name def __f1(self): #_Foo__f1 print(‘f1‘) def f2(self): self.__f1() #self._Foo__f1() f=Foo(‘egon‘) # print(f.__N) # f.__f1() # f.__Name # f.f2() #这种隐藏需要注意的问题: #1:这种隐藏只是一种语法上变形操作,并不会将属性真正隐藏起来 # print(Foo.__dict__) # print(f.__dict__) # print(f._Foo__Name) # print(f._Foo__N)
2:这种语法级别的变形,是在类定义阶段发生的,并且只在类定义阶段发生 Foo.__x=123123123123123123123123123123123123123123 print(Foo.__dict__) print(Foo.__x) f.__x=123123123 print(f.__dict__) print(f.__x)
3:在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。 class Foo: def __f1(self): #_Foo__f1 print(‘Foo.f1‘) def f2(self): self.__f1() #self._Foo_f1 class Bar(Foo): def __f1(self): #_Bar__f1 print(‘Bar.f1‘) b=Bar()b.f2()
#封装不是单纯意义的隐藏 #1:封装数据属性:将属性隐藏起来,然后对外提供访问属性的接口,关键是我们在接口内定制一些控制逻辑从而严格控制使用对数据属性的使用 class People: def __init__(self,name,age): if not isinstance(name,str): raise TypeError(‘%s must be str‘ %name) if not isinstance(age,int): raise TypeError(‘%s must be int‘ %age) self.__Name=name self.__Age=age def tell_info(self): print(‘<名字:%s 年龄:%s>‘ %(self.__Name,self.__Age)) def set_info(self,x,y): if not isinstance(x,str): raise TypeError(‘%s must be str‘ %x) if not isinstance(y,int): raise TypeError(‘%s must be int‘ %y) self.__Name=x self.__Age=y # p=People(‘gongxu‘,18) # p.tell_info() # # # p.set_info(‘Gongxu‘,‘19‘) # p.set_info(‘Gongxu‘,19) # p.tell_info()
2:封装函数属性:为了隔离复杂度
取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱 对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做 隔离了复杂度,同时也提升了安全性 class ATM: def __card(self): print(‘插卡‘) def __auth(self): print(‘用户认证‘) def __input(self): print(‘输入取款金额‘) def __print_bill(self): print(‘打印账单‘) def __take_money(self): print(‘取款‘) def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() a=ATM() a.withdraw()
静态属性
求bmi指数 class People: def __init__(self,name,weight,height): self.name=name self.weight=weight self.height=height @property def bmi(self): return self.weight/(self.height**2) p=People(‘haha‘,53,1.63) print(p.bmi
#访问,设置,删除(了解) class Foo: def __init__(self,x): self.__Name=x @property def name(self): return self.__Name @name.setter def name(self,val): if not isinstance(val,str): raise TypeError self.__Name=val @name.deleter def name(self): # print(‘=-====>‘) # del self.__Name raise PermissionError f=Foo(‘egon‘) # print(f.name) # # # f.name=‘Egon‘ # f.name=123123123213 # print(f.name) del f.name print(f.name)
item系列
class Foo: def __getitem__(self, item): print(‘=====>get‘) return self.__dict__[item] def __setitem__(self, key, value): self.__dict__[key]=value # setattr(self,key,value) def __delitem__(self, key): self.__dict__.pop(key) f=Foo() # f.x=1 # print(f.x) # print(f.__dict__) f[‘x‘]=123123123123 # del f[‘x‘] print(f[‘x‘])
打印对象信息__str__
class People: def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex def __str__(self): #在对象被打印时触发执行 return ‘<name:%s age:%s sex:%s>‘ %(self.name,self.age,self.sex) p1=People(‘egon‘,18,‘male‘) p2=People(‘alex‘,38,‘male‘) print(p1) print(p2)
析构方法__del__
class Foo: def __init__(self,x): self.x=x def __del__(self): #在对象资源被释放时触发 print(‘-----del------‘) print(self) f=Foo(100000) del f print(‘=======================>‘)
异常处理
报错格式及顺序
try: print("try报错 就匹配执行except类型,没有则报错,Exception指定所有报错") except Exception as e: print(e) else: print("如果try没报错,执行else") finally: print("无论执行try或else 最后都执行finally")
简单实例
#输入1或0 try: s = 1 if s is 0: #print("%s 是错误的"%s) raise NameError("%s 是错误的"%s) #如果引发NameError异常,后面的代码将不能执行 print(len(s)) except TypeError: print("%s 是错误的" % s)
实例
#捕获出错后,提示异常信息,且不退出,捕获错误可多个 #Exception捕获所有错误,写其他错误类型就是捕获具体错误类型,写一种类型加一个except #输入字符串 提示报错并继续 while True: num1 = input("请输入第一位数字:") num2 = input("请输入第二位数字:") try: num1 = int(num1) num2 = int(num2) result = num1 + num2 exit("%s + %s = %s"%(num1,num1,result)) except NameError as e: #捕获具体错误,后操作如下,报错重要性可写入log,也可忽略, print("出现异常,信息如下:") print(e) except Exception as e: #捕获所有错误 print("出现异常,信息如下:") print(e) #invalid literal for int() with base 10: ‘asfd‘ 这是说第10行输入不是int类型
主动错误模式
try: raise Exception(‘错误了。。。‘) except Exception as e: print(e) #错误了。。。
自定义异常模式
class guolm(Exception): #创建类 传参是Exception(所有报错) def __init__(self,msg): self.message = msg def __str__(self): return self.message try: raise guolm("我的异常") #主动异常 except guolm as e: print(e) #我的异常
断言
assert 1 == 1 #条件满足则继续 assert 1 == 2 #条件不满足则报错
错误类型
python2.x捕获异常语法: try: ...some functions... except Exception, e: print(e) python3.x捕获异常语法: try: ...some functions... except Exception as e: print(e) 注意这里 Exception, e 变成了 Exception as e python常见的异常类型 0. Exception 捕获所有错误 1. NameError: 尝试访问一个未申明的变量 >>> v NameError: name ‘v‘ is not defined 2. ZeroDivisionError: 除数为0 >>> v = 1/0 ZeroDivisionError: int division or modulo by zero 3. SyntaxError: 语法错误 int int SyntaxError: invalid syntax (<pyshell#14>, line 1) 4. IndexError: 索引超出范围 List = [2] >>> List[3] Traceback (most recent call last): File "<pyshell#18>", line 1, in <module> List[3] IndexError: list index out of range 5. KeyError: 字典关键字不存在,试图访问字典里不存在的键 Dic = {‘1‘:‘yes‘, ‘2‘:‘no‘} >>> Dic[‘3‘] Traceback (most recent call last): File "<pyshell#20>", line 1, in <module> Dic[‘3‘] KeyError: ‘3‘ 6. IOError: 输入输出错误:基本上是无法打开文件 >>> f = open(‘abc‘) IOError: [Errno 2] No such file or directory: ‘abc‘ 7. AttributeError: 访问未知对象属性 >>> class Worker: def Work(): print("I am working") >>> w = Worker() >>> w.a Traceback (most recent call last): File "<pyshell#51>", line 1, in <module> w.a AttributeError: ‘Worker‘ object has no attribute ‘a‘ 8.ValueError: 数值错误 >>> int(‘d‘) Traceback (most recent call last): File "<pyshell#54>", line 1, in <module> int(‘d‘) ValueError: invalid literal for int() with base 10: ‘d‘ 9. TypeError: 类型错误 >>> iStr = ‘22‘ >>> iVal = 22 >>> obj = iStr + iVal; Traceback (most recent call last): File "<pyshell#68>", line 1, in <module> obj = iStr + iVal; TypeError: Can‘t convert ‘int‘ object to str implicitly 10. AssertionError: 断言错误 >>> assert 1 != 1 Traceback (most recent call last): File "<pyshell#70>", line 1, in <module> assert 1 != 1 AssertionError 11. MemoryError: 内存耗尽异常 12.ImportError 无法引入模块或包;基本上是路径问题或名称错误 13.IndentationError 语法错误(的子类) ;代码没有正确对齐 14.KeyboardInterrupt Ctrl+C被按下 15.UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量, 导致你以为正在访问它 16. NotImplementedError: 方法没实现引起的异常 class Base(object): def __init__(self): pass def action(self): #抛出异常,说明该接口方法未实现 raise NotImplementedError 17. LookupError: 键、值不存在引发的异常 LookupError异常是IndexError、KeyError的基类, 如果你不确定数据类型是字典还是列表时,可以用LookupError捕获此异常 18. StandardError 标准异常 除StopIteration, GeneratorExit, KeyboardInterrupt 和SystemExit外,其他异常都是StandarError的子类。 错误检测与异常处理区别在于:错误检测是在正常的程序流中,处理不可预见问题的代码,例如一个调用操作未能成功结束
更多错误
ArithmeticError AssertionError AttributeError BaseException BufferError BytesWarning DeprecationWarning EnvironmentError EOFError Exception FloatingPointError FutureWarning GeneratorExit ImportError ImportWarning IndentationError IndexError IOError KeyboardInterrupt KeyError LookupError MemoryError NameError NotImplementedError OSError OverflowError PendingDeprecationWarning ReferenceError RuntimeError RuntimeWarning StandardError StopIteration SyntaxError SyntaxWarning SystemError SystemExit TabError TypeError UnboundLocalError UnicodeDecodeError UnicodeEncodeError UnicodeError UnicodeTranslateError UnicodeWarning UserWarning ValueError Warning ZeroDivisionError 更多异常
Socket
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求。
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
socket和file的区别:
- file模块是针对某个指定文件进行【打开】【读写】【关闭】
- socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
import socket ip_port = (‘127.0.0.1‘,9999) sk = socket.socket() sk.bind(ip_port) sk.listen(5) while True: print ‘server waiting...‘ conn,addr = sk.accept() client_data = conn.recv(1024) print client_data conn.sendall(‘不要回答,不要回答,不要回答‘) conn.close()
服务器
import socket ip_port = (‘127.0.0.1‘,9999) sk = socket.socket() sk.connect(ip_port) sk.sendall(‘请求回答‘) server_reply = sk.recv(1024) print server_reply sk.close()
客户端
套接字发展史及分类
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。
基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
基于网络类型的套接字家族
套接字家族的名字:AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)
import socket 2 socket.socket(socket_family,socket_type,protocal=0) 3 socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。 4 5 获取tcp/ip套接字 6 tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 7 8 获取udp/ip套接字 9 udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 10 11 由于 socket 模块中有太多的属性。我们在这里破例使用了‘from module import *‘语句。使用 ‘from socket import *‘,我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能 大幅减短我们的代码。 12 例如tcpSock = socket(AF_INET, SOCK_STREAM)
参数一:地址簇
socket.AF_INET IPv4(默认)
socket.AF_INET6 IPv6
socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
参数二:类型
socket.SOCK_STREAM 流式socket , for TCP (默认)
socket.SOCK_DGRAM 数据报式socket , for UDP
socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
socket.SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
socket.SOCK_SEQPACKET 可靠的连续数据包服务
参数三:协议
0(默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字(将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。)
s.listen() 开始TCP监听(开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5这个值不能无限大,因为要在内核中维护连接队列)
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来(接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址客户端套接字函数s.connect() 主动初始化TCP服务器连接(连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误)s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常(同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061)
公共用途的套接字函数
s.recv() 接收TCP数据(接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。)
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据(与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。)
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址(返回套接字自己的地址。通常是一个元组(ipaddr,port))
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字
面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式(是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。)
s.settimeout() 设置阻塞套接字操作的超时时间(设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s))
s.gettimeout() 得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
tcp服务端
1 ss = socket() #创建服务器套接字 2 ss.bind() #把地址绑定到套接字 3 ss.listen() #监听链接 4 inf_loop: #服务器无限循环 5 cs = ss.accept() #接受客户端链接 6 comm_loop: #通讯循环 7 cs.recv()/cs.send() #对话(接收与发送) 8 cs.close() #关闭客户端套接字 9 ss.close() #关闭服务器套接字(可选)
tcp客户端
1 cs = socket() # 创建客户套接字 2 cs.connect() # 尝试连接服务器 3 comm_loop: # 通讯循环 4 cs.send()/cs.recv() # 对话(发送/接收) 5 cs.close() # 关闭客户套接字
socket通信流程与打电话流程类似,我们就以打电话为例来实现一个low版的套接字通信
import socket ip_port=(‘127.0.0.1‘,9000) #电话卡 BUFSIZE=1024 #收发消息的尺寸 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机 s.bind(ip_port) #手机插卡 s.listen(5) #手机待机 conn,addr=s.accept() #手机接电话 # print(conn) # print(addr) print(‘接到来自%s的电话‘ %addr[0]) msg=conn.recv(BUFSIZE) #听消息,听话 print(msg,type(msg)) conn.send(msg.upper()) #发消息,说话 conn.close() #挂电话 s.close() #手机关机
服务端
import socket ip_port=(‘127.0.0.1‘,9000) BUFSIZE=1024 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect_ex(ip_port) #拨电话 s.send(‘linhaifeng nb‘.encode(‘utf-8‘)) #发消息,说话(只能发送字节类型) feedback=s.recv(BUFSIZE) #收消息,听话 print(feedback.decode(‘utf-8‘)) s.close() #挂电话
客户端
加上链接循环与通信循环
import socket ip_port=(‘127.0.0.1‘,8081)#电话卡 BUFSIZE=1024 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机 s.bind(ip_port) #手机插卡 s.listen(5) #手机待机 while True: #新增接收链接循环,可以不停的接电话 conn,addr=s.accept() #手机接电话 # print(conn) # print(addr) print(‘接到来自%s的电话‘ %addr[0]) while True: #新增通信循环,可以不断的通信,收发消息 msg=conn.recv(BUFSIZE) #听消息,听话 # if len(msg) == 0:break #如果不加,那么正在链接的客户端突然断开,recv便不再阻塞,死循环发生 print(msg,type(msg)) conn.send(msg.upper()) #发消息,说话 conn.close() #挂电话 s.close() #手机关机
服务端
import socket ip_port=(‘127.0.0.1‘,8081) BUFSIZE=1024 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect_ex(ip_port) #拨电话 while True: #新增通信循环,客户端可以不断发收消息 msg=input(‘>>: ‘).strip() if len(msg) == 0:continue s.send(msg.encode(‘utf-8‘)) #发消息,说话(只能发送字节类型) feedback=s.recv(BUFSIZE) #收消息,听话 print(feedback.decode(‘utf-8‘)) s.close() #挂电话
客户端
什么是粘包
只有TCP有粘包现象,UDP永远不会粘包
1.TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
2.UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。
3.即面向消息的通信是有消息保护边界的。tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略
udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠
tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。
解决黏包的方法
为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据
struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes
import json,struct #假设通过客户端上传1T:1073741824000的文件a.txt #为避免粘包,必须自定制报头 header={‘file_size‘:1073741824000,‘file_name‘:‘/a/b/c/d/e/a.txt‘,‘md5‘:‘8f6fbf8347faa4924a76856701edb0f3‘} #1T数据,文件路径和md5值 #为了该报头能传送,需要序列化并且转为bytes head_bytes=bytes(json.dumps(header),encoding=‘utf-8‘) #序列化并转成bytes,用于传输 #为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节 head_len_bytes=struct.pack(‘i‘,len(head_bytes)) #这4个字节里只包含了一个数字,该数字是报头的长度 #客户端开始发送 conn.send(head_len_bytes) #先发报头的长度,4个bytes conn.send(head_bytes) #再发报头的字节格式 conn.sendall(文件内容) #然后发真实内容的字节格式 #服务端开始接收 head_len_bytes=s.recv(4) #先收报头4个bytes,得到报头长度的字节格式 x=struct.unpack(‘i‘,head_len_bytes)[0] #提取报头的长度 head_bytes=s.recv(x) #按照报头长度x,收取报头的bytes格式 header=json.loads(json.dumps(header)) #提取报头 #最后根据报头的内容提取真实的数据,比如 real_data_len=s.recv(header[‘file_size‘]) s.recv(real_data_len)
import struct import binascii import ctypes values1 = (1, ‘abc‘.encode(‘utf-8‘), 2.7) values2 = (‘defg‘.encode(‘utf-8‘),101) s1 = struct.Struct(‘I3sf‘) s2 = struct.Struct(‘4sI‘) print(s1.size,s2.size) prebuffer=ctypes.create_string_buffer(s1.size+s2.size) print(‘Before : ‘,binascii.hexlify(prebuffer)) # t=binascii.hexlify(‘asdfaf‘.encode(‘utf-8‘)) # print(t) s1.pack_into(prebuffer,0,*values1) s2.pack_into(prebuffer,s1.size,*values2) print(‘After pack‘,binascii.hexlify(prebuffer)) print(s1.unpack_from(prebuffer,0)) print(s2.unpack_from(prebuffer,s1.size)) s3=struct.Struct(‘ii‘) s3.pack_into(prebuffer,0,123,123) print(‘After pack‘,binascii.hexlify(prebuffer)) print(s3.unpack_from(prebuffer,0))
关于struct的用法
import socket,struct,json import subprocess phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加 phone.bind((‘127.0.0.1‘,8080)) phone.listen(5) while True: conn,addr=phone.accept() while True: cmd=conn.recv(1024) if not cmd:break print(‘cmd: %s‘ %cmd) res=subprocess.Popen(cmd.decode(‘utf-8‘), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) err=res.stderr.read() print(err) if err: back_msg=err else: back_msg=res.stdout.read() conn.send(struct.pack(‘i‘,len(back_msg))) #先发back_msg的长度 conn.sendall(back_msg) #在发真实的内容 conn.close()
服务端自定制报头
import socket,time,struct s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex((‘127.0.0.1‘,8080)) while True: msg=input(‘>>: ‘).strip() if len(msg) == 0:continue if msg == ‘quit‘:break s.send(msg.encode(‘utf-8‘)) l=s.recv(4) x=struct.unpack(‘i‘,l)[0] print(type(x),x) # print(struct.unpack(‘I‘,l)) r_s=0 data=b‘‘ while r_s < x: r_d=s.recv(1024) data+=r_d r_s+=len(r_d) # print(data.decode(‘utf-8‘)) print(data.decode(‘gbk‘)) #windows默认gbk编码
客户端自定制报头
我们可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)
发送时:
先发报头长度
再编码报头内容然后发送
最后发真实内容
接收时:
先手报头长度,用struct取出来
根据取出的长度收取报头内容,然后解码,反序列化
从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容
import socket,struct,json import subprocess phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加 phone.bind((‘127.0.0.1‘,8080)) phone.listen(5) while True: conn,addr=phone.accept() while True: cmd=conn.recv(1024) if not cmd:break print(‘cmd: %s‘ %cmd) res=subprocess.Popen(cmd.decode(‘utf-8‘), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) err=res.stderr.read() print(err) if err: back_msg=err else: back_msg=res.stdout.read() headers={‘data_size‘:len(back_msg)} head_json=json.dumps(headers) head_json_bytes=bytes(head_json,encoding=‘utf-8‘) conn.send(struct.pack(‘i‘,len(head_json_bytes))) #先发报头的长度 conn.send(head_json_bytes) #再发报头 conn.sendall(back_msg) #在发真实的内容 conn.close()
服务端复杂报头
from socket import * import struct,json ip_port=(‘127.0.0.1‘,8080) client=socket(AF_INET,SOCK_STREAM) client.connect(ip_port) while True: cmd=input(‘>>: ‘) if not cmd:continue client.send(bytes(cmd,encoding=‘utf-8‘)) head=client.recv(4) head_json_len=struct.unpack(‘i‘,head)[0] head_json=json.loads(client.recv(head_json_len).decode(‘utf-8‘)) data_len=head_json[‘data_size‘] recv_size=0 recv_data=b‘‘ while recv_size < data_len: recv_data+=client.recv(1024) recv_size+=len(recv_data) print(recv_data.decode(‘utf-8‘)) #print(recv_data.decode(‘gbk‘)) #windows默认gbk编码
客户端