第1章 异常处理
1.1 什么是异常?
1.1.1 描述
#1 什么是异常?
# 异常是错误发生的信号,一旦程序出错,就会产生一个异常,应用程序未处理该异常,
# 异常便会抛出,程序随之终止
异常就是程序运行时发生错误的信号(在程序出现错误时,则会产生一个异常,若程序没有处理它,则会抛出该异常,程序的运行也随之终止),在python中,错误触发的异常如下
1.1.2 错误分类
而错误分成两种
1.语法错误(这种错误,根本过不了python解释器的语法检测,必须在程序执行前就改正)#语法错误示范一if#语法错误示范二def test: pass#语法错误示范三class Foo pass#语法错误示范四print(haha
2.逻辑错误#TypeError:int类型不可迭代for i in 3: pass#ValueErrornum=input(">>: ") #输入helloint(num) #NameErroraaa #IndexErrorl=['egon','aa']l[3] #KeyErrordic={ 'name':'egon'}dic['age'] #AttributeErrorclass Foo:passFoo.x #ZeroDivisionError:无法完成计算res1=1/0res2=1+'str'
1.2 异常的种类
在python中不同的异常可以用不同的类型(python中统一了类与类型,类型即类)去标识,一个异常标识一种错误
1.2.1 常用异常
#######################常用异常####################AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性xIOError 输入/输出异常;基本上是无法打开文件ImportError 无法引入模块或包;基本上是路径问题或名称错误IndentationError 语法错误(的子类) ;代码没有正确对齐IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]KeyError 试图访问字典里不存在的键KeyboardInterrupt Ctrl+C被按下NameError 使用一个还未被赋予对象的变量SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了)TypeError 传入对象类型与要求的不符合UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,导致你以为正在访问它ValueError 传入一个调用者不期望的值,即使值的类型是正确的
1.2.2 更多异常
ArithmeticErrorAssertionErrorAttributeErrorBaseExceptionBufferErrorBytesWarningDeprecationWarningEnvironmentErrorEOFErrorExceptionFloatingPointErrorFutureWarningGeneratorExitImportErrorImportWarningIndentationErrorIndexErrorIOErrorKeyboardInterruptKeyErrorLookupErrorMemoryErrorNameErrorNotImplementedErrorOSErrorOverflowErrorPendingDeprecationWarningReferenceErrorRuntimeErrorRuntimeWarningStandardErrorStopIterationSyntaxErrorSyntaxWarningSystemErrorSystemExitTabErrorTypeErrorUnboundLocalErrorUnicodeDecodeErrorUnicodeEncodeErrorUnicodeErrorUnicodeTranslateErrorUnicodeWarningUserWarningValueErrorWarningZeroDivisionError
1.2.3 常见异常类型
#2、常见异常类型#I:语法错误应该在程序运行前修正# if 1 >2# print('xxxxx') #II:逻辑错误# x # l=[]# l[10000] #IndexError # class Foo:# pass# Foo.x #AttributeError: # k={'x':1}# k['y'] #KeyError # 1/0 #ZeroDivisionError # for i in 3: #TypeError:# pass # age=input('>>: ') #ValueError# age=int(age)
1.3 异常处理
1.3.1 异常处理描述
为了保证程序的健壮性与容错性,即在遇到错误时程序不会崩溃,我们需要对异常进行处理,
如果错误发生的条件是可预知的,我们需要用if进行处理:在错误发生之前进行预防
AGE=10while True: age=input('>>: ').strip() if age.isdigit(): #只有在age为字符串形式的整数时,下列代码才不会出错,该条件是可预知的 age=int(age) if age == AGE: print('you got it') break
如果错误发生的条件是不可预知的,则需要用到try...except:在错误发生之后进行处理
#基本语法为try: 被检测的代码块except 异常类型: try中一旦检测到异常,就执行这个位置的逻辑#举例try: f=open('a.txt') g=(line.strip() for line in f) print(next(g)) print(next(g)) print(next(g)) print(next(g)) print(next(g))except StopIteration: f.close()
1.3.2 异常分类使用
#1 异常类只能用来处理指定的异常情况,如果非指定异常则无法处理。s1 = 'hello'try: int(s1)except IndexError as e: # 未捕获到异常,程序直接报错 print e #2 多分支s1 = 'hello'try: int(s1)except IndexError as e: print(e)except KeyError as e: print(e)except ValueError as e: print(e) #3 万能异常Exceptions1 = 'hello'try: int(s1)except Exception as e: print(e) #4 多分支异常与万能异常#4.1 如果你想要的效果是,无论出现什么异常,我们统一丢弃,或者使用同一段代码逻辑去处理他们,那么骚年,大胆的去做吧,只有一个Exception就足够了。#4.2 如果你想要的效果是,对于不同的异常我们需要定制不同的处理逻辑,那就需要用到多分支了。 #5 也可以在多分支后来一个Exceptions1 = 'hello'try: int(s1)except IndexError as e: print(e)except KeyError as e: print(e)except ValueError as e: print(e)except Exception as e: print(e) #6 异常的其他机构s1 = 'hello'try: int(s1)except IndexError as e: print(e)except KeyError as e: print(e)except ValueError as e: print(e)#except Exception as e:# print(e)else: print('try内代码块没有异常则执行我')finally: print('无论异常与否,都会执行该模块,通常是进行清理工作') #7 主动触发异常try: raise TypeError('类型错误')except Exception as e: print(e) #8 自定义异常class EgonException(BaseException): def __init__(self,msg): self.msg=msg def __str__(self): return self.msg try: raise EgonException('类型错误')except EgonException as e: print(e) #9 断言:assert 条件assert 1 == 1 assert 1 == 2 #10 总结try..except 1:把错误处理和真正的工作分开来2:代码更易组织,更清晰,复杂的工作任务更容易实现;3:毫无疑问,更安全了,不至于由于一些小的疏忽而使程序意外崩溃了;
1.3.3 如何处理异常实例
#3、如何处理异常# print('====>start<=====')## try:# l=[]# print(l[1111])# print('====>1')# print('====>2')# print('====>3')# except IndexError:# pass## print('====>end<=======') # print('====>start<=====')# try:# l=[]# print(l[1111])# print('====>1')# print('====>2')# print('====>3')# except IndexError as e:# print('===>',e)## print('====>end<=======') # 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<=======') # 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<=======') # 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<=======') '''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) age=input('>>: ')if age.isdigit(): age=int(age) if age > 50: print('====>too big')
1.4 什么时候用异常处理
有的同学会这么想,学完了异常处理后,好强大,我要为我的每一段程序都加上try...except,干毛线去思考它会不会有逻辑错误啊,这样就很好啊,多省脑细胞===》2B青年欢乐多
首先try...except是你附加给你的程序的一种异常处理的逻辑,与你的主要的工作是没有关系的,这种东西加的多了,会导致你的代码可读性变差
然后异常处理本就不是你2b逻辑的擦屁股纸,只有在错误发生的条件无法预知的情况下,才应该加上try...except,其他的逻辑错误应该尽量修正。
第2章 网络编程
2.1 楔子
你现在已经学会了写python代码,假如你写了两个python文件a.py和b.py,分别去运行,你就会发现,这两个python的文件分别运行的很好。但是如果这两个程序之间想要传递一个数据,你要怎么做呢?
这个问题以你现在的知识就可以解决了,我们可以创建一个文件,把a.py想要传递的内容写到文件中,然后b.py从这个文件中读取内容就可以了。
但是当你的a.py和b.py分别在不同电脑上的时候,你要怎么办呢?
类似的机制有计算机网盘,qq等等。我们可以在我们的电脑上和别人聊天,可以在自己的电脑上向网盘中上传、下载内容。这些都是两个程序在通信。
2.2 软件开发的架构
我们了解的涉及到两个程序之间通讯的应用大致可以分为两种:
第一种是应用类:qq、微信、网盘、优酷这一类是属于需要安装的桌面应用
第二种是web类:比如百度、知乎、博客园等使用浏览器访问就可以直接使用的应用
这些应用的本质其实都是两个程序之间的通讯。而这两个分类又对应了两个软件开发的架构~
2.2.1 C/S架构
C/S即:Client与Server ,中文意思:客户端与服务器端架构,这种架构也是从用户层面(也可以是物理层面)来划分的。
这里的客户端一般泛指客户端应用程序EXE,程序需要先安装后,才能运行在用户的电脑上,对用户的电脑操作系统环境依赖较大。
2.2.2 B/S架构
B/S即:Browser与Server,中文意思:浏览器端与服务器端架构,这种架构是从用户层面来划分的。
Browser浏览器,其实也是一种Client客户端,只是这个客户端不需要大家去安装什么应用程序,只需在浏览器上通过HTTP请求服务器端相关的资源(网页资源),客户端Browser浏览器就能进行增删改查。
2.3 网络基础
2.3.1 详细请见外链地址
或者
2.3.2 一个程序如何在网络上找到另一个程序?
首先,程序必须要启动,其次,必须有这台机器的地址,我们都知道我们人的地址大概就是国家\省\市\区\街道\楼\门牌号这样字。那么每一台联网的机器在网络上也有自己的地址,它的地址是怎么表示的呢?
就是使用一串数字来表示的,例如:100.4.5.6
#########################什么是ip地址?############################IP地址是指互联网协议地址(英语:Internet Protocol Address,又译为网际协议地址),是IP Address的缩写。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也就是4个字节)。IP地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之间的十进制整数。例:点分十进IP地址(100.4.5.6),实际上是32位二进制数(01100100.00000100.00000101.00000110)。
#####################什么是端口#########################"端口"是英文port的意译,可以认为是设备与外界通讯交流的出口。因此ip地址精确到具体的一台电脑,而端口精确到具体的程序。
2.4 osi七层模型
2.4.1 引子
须知一个完整的计算机系统是由硬件、操作系统、应用软件三者组成,具备了这三个条件,一台计算机系统就可以自己跟自己玩了(打个单机游戏,玩个扫雷啥的)
如果你要跟别人一起玩,那你就需要上网了,什么是互联网?
互联网的核心就是由一堆协议组成,协议就是标准,比如全世界人通信的标准是英语,如果把计算机比作人,互联网协议就是计算机界的英语。所有的计算机都学会了互联网协议,那所有的计算机都就可以按照统一的标准去收发信息从而完成通信了。
2.4.2 osi七层模型
人们按照分工不同把互联网协议从逻辑上划分了层级:
2.4.3 为何学习socket一定要先学习互联网协议
为何学习socket一定要先学习互联网协议:
1.首先:本节课程的目标就是教会你如何基于socket编程,来开发一款自己的C/S架构软件
2.其次:C/S架构的软件(软件属于应用层)是基于网络进行通信的
3.然后:网络的核心即一堆协议,协议即标准,你想开发一款基于网络通信的软件,就必须遵循这些标准。
4.最后:就让我们从这些标准开始研究,开启我们的socket编程之旅
2.5 socket概念
2.5.1 socket层
2.5.2 理解socket
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
###################站在你的角度上看socket########################其实站在你的角度上看,socket就是一个模块。我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。也有人将socket说成ip+port,因为ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序。所以我们只要确立了ip和port就能找到一个应用程序,并且使用socket模块来与之通信。
2.6 套接字(socket)的发展史
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。
2.6.1 基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
2.6.2 基于网络类型的套接字家族
套接字家族的名字:AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)
2.7 套接字工作流程
一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。 生活中的场景就解释了这工作原理。
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
2.7.1 socket()模块函数用法
import socketsocket.socket(socket_family,socket_type,protocal=0)socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。 获取tcp/ip套接字tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 获取udp/ip套接字udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 由于 socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。使用 'from socket import *',我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能 大幅减短我们的代码。例如tcpSock = socket(AF_INET, SOCK_STREAM)
2.7.2 服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
2.7.3 客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
2.7.4 公共用途的套接字函数
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() 关闭套接字
2.7.5 面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
2.7.6 面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
2.7.7 socket实验推演流程
1:用打电话的流程快速描述socket通信2:服务端和客户端加上基于一次链接的循环通信3:客户端发送空,卡主,证明是从哪个位置卡的服务端:from socket import *phone=socket(AF_INET,SOCK_STREAM)phone.bind(('127.0.0.1',8081))phone.listen(5) conn,addr=phone.accept()while True: data=conn.recv(1024) print('server===>') print(data) conn.send(data.upper())conn.close()phone.close()客户端:from socket import * phone=socket(AF_INET,SOCK_STREAM)phone.connect(('127.0.0.1',8081)) while True: msg=input('>>: ').strip() phone.send(msg.encode('utf-8')) print('client====>') data=phone.recv(1024) print(data) 说明卡的原因:缓冲区为空recv就卡住,引出原理图 4.演示客户端断开链接,服务端的情况,提供解决方法 5.演示服务端不能重复接受链接,而服务器都是正常运行不断来接受客户链接的 6:简单演示udp服务端from socket import *phone=socket(AF_INET,SOCK_DGRAM)phone.bind(('127.0.0.1',8082))while True: msg,addr=phone.recvfrom(1024) phone.sendto(msg.upper(),addr)客户端from socket import *phone=socket(AF_INET,SOCK_DGRAM)while True: msg=input('>>: ') phone.sendto(msg.encode('utf-8'),('127.0.0.1',8082)) msg,addr=phone.recvfrom(1024) print(msg) udp客户端可以并发演示udp客户端可以输入为空演示,说出recvfrom与recv的区别,暂且不提tcp流和udp报的概念,留到粘包去说
2.8 基于TCP的套接字
tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
2.8.1 实例一server端与client端
#################server端#################import socketsk = socket.socket()sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字sk.listen() #监听链接conn,addr = sk.accept() #接受客户端链接ret = conn.recv(1024) #接收客户端信息print(ret) #打印客户端信息conn.send(b'hi') #向客户端发送信息conn.close() #关闭客户端套接字sk.close() #关闭服务器套接字(可选)
#####################client端############################import socketsk = socket.socket() # 创建客户套接字sk.connect(('127.0.0.1',8898)) # 尝试连接服务器sk.send(b'hello!')ret = sk.recv(1024) # 对话(发送/接收)print(ret)sk.close() # 关闭客户套接字
2.8.2 问题:有的同学在重启服务端时可能会遇到
##################解决方法####################加入一条socket配置,重用ip和端口import socketfrom socket import SOL_SOCKET,SO_REUSEADDRsk = socket.socket()sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字sk.listen() #监听链接conn,addr = sk.accept() #接受客户端链接ret = conn.recv(1024) #接收客户端信息print(ret) #打印客户端信息conn.send(b'hi') #向客户端发送信息conn.close() #关闭客户端套接字sk.close() #关闭服务器套接字(可选)
2.8.3 实例二server端与client端
##################server端######################import socket #1、买手机phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp协议 #2、绑定手机# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)phone.bind(('127.0.0.1',8081)) #0-65535 #3、开机phone.listen(5) #4、等待电话连接print('starting...')conn,client_addr=phone.accept() #(conn,client_addr)print(conn,client_addr) #5、收\发消息data=conn.recv(1024) #1024bytes? conn.send(data.upper()) #6、挂电话连接conn.close() #7、关机phone.close()
################client端####################import socket #1、买手机phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp协议 #2、拨电话phone.connect(('127.0.0.1',8081)) #0-65535 #3、发收消息phone.send('hello'.encode('utf-8')) data=phone.recv(1024)print(data) #4、挂电话phone.close()
2.8.4 实例二加上链接循环与通信循环进阶版
模拟一个服务端三个客户端连接通信
#######################服务端进阶版###########################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-65535phone.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()
#############client端一进阶版#####################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()
#############client端二进阶版#####################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()
#############client端三进阶版#####################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()
2.8.5 实现ssh远程执行命令
#################服务端##################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()
#####################client端#################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()
2.9 基于UDP协议的socket
udp是无链接的,先启动哪一端都不会报错
2.9.1 简单使用
###################server端#####################import socketudp_sk = socket.socket(type=socket.SOCK_DGRAM) #创建一个服务器的套接字udp_sk.bind(('127.0.0.1',9000)) #绑定服务器套接字msg,addr = udp_sk.recvfrom(1024)print(msg)udp_sk.sendto(b'hi',addr) # 对话(接收与发送)udp_sk.close() # 关闭服务器套接字
#######################client端#######################import socketip_port=('127.0.0.1',9000)udp_sk=socket.socket(type=socket.SOCK_DGRAM)udp_sk.sendto(b'hello',ip_port)back_msg,addr=udp_sk.recvfrom(1024)print(back_msg.decode('utf-8'),addr)
2.9.2 qq聊天
#################server端##############################_*_coding:utf-8_*_import socketip_port=('127.0.0.1',8081)udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)udp_server_sock.bind(ip_port) while True: qq_msg,addr=udp_server_sock.recvfrom(1024) print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8'))) back_msg=input('回复消息: ').strip() udp_server_sock.sendto(back_msg.encode('utf-8'),addr)
##################################client端######################_*_coding:utf-8_*_import socketBUFSIZE=1024udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) qq_name_dic={ '金老板':('127.0.0.1',8081), '哪吒':('127.0.0.1',8081), 'egg':('127.0.0.1',8081), 'yuan':('127.0.0.1',8081),} while True: qq_name=input('请选择聊天对象: ').strip() while True: msg=input('请输入消息,回车发送,输入q结束和他的聊天: ').strip() if msg == 'q':break if not msg or not qq_name or qq_name not in qq_name_dic:continue udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name]) back_msg,addr=udp_client_socket.recvfrom(BUFSIZE) print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8'))) udp_client_socket.close()
2.9.3 时间服务器
#################server端############################## _*_coding:utf-8_*_from socket import *from time import strftime ip_port = ('127.0.0.1', 9000)bufsize = 1024 tcp_server = socket(AF_INET, SOCK_DGRAM)tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)tcp_server.bind(ip_port) while True: msg, addr = tcp_server.recvfrom(bufsize) print('===>', msg) if not msg: time_fmt = '%Y-%m-%d %X' else: time_fmt = msg.decode('utf-8') back_msg = strftime(time_fmt) tcp_server.sendto(back_msg.encode('utf-8'), addr) tcp_server.close()
#################client端##############################_*_coding:utf-8_*_from socket import *ip_port=('127.0.0.1',9000)bufsize=1024 tcp_client=socket(AF_INET,SOCK_DGRAM) while True: msg=input('请输入时间格式(例%Y %m %d)>>: ').strip() tcp_client.sendto(msg.encode('utf-8'),ip_port) data=tcp_client.recv(bufsize)
2.10 socket参数的详解
socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)
创建socket对象的参数说明:
family | 地址系列应为AF_INET(默认值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。 (AF_UNIX 域实际上是使用本地 socket 文件来通信) |
type | 套接字类型应为SOCK_STREAM(默认值),SOCK_DGRAM,SOCK_RAW或其他SOCK_常量之一。 SOCK_STREAM 是基于TCP的,有保障的(即能保证数据正确传送到对方)面向连接的SOCKET,多用于资料传送。 SOCK_DGRAM 是基于UDP的,无保障的面向消息的socket,多用于在网络上发广播信息。 |
proto | 协议号通常为零,可以省略,或者在地址族为AF_CAN的情况下,协议应为CAN_RAW或CAN_BCM之一。 |
fileno | 如果指定了fileno,则其他参数将被忽略,导致带有指定文件描述符的套接字返回。 与socket.fromfd()不同,fileno将返回相同的套接字,而不是重复的。 这可能有助于使用socket.close()关闭一个独立的插座。 |
2.11 粘包
2.11.1 黏包现象
让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)
###########################注意#########################res=subprocess.Popen(cmd.decode('utf-8'),shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE) 的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码且只能从管道里读一次结果
同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包。
2.11.2 基于tcp协议实现的黏包
########################tcp - server####################_*_coding:utf-8_*_from socket import *import subprocess ip_port=('127.0.0.1',8888)BUFSIZE=1024 tcp_socket_server=socket(AF_INET,SOCK_STREAM)tcp_socket_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)tcp_socket_server.bind(ip_port)tcp_socket_server.listen(5) while True: conn,addr=tcp_socket_server.accept() print('客户端',addr) while True: cmd=conn.recv(BUFSIZE) if len(cmd) == 0:break res=subprocess.Popen(cmd.decode('utf-8'),shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) stderr=res.stderr.read() stdout=res.stdout.read() conn.send(stderr) conn.send(stdout)
######################tcp - client########################_*_coding:utf-8_*_import socketBUFSIZE=1024ip_port=('127.0.0.1',8888) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)res=s.connect_ex(ip_port) while True: msg=input('>>: ').strip() if len(msg) == 0:continue if msg == 'quit':break s.send(msg.encode('utf-8')) act_res=s.recv(BUFSIZE) print(act_res.decode('utf-8'),end='')
2.11.3 基于tcp协议黏包现象实例二
#################server端###################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()
######################client端##############################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()
2.11.4 基于udp协议实现的黏包
#################udp - server#######################_*_coding:utf-8_*_from socket import *import subprocess ip_port=('127.0.0.1',9000)bufsize=1024 udp_server=socket(AF_INET,SOCK_DGRAM)udp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)udp_server.bind(ip_port) while True: #收消息 cmd,addr=udp_server.recvfrom(bufsize) print('用户命令----->',cmd) #逻辑处理 res=subprocess.Popen(cmd.decode('utf-8'),shell=True,stderr=subprocess.PIPE,stdin=subprocess.PIPE,stdout=subprocess.PIPE) stderr=res.stderr.read() stdout=res.stdout.read() #发消息 udp_server.sendto(stderr,addr) udp_server.sendto(stdout,addr)udp_server.close()
######################udp - client#############################from socket import *ip_port=('127.0.0.1',9000)bufsize=1024 udp_client=socket(AF_INET,SOCK_DGRAM) while True: msg=input('>>: ').strip() udp_client.sendto(msg.encode('utf-8'),ip_port) err,addr=udp_client.recvfrom(bufsize) out,addr=udp_client.recvfrom(bufsize) if err: print('error : %s'%err.decode('utf-8'),end='') if out: print(out.decode('utf-8'), end='')
注意:只有TCP有粘包现象,UDP永远不会粘包
2.12 黏包成因
2.12.1 TCP协议中的数据传递
##################tcp协议的拆包机制###################当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。MTU是Maximum Transmission Unit的缩写。意思是网络上传送的最大数据包。MTU的单位是字节。 大部分网络设备的MTU都是1500。如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率,降低网络速度。
#########################面向流的通信特点和Nagle算法##############################TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。对于空消息:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp协议会帮你封装上消息头发送过去。可靠黏包的tcp协议:tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。
##################基于tcp协议特点的黏包现象成因#############
##############socket数据传输过程中的用户态与内核态说明################例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束 此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。
2.12.2 UDP不会发生黏包
UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。对于空消息:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp协议会帮你封装上消息头发送过去。不可靠不黏包的udp协议:udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y;x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠。
补充说明:
#####################udp和tcp一次发送数据长度的限制#####################用UDP协议发送时,用sendto函数最大能发送数据的长度为:65535- IP头(20) – UDP头(8)=65507字节。用sendto函数发送数据时,如果发送数据长度大于该值,则函数会返回错误。(丢弃这个包,不进行发送)用TCP协议发送时,由于TCP是数据流协议,因此不存在包大小的限制(暂不考虑缓冲区的大小),这是指在用send函数时,数据长度参数不受限制。而实际上,所指定的这段数据并不一定会一次性发送出去,如果这段数据比较长,会被分段发送,如果比较短,可能会等待和下一次数据一起发送。
2.12.3 会发生黏包的两种情况
情况一 发送方的缓存机制
发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
#############################服务端######################_*_coding:utf-8_*_from socket import *ip_port=('127.0.0.1',8080) tcp_socket_server=socket(AF_INET,SOCK_STREAM)tcp_socket_server.bind(ip_port)tcp_socket_server.listen(5) conn,addr=tcp_socket_server.accept() data1=conn.recv(10)data2=conn.recv(10) print('----->',data1.decode('utf-8'))print('----->',data2.decode('utf-8')) conn.close()
####################客户端#####################_*_coding:utf-8_*_import socketBUFSIZE=1024ip_port=('127.0.0.1',8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)res=s.connect_ex(ip_port) s.send('hello'.encode('utf-8'))s.send('feng'.encode('utf-8'))
情况二 接收方的缓存机制
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
#######################服务端##########################_*_coding:utf-8_*_from socket import *ip_port=('127.0.0.1',8080) tcp_socket_server=socket(AF_INET,SOCK_STREAM)tcp_socket_server.bind(ip_port)tcp_socket_server.listen(5) conn,addr=tcp_socket_server.accept() data1=conn.recv(2) #一次没有收完整data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的 print('----->',data1.decode('utf-8'))print('----->',data2.decode('utf-8')) conn.close()
############################客户端##########################_*_coding:utf-8_*_import socketBUFSIZE=1024ip_port=('127.0.0.1',8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)res=s.connect_ex(ip_port) s.send('hello feng'.encode('utf-8'))
2.12.4 总结
黏包现象只发生在tcp协议中:
1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。
2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
2.13 黏包的解决方案一
问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。
###################服务端#####################_*_coding:utf-8_*_import socket,subprocessip_port=('127.0.0.1',8080)s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(ip_port)s.listen(5) while True: conn,addr=s.accept() print('客户端',addr) while True: msg=conn.recv(1024) if not msg:break res=subprocess.Popen(msg.decode('utf-8'),shell=True,\ stdin=subprocess.PIPE,\ stderr=subprocess.PIPE,\ stdout=subprocess.PIPE) err=res.stderr.read() if err: ret=err else: ret=res.stdout.read() data_length=len(ret) conn.send(str(data_length).encode('utf-8')) data=conn.recv(1024).decode('utf-8') if data == 'recv_ready': conn.sendall(ret) conn.close()
##################客户端###################_*_coding:utf-8_*_import socket,times=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')) length=int(s.recv(1024).decode('utf-8')) s.send('recv_ready'.encode('utf-8')) send_size=0 recv_size=0 data=b'' while recv_size < length: data+=s.recv(1024) recv_size+=len(data) print(data.decode('utf-8'))
存在的问题:
程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗。
2.14 解决方案进阶
刚刚的方法,问题在于我们我们在发送
我们可以借助一个模块,这个模块可以把要发送的数据长度转换成固定长度的字节。这样客户端每次接收消息之前只要先接受这个固定长度字节的内容看一看接下来要接收的信息大小,那么最终接受的数据只要达到这个值就停止,就能刚好不多不少的接收完整的数据了。
2.14.1 struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes
>>> struct.pack('i',1111111111111) struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
import json,struct#假设通过客户端上传1T:1073741824000的文件a.txt #为避免粘包,必须自定制报头header={ 'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T数据,文件路径和md5值 #为了该报头能传送,需要序列化并且转为byteshead_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个bytesconn.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)
####################关于struct的详细用法######################_*_coding:utf-8_*_#http://www.cnblogs.com/coser/archive/2011/12/17/2291160.html__author__ = 'Linhaifeng'import structimport binasciiimport 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))
2.14.2 使用struct解决黏包
借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。
发送时 | 接收时 |
先发送struct转换好的数据长度4字节 | 先接受4个字节使用struct转换成数字来获取要接收的数据长度 |
再发送数据 | 再按照长度接收数据 |
#################服务端(自定制报头)##################import socket,struct,jsonimport subprocessphone=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()
#####################客户端(自定制报头)#######################_*_coding:utf-8_*_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编码
2.14.3 使用struct与json序列化解决黏包
我们还可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)
发送时 | 接收时 |
先发报头长度 | 先收报头长度,用struct取出来 |
再编码报头内容然后发送 | 根据取出的长度收取报头内容,然后解码,反序列化 |
最后发真实内容 | 从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容 |
#############服务端:定制稍微复杂一点的报头#################import socket,struct,jsonimport subprocessphone=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编码
2.15 课堂讲解实例解决粘包问题版本1
2.15.1 struct模块的使用
####################struct模块的使用#################import struct headers=struct.pack('i',132333)# print(headers,len(headers)) res=struct.unpack('i',headers)print(res[0])
2.15.2 服务端
###################服务端#############from socket import *import subprocessimport 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() #1、制作固定长度的报头 total_size = len(stdout) + len(stderr) headers=struct.pack('i',total_size) #2、先发送命令长度 conn.send(headers) #3、发送命令的执行结果 conn.send(stdout) conn.send(stderr) except ConnectionResetError: break conn.close() server.close()
2.15.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')) #1、先接收命令长度 headers=client.recv(4) total_size = struct.unpack('i', headers)[0] #2、再收命令的结果 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()
2.16 课堂讲解实例解决粘包问题版本2
2.16.1 struct模块的使用
##################### 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' : 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111213123123123123123123111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111} 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))
2.16.2 服务端
##########################服务端######################from socket import *import subprocessimport structimport 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() #1、制作报头 headers = { 'filepath': 'a.txt', 'md5': '123sxd123x123', 'total_size': len(stdout) + len(stderr) } headers_json = json.dumps(headers) headers_bytes = headers_json.encode('utf-8') #2、先发报头的长度 conn.send(struct.pack('i',len(headers_bytes))) #3、发送报头 conn.send(headers_bytes) #4、发送命令的执行结果 conn.send(stdout) conn.send(stderr) except ConnectionResetError: break conn.close() server.close()
2.16.3 客户端
######################客户端############################from socket import *import structimport 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')) #1、先接收报头的长度 headers_size=struct.unpack('i',client.recv(4))[0] #2、再收报头 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'] #3、再收命令的结果 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()
2.17 FTP作业:上传下载文件
2.17.1 原博客实例
####################服务端#################import socketimport structimport jsonimport subprocessimport os class MYTCPServer: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding='utf-8' request_queue_size = 5 server_dir='file_upload' def __init__(self, server_address, bind_and_activate=True): """Constructor. May be extended, do not override.""" self.server_address=server_address self.socket = socket.socket(self.address_family, self.socket_type) if bind_and_activate: try: self.server_bind() self.server_activate() except: self.server_close() raise def server_bind(self): """Called by constructor to bind the socket. """ if self.allow_reuse_address: self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind(self.server_address) self.server_address = self.socket.getsockname() def server_activate(self): """Called by constructor to activate the server. """ self.socket.listen(self.request_queue_size) def server_close(self): """Called to clean-up the server. """ self.socket.close() def get_request(self): """Get the request and client address from the socket. """ return self.socket.accept() def close_request(self, request): """Called to clean up an individual request.""" request.close() def run(self): while True: self.conn,self.client_addr=self.get_request() print('from client ',self.client_addr) while True: try: head_struct = self.conn.recv(4) if not head_struct:break head_len = struct.unpack('i', head_struct)[0] head_json = self.conn.recv(head_len).decode(self.coding) head_dic = json.loads(head_json) print(head_dic) #head_dic={'cmd':'put','filename':'a.txt','filesize':123123} cmd=head_dic['cmd'] if hasattr(self,cmd): func=getattr(self,cmd) func(head_dic) except Exception: break def put(self,args): file_path=os.path.normpath(os.path.join( self.server_dir, args['filename'] )) filesize=args['filesize'] recv_size=0 print('----->',file_path) with open(file_path,'wb') as f: while recv_size < filesize: recv_data=self.conn.recv(self.max_packet_size) f.write(recv_data) recv_size+=len(recv_data) print('recvsize:%s filesize:%s' %(recv_size,filesize)) tcpserver1=MYTCPServer(('127.0.0.1',8080)) tcpserver1.run() #下列代码与本题无关class MYUDPServer: """UDP server class.""" address_family = socket.AF_INET socket_type = socket.SOCK_DGRAM allow_reuse_address = False max_packet_size = 8192 coding='utf-8' def get_request(self): data, client_addr = self.socket.recvfrom(self.max_packet_size) return (data, self.socket), client_addr def server_activate(self): # No need to call listen() for UDP. pass def shutdown_request(self, request): # No need to shutdown anything. self.close_request(request) def close_request(self, request): # No need to close anything. pass
#####################客户端##################import socketimport structimport jsonimport os class MYTCPClient: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding='utf-8' request_queue_size = 5 def __init__(self, server_address, connect=True): self.server_address=server_address self.socket = socket.socket(self.address_family, self.socket_type) if connect: try: self.client_connect() except: self.client_close() raise def client_connect(self): self.socket.connect(self.server_address) def client_close(self): self.socket.close() def run(self): while True: inp=input(">>: ").strip() if not inp:continue l=inp.split() cmd=l[0] if hasattr(self,cmd): func=getattr(self,cmd) func(l) def put(self,args): cmd=args[0] filename=args[1] if not os.path.isfile(filename): print('file:%s is not exists' %filename) return else: filesize=os.path.getsize(filename) head_dic={ 'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize} print(head_dic) head_json=json.dumps(head_dic) head_json_bytes=bytes(head_json,encoding=self.coding) head_struct=struct.pack('i',len(head_json_bytes)) self.socket.send(head_struct) self.socket.send(head_json_bytes) send_size=0 with open(filename,'rb') as f: for line in f: self.socket.send(line) send_size+=len(line) print(send_size) else: print('upload successful') client=MYTCPClient(('127.0.0.1',8080)) client.run()
2.17.2 课堂讲解实例
#######################服务端#######################import socketimport osimport jsonimport 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): #1、制作报头 headers = { 'filename': filename, 'md5': '123sxd123x123', 'filesize': os.path.getsize(filepath) } headers_json = json.dumps(headers) headers_bytes = headers_json.encode('utf-8') #2、先发报头的长度 self.conn.send(struct.pack('i',len(headers_bytes))) #3、发送报头 self.conn.send(headers_bytes) #4、发送真实的数据 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()
#####################客户端#######################import socketimport structimport jsonimport 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')) # 1、先接收报头的长度 headers_size = struct.unpack('i', self.client.recv(4))[0] # 2、再收报头 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) # 3、再收真实的数据 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()
2.18 认证客户端的链接合法性
如果你想在分布式系统中实现一个简单的客户端链接认证功能,又不像SSL那么复杂,那么利用hmac+加盐的方式来实现
2.18.1 服务端
#######################服务端######################_*_coding:utf-8_*___author__ = 'Linhaifeng'from socket import *import hmac,os secret_key=b'linhaifeng bang bang bang'def conn_auth(conn): ''' 认证客户端链接 :param conn: :return: ''' print('开始验证新链接的合法性') msg=os.urandom(32) conn.sendall(msg) h=hmac.new(secret_key,msg) digest=h.digest() respone=conn.recv(len(digest)) return hmac.compare_digest(respone,digest) def data_handler(conn,bufsize=1024): if not conn_auth(conn): print('该链接不合法,关闭') conn.close() return print('链接合法,开始通信') while True: data=conn.recv(bufsize) if not data:break conn.sendall(data.upper()) def server_handler(ip_port,bufsize,backlog=5): ''' 只处理链接 :param ip_port: :return: ''' tcp_socket_server=socket(AF_INET,SOCK_STREAM) tcp_socket_server.bind(ip_port) tcp_socket_server.listen(backlog) while True: conn,addr=tcp_socket_server.accept() print('新连接[%s:%s]' %(addr[0],addr[1])) data_handler(conn,bufsize) if __name__ == '__main__': ip_port=('127.0.0.1',9999) bufsize=1024 server_handler(ip_port,bufsize)
2.18.2 客户端(合法)
#########################客户端(合法)######################_*_coding:utf-8_*___author__ = 'Linhaifeng'from socket import *import hmac,os secret_key=b'linhaifeng bang bang bang'def conn_auth(conn): ''' 验证客户端到服务器的链接 :param conn: :return: ''' msg=conn.recv(32) h=hmac.new(secret_key,msg) digest=h.digest() conn.sendall(digest) def client_handler(ip_port,bufsize=1024): tcp_socket_client=socket(AF_INET,SOCK_STREAM) tcp_socket_client.connect(ip_port) conn_auth(tcp_socket_client) while True: data=input('>>: ').strip() if not data:continue if data == 'quit':break tcp_socket_client.sendall(data.encode('utf-8')) respone=tcp_socket_client.recv(bufsize) print(respone.decode('utf-8')) tcp_socket_client.close() if __name__ == '__main__': ip_port=('127.0.0.1',9999) bufsize=1024 client_handler(ip_port,bufsize)
2.18.3 客户端(非法:不知道加密方式)
##########################客户端(非法:不知道加密方式)###################_*_coding:utf-8_*___author__ = 'Linhaifeng'from socket import * def client_handler(ip_port,bufsize=1024): tcp_socket_client=socket(AF_INET,SOCK_STREAM) tcp_socket_client.connect(ip_port) while True: data=input('>>: ').strip() if not data:continue if data == 'quit':break tcp_socket_client.sendall(data.encode('utf-8')) respone=tcp_socket_client.recv(bufsize) print(respone.decode('utf-8')) tcp_socket_client.close() if __name__ == '__main__': ip_port=('127.0.0.1',9999) bufsize=1024 client_handler(ip_port,bufsize)
2.18.4 客户端(非法:不知道secret_key)
#########################客户端(非法:不知道secret_key)####################_*_coding:utf-8_*___author__ = 'Linhaifeng'from socket import *import hmac,os secret_key=b'linhaifeng bang bang bang1111'def conn_auth(conn): ''' 验证客户端到服务器的链接 :param conn: :return: ''' msg=conn.recv(32) h=hmac.new(secret_key,msg) digest=h.digest() conn.sendall(digest) def client_handler(ip_port,bufsize=1024): tcp_socket_client=socket(AF_INET,SOCK_STREAM) tcp_socket_client.connect(ip_port) conn_auth(tcp_socket_client) while True: data=input('>>: ').strip() if not data:continue if data == 'quit':break tcp_socket_client.sendall(data.encode('utf-8')) respone=tcp_socket_client.recv(bufsize) print(respone.decode('utf-8')) tcp_socket_client.close() if __name__ == '__main__': ip_port=('127.0.0.1',9999) bufsize=1024 client_handler(ip_port,bufsize)
2.19 socketserver实现并发
解读socketserver源码http://www.cnblogs.com/Eva-J/p/5081851.html
2.19.1 server端
########################### server端#####################import socketserverclass Myserver(socketserver.BaseRequestHandler): def handle(self): self.data = self.request.recv(1024).strip() print("{} wrote:".format(self.client_address[0])) print(self.data) self.request.sendall(self.data.upper()) if __name__ == "__main__": HOST, PORT = "127.0.0.1", 9999 # 设置allow_reuse_address允许服务器重用地址 socketserver.TCPServer.allow_reuse_address = True # 创建一个server, 将服务地址绑定到127.0.0.1:9999 server = socketserver.TCPServer((HOST, PORT),Myserver) # 让server永远运行下去,除非强制停止程序 server.serve_forever()
2.19.2 client端
################################# client端#######################import socket HOST, PORT = "127.0.0.1", 9999data = "hello" # 创建一个socket链接,SOCK_STREAM代表使用TCP协议with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.connect((HOST, PORT)) # 链接到客户端 sock.sendall(bytes(data + "\n", "utf-8")) # 向服务端发送数据 received = str(sock.recv(1024), "utf-8")# 从服务端接收数据 print("Sent: {}".format(data))print("Received: {}".format(received))
2.19.3 FtpServer
###########################FtpServer####################import socketserverimport structimport jsonimport osclass FtpServer(socketserver.BaseRequestHandler): coding='utf-8' server_dir='file_upload' max_packet_size=1024 BASE_DIR=os.path.dirname(os.path.abspath(__file__)) def handle(self): print(self.request) while True: data=self.request.recv(4) data_len=struct.unpack('i',data)[0] head_json=self.request.recv(data_len).decode(self.coding) head_dic=json.loads(head_json) # print(head_dic) cmd=head_dic['cmd'] if hasattr(self,cmd): func=getattr(self,cmd) func(head_dic) def put(self,args): file_path = os.path.normpath(os.path.join( self.BASE_DIR, self.server_dir, args['filename'] )) filesize = args['filesize'] recv_size = 0 print('----->', file_path) with open(file_path, 'wb') as f: while recv_size < filesize: recv_data = self.request.recv(self.max_packet_size) f.write(recv_data) recv_size += len(recv_data) print('recvsize:%s filesize:%s' % (recv_size, filesize)) ftpserver=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer)ftpserver.serve_forever()
2.19.4 FtpClient
########################### FtpClient########################import socketimport structimport jsonimport os class MYTCPClient: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding='utf-8' request_queue_size = 5 def __init__(self, server_address, connect=True): self.server_address=server_address self.socket = socket.socket(self.address_family, self.socket_type) if connect: try: self.client_connect() except: self.client_close() raise def client_connect(self): self.socket.connect(self.server_address) def client_close(self): self.socket.close() def run(self): while True: inp=input(">>: ").strip() if not inp:continue l=inp.split() cmd=l[0] if hasattr(self,cmd): func=getattr(self,cmd) func(l) def put(self,args): cmd=args[0] filename=args[1] if not os.path.isfile(filename): print('file:%s is not exists' %filename) return else: filesize=os.path.getsize(filename) head_dic={ 'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize} print(head_dic) head_json=json.dumps(head_dic) head_json_bytes=bytes(head_json,encoding=self.coding) head_struct=struct.pack('i',len(head_json_bytes)) self.socket.send(head_struct) self.socket.send(head_json_bytes) send_size=0 with open(filename,'rb') as f: for line in f: self.socket.send(line) send_size+=len(line) print(send_size) else: print('upload successful') client=MYTCPClient(('127.0.0.1',8080)) client.run()
第3章 作业