python_网络编程
网络
ISO(国际标准化组织)--->网络体系结构标准(OSI模型)
OSI:
网络信息传输比较复杂需要很多功能协同-->将功能分开,降低耦合度,让每个模块完成一定的功能-->将这些模块按照一定的顺 序进行组合,完成功能,条理清晰。
按照规定功能,顺序排序的体系结构就叫做OSI模型
OSI七层模型:
1、应用层:
1、提供用户服务,例如处理应用程序,文件传输,数据管理
2、表示层
1、做数据的转换和压缩,解压,加密等
3、会话层
1、决定了进程间的连接建立,选择使用什么样的传输协议
4、传输层
1、建立网络连接,提供合适的连接传输服务,提供流量控制
5、网络层
1、控制分组传输,进行路由选择,网络互联
6、链路层
1、提供链路交换,具体的数据收发
7、物理层
1、物理硬件,具体的传输条件和传输接口
四层模型:
1、应用层(包含应用层、表示层、会话层)
2、传输层
3、网络层
4、物理链路层(包含链路层、物理层)
五层模型(TCP/IP模型):
1、应用层(包含应用层、表示层、会话层)
2、传输层
3、网络层
4、链路层
5、物理层
协议:网络协议即在网络传输过程中,为保证通信正常而制定的都遵循的约定
1、应用层协议:TFTP、DNS、FTP、SMTP、HTTP
2、传输层协议:TCP 、UDP
3、网络层协议:IP、ARP、ICMP
4、物理链路层协议:IEEE
网络知识:
1、主机:
1、主机名称(计算机名,域名):socket.gethostname()查看主机名
2、本地主机表示方法:IP : ‘localhost’、127.0.0.1 表示本机通信地址
3、0.0.0.0:表示在局域网内的可用主机IP
4、192.168.43.105:表示本机在网络上的标识(linux终端上ifconfig查看IP地址)
5、>>> socket.gethostbyname('xdl-gj')
'127.0.1.1'
>>> socket.gethostbyname('localhost')
'127.0.0.1'
2、IP地址:
1、IPv4:点分十进制(127.0.0.1)或二进制(8*4 32为2进制表示)
2、IPv6:更多可用的IP
gethostbyaddr():查看主机的信息,返回一个元组里面有三个类列表分别代表主机名、主机别名、主机地址
>>> socket.gethostbyaddr('127.0.0.1')
('localhost', [], ['127.0.0.1'])
IP地址转换为二进制
1、inet_aton:将地址十进制转换为二进制
>>> socket.inet_aton('127.0.0.1')
b'\x7f\x00\x00\x01'
>>> socket.inet_ntoa(b'\x7f\x00\x00\x01')
'127.0.0.1'
2、inet_ntoa:将地址二进制转为十进制
3、inet_pton(socket.AF_INET.'127.0.0.1'):可以转换IPv4(AF_INET)和IPv6(AF_INET6)
4、inet_ntop()
>>> socket.inet_pton(socket.AF_INET,'127.0.0.1')
b'\x7f\x00\x00\x01'
>>> socket.inet_ntop(socket.AF_INET,b'\x7f\x00\x00\x01')
'127.0.0.1'
3、域名:互联网服务器IP的名字,方便使用
4、端口号:是地址的组成部分,用于在一个系统中区分应用层程序
1、取值范围:1~65535
2、1~255:是众所周知的端口
3、256~1023:系统进程占用
4、1024~49151:登记端口(用户可以使用)
5、19152~65535:私有端口或者动态端口
6、getservnyname():获取系统中某个网络程序的端口号
>>> socket.getservbyname('mysql')
3306
>>> socket.getservbyname('ssh')
22
>>> socket.getservbyname('http')
80
>>> socket.getservbyname('https')
443
5、子网掩码:与IP配合使用,用来确定当前的网段
6、字节序:
1、小端序:低序字节存在低地址位
2、大端序:高序字节存在低地址位
3、网络统一:网络字节序(等同于大端序)保证不同主机按照相同方式发送接收数据
传输层提供的通信类型:
1、面向连接的可靠服务(TCP)
1、TCP协议中规定,传输服务必须建立连接,传输结束必须断开连接,传输数据必须保证可靠
2、建立连接(三次握手)
1、客户端向服务端发送连接请求(发送一个试探的标志字符给服务器,)
2、服务端接受到请求后告知客户端可以连接
3、客户端再次告知服务端已经收到回复,下面开始发送具体消息
3、数据的可靠性:无重复、无丢失、无失序、无错误
4、断开连接(四次挥手)
1、主动方发送标志告知被动方要断开连接
2、被动方返回相应的标志信息告知主动方我已经接收到你的请求(之后会处理没有处理完的事情)
3、被动方再次发送标识信息表示已经准备就绪可以断开
4、主动方断开连接并告诉被动方
5、使用情况:
1、对传输质量要求较高,需求可靠的传输
2、传输的数据量较大(比如传文件),不需要频繁的连接断开
3、比如:QQ消息,邮件发送,文件上传,账户登录
2、面向无连接的不可靠服务(UDP)
1、不保证数据的可靠性
2、数据的发送都是由发起端决定的,不考虑接收端的情况
3、没有三次握手和四次挥手的过程
4、传输效率高
5、可靠性由应用层来实现
6、使用情况:
1、对实时性要求较高
2、网络情况不佳的时候
3、对数据的准确性没有严格要求
4、建立必要的非连接的情况(比如广播、组播)
5、比如:视频、直播
套接字:(网络间进行通信的方式的名称)
在linux中演化为一种文件类型socket
套接字的分类:
1、流式套接字:表示使传输层使用tcp协议提供面向连接的传输服务
2、数据报套接字:表示传输层使用udp协议提供面向无连接的传输服务
3、原始套接字:一般用作底层协议测试(用不到)
基于TCP协议的socket编程
服务端:
1、创建一个tcp流式套接字(socket)
socket(family=AF_INET,type=SOCK_STREAM,proto=0)
1、功能:创建一个套接字对象
2、参数:
1、family:协议族类型(AF_INET UNIX)
2、type:套接字类型
1、SOCK_STREAM:tcp流式套接字
2、SOCK_DGRAM:udp数据报套接字
3、SOCK_RAM:原始套接字
3、proto:子协议选项,一般为0表示没有子协议
3、返回值:返回套接字对象
2、绑定本机的IP和端口号(bind)
bind(address)
1、功能:绑定本机的IP和端口号
2、参数:是一个包含两个元素的元组,元组的第一个元素是主机名(字符串),第二个是使用的端口号(数字)
比如:('',8888)('localhost',8888)('127.0.0.1',8888)只能本机测试使用
('0.0.0.0',8888)(192.168.0.105)可以让其他主机访问
3、将套接字变为可监听套接字(listen)
linten(n)
1、功能:将套接字设置为监听套接字,并且设置一个连接等待队列
2、参数:是一个正整数(>=1),在linux系统下无效
4、套接字等待客户端请求(accept)
accept()
1、功能:阻塞等待客户端的连接
2、参数:无
3、返回值:
1、第一个返回值为 和客户端交互的新的套接字
2、第二个返回值为 连接进来的客户端的address
5、消息的收发(send/recv)
1、recv(buffer)
1、功能:接收网络消息
2、参数:正整数,表示一次接收从缓冲区中拿到的消息的字节数
3、返回值:返回接收到的消息(字节串)
4、当接收的网络缓冲区中没有内容时,则会被阻塞
5、当连接断开后,recv会结束阻塞返回一个空字符串
2、send(data)
1、功能:发送网络消息
2、参数:要发送的内容
3、返回值:实际发送的字节数
4、python3中要求send的内容必须为bytes格式
3、sendall(data)
1、功能:发送网络信息
2、参数:要发送的内容,要求为bytes格式
3、返回值:如果成功发送返回None,发送失败报异常
6、关闭套接字(close)
close()
1、功能:关闭一个套接字
#python网络套接字模块 from socket import * HOST = '127.0.0.1' PORT = 8888 ADDR = (HOST,PORT) BUFFERSIZE = 1024 #1、创建流式套接字 socketfd = socket(AF_INET,SOCK_STREAM,proto=0) #2、绑定本机IP和端口号 socketfd.bind(ADDR) #3、将套接字变为可监听套接字 socketfd.listen(5) print('wait for connect.....') #4、套接字等待客户端请求 conn,addr = socketfd.accept() print('connect from ',addr) #5、消息的收发 while True: data = conn.recv(BUFFERSIZE) if not data: break print('接收到:',data.decode()) n = conn.send(b'Recv your message') print('发送了%d字节的数据'%n) #6、关闭套接字 conn.close()#表示和客户端断开连接 socketfd.close()#清除套接字,不能再使用
客户端
connect(address)
1、功能:向服务器发起连接请求
2、参数:address 是一个元组,即为要连接的服务器的地址
注意点:
1、客户端要和服务器端的套接字类型相同
2、客户端就是用创建的套接字和服务器交互
3、recv和send要与服务器配合,避免recv死阻塞
TCP循环服务不能满足多个客户端同时发送请求的情况,它不允许某个客户端单独长期占有服务器资源
from socket import * HOST = '127.0.0.1' PORT = 8888 ADDR = (HOST,PORT) BUFFERSIZE = 1024 connfd = socket(AF_INET,SOCK_STREAM) #连接服务器 connfd.connect(ADDR) #和服务器进行通信 while True: data = input('发送>>>') if not data: break connfd.send(data.encode()) data = connfd.recv(1024) print('客户端收到:',data.decode()) #关闭套接字 connfd.close()
tcp数据传输
1、recv会不断取出缓冲区中的内容,如果一次没有拿完,那么下次会继续收取没有拿完的消息
TCP粘包:
1、指的是发送方发送若干数据的时候,因为是数据流的传输方式,导致数据粘连在一起,接收方一次将多次发送过来的数据一起接收,产生接收数据的粘连
2、粘包是TCP传输特有的现象,因为TCP传输没有消息边界。如果是发送连续的内容比如文件等则粘包没有影响,如果是每次发送为单独需要处理的内容则需处理粘包
如何处理粘包
1、将消息格式化(每次固定发送一定长度的数据)
2、发送消息的同时发送一个消息长度标识
3、让消息的发送延迟,使接收端每次都能够有时间接收一个消息
UDP数据报套接字:面向无连接的不可靠的传输服务
服务器:
1、创建数据报套接字
2、绑定本地IP和端口
3、收发消息
recvfrom(BUFFERSIZE)
1、功能:在udp中接收消息
2、参数:buffersize表示一次最多可以接收多少字节的消息
3、返回值:
1、data:接收到的消息
2、addr:表示从哪个客户端接收的消息
4、每次只能接收一个数据包,如果数据包的大小超过recvfrom的设置大小则会出现数据丢失
sendto(data,addr)
1、功能:向一个网络终端发送消息
2、参数:
1、data 要发送的消息(bytes)
2、addr 要发送对象的地址
4、关闭套接字
from socket import * import sys from time import ctime #从命令行传入客户端IP和端口号 HOST = sys.argv[1] PORT = int(sys.argv[2]) ADDR = (HOST,PORT) BUFFERSIZE = 1024 #1、创建数据报套接字 sockdf = socket(AF_INET,SOCK_DGRAM) #2、绑定本地IP和端口 sockdf.bind(ADDR) #3、收发消息 while True: data,addr = sockdf.recvfrom(BUFFERSIZE) print('recv from',addr,':',data.decode()) sockdf.sendto(('在%s接收到消息'%ctime()).encode(),addr) #4、关闭套接字 sockdf.close()
客户端:
1、创建套接字
2、消息收发
3、关闭套接字
import sys
sys.argv:
1、将命令行内容收集为一个列表,每个元素是命令行中的一项
2、命令行传入的内容均为str格式
3、命令行内容以空格分割,引号可以合成一个整体
from socket import * import sys #从命令行传入服务器IP和端口号 HOST = sys.argv[1] PORT = int(sys.argv[2]) ADDR = (HOST,PORT) BUFFERSIZE = 1024 #1、创建数据报套接字 socketfd = socket(AF_INET,SOCK_DGRAM) #2、消息收发 while True: data = input('消息>>') if not data:#输入为空时客户端退出 break socketfd.sendto(data.encode(),ADDR) data,addr = socketfd.recvfrom(BUFFERSIZE) print('从服务器接收:',data.decode()) #3、关闭套接字 socketfd.close()
总结:tcp 和 udp的区别
1、tcp是有连接的,udp是无连接的
2、tcp有三次握手四次挥手,udp没有
3、tcp是以数据流传输数据,会粘包,udp是数据报的形式传输,没有粘包现象
4、tcp的连接需要消耗一定的资源,相比之下udp资源消耗少
5、tcp保证数据的可靠性,udp不保证
6、tcp需要listen、accept、connect,udp不需要
socket模块 和 套接字属性
套接字属性:
1、getpeername()
1、功能:用作服务器连接套接字,查看连接的客户端的地址
2、getsockname()
1、功能:获取套接字对应的绑定的IP和端口
3、s.type
1、功能:获取套接字的类型(SOCK_STREAM/SOCK_DGRAM/SOCK_RAM)
4、fileno()
1、功能:获取套接字的文件描述符编码
2、文件描述符:系统会给进程中的每一个IO操作对象匹配一个>=0的正整数作为标号,我们称之为该IO操作的文件描述符。一个进程中所有的IO的文件描述符不会重复
5、setsockopt(level,optname,value)
1、功能:设置套接字选项,可以增加或改变套接字的功能
2、参数:
1、level:要定义的选项类型,比如:SOL_SOCKET,IPPROTO_IP,IPPROTO_TCP
2、optname:每种类型都有具体的选项(level的子选项),根据具体的需求选项进行设置
比如:SOL_SOCKET--->SO_REUSEADDR
3、value:将选择的选项设置为 什么值
6、getsockopt(level,otpname)
1、功能:获取相应选项的值,即setsockopt()中的第三个参数value
2、参数:
1、level:要定义的选项类型,比如:SOL_SOCKET,IPPROTO_IP,IPPROTO_TCP
2、optname:每种类型都有具体的选项(level的子选项),根据具体的需求选项进行设置
3、返回值:获取到值
from socket import * HOST = '127.0.0.1' PORT = 8888 ADDR = (HOST,PORT) BUFFERSIZE = 1024 socketfd = socket(AF_INET,SOCK_STREAM,proto=0) print(socketfd.fileno())#3,前面三个分别为os.stdin,os.stdout,os.stdnumber print(socketfd.type)#SocketKind.SOCK_STREAM print(socketfd.getsockname())#('0.0.0.0', 0)获取套接字对应的绑定的IP和端口,此时还没有绑定所以为0 #将端口号设置为立即重用 socketfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) print(socketfd.getsockopt(SOL_SOCKET,SO_REUSEADDR))#1,获取上面函数的第三个参数,如果没有设置则返回0 socketfd.bind(ADDR) print(socketfd.getsockname())#('127.0.0.1', 9999) socketfd.listen(5) print('wait for connect.....') conn,addr = socketfd.accept() print('connect from ',conn.getpeername())#connect from ('127.0.0.1', 43236) print(conn.fileno()) while True: data = conn.recv(BUFFERSIZE) if not data: break print('接收到:',data.decode()) n = conn.send(b'Recv your message') print('发送了%d字节的数据'%n) conn.close()#表示和客户端断开连接 socketfd.close()#清除套接字,不能再使用
服务器:
1、硬件服务器:计算机主机 (IBM,HP,)
2、软件服务器:网络服务器,提供后端逻辑服务和请求处理的程序集合及架构,例如:web服务器等
1、服务器架构:c/s b/s 服务器的组织形式
2、服务器追求:更快速,更安全,并发量更大
socket服务器模型
1、循环服务器模型:循环处理客户端的请求,处理完一个继续处理下一个
1、缺点:不能同时处理多个请求,不允许某个客户端长期占用服务器资源
2、因为udp不需要进行连接,所以循环服务器模型更加适合udp通信
2、并发服务器模型:每有一个客户端就创建一个进程或线程处理客户端的具体请求事情,而主线程或主进程继续接收其他客户端的连接
1、多进程实现(fork)
1、创建套接字,绑定,监听
2、接收客户端连接请求,创建新的进程
3、主进程继续接收下一个客户端连接请求,子进程处理客户端事件
4、有客户端断开则关闭相应的子进程
from socket import * import os import signal #信号用来处理僵尸进程 def handler(c): '''子进程处理客户端请求事件的函数''' while True: data = conn.recv(BUFFERSIZE) if not data: break print('recv %s from %s'%(data.decode(),addr)) num = conn.send(b'hello') print('send %s size to %s:'%(num,addr)) conn.close() os._exit(0)#子进程结束 HOST = '127.0.0.1' PORT = 8888 ADDR = (HOST,PORT) BUFFERSIZE = 1024 # 1、创建套接字,绑定,监听 socketfd = socket(AF_INET,SOCK_STREAM) socketfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)#将端口号设置为立即重用 socketfd.bind(ADDR) socketfd.listen(5) #处理僵尸进程 signal.signal(signal.SIGCHLD,signal.SIG_IGN) # 2、接收客户端连接请求,创建新的进程 while True: try: conn,addr = socketfd.accept() except KeyboardInterrupt: print('服务器结束') socketfd.close()#如果按ctrl+c则关闭套接字 os._exit(0) except Exception: continue#如果是其他异常则继续监听 print('接收到客户端连接>',conn.getpeername()) #创建子进程,子进程处理客户端事件 pid = os.fork() if pid < 0: print('create process failed') continue elif pid ==0: socketfd.close()#把子进程中的流式套接字关闭 print('接收客户端请求事件') handler(conn) else: conn.close()#把主进程中的连接客户端的套接字关闭 continue# 主进程继续接收下一个客户端连接请求,
2、多线程实现:(threading)
1、创建套接字,绑定,监听
2、接收客户端连接请求,创建新的线程
3、主进程继续接收下一个客户端连接请求,子线程处理客户端事件
4、有客户端断开则关闭相应的子线程
from socket import * import threading import os def handler(c): '''子线程处理客户端请求事件的函数''' while True: data = conn.recv(BUFFERSIZE) if not data: break print('recv %s from %s'%(data.decode(),addr)) num = conn.send(b'hello') print('send %s size to %s:'%(num,addr)) conn.close() HOST = '127.0.0.1' PORT = 8888 ADDR = (HOST,PORT) BUFFERSIZE = 1024 # 1、创建套接字,绑定,监听 socketfd = socket(AF_INET,SOCK_STREAM) socketfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)#将端口号设置为立即重用 socketfd.bind(ADDR) socketfd.listen(5) # 2、接收客户端连接请求,创建新的线程 while True: try: conn,addr = socketfd.accept() except KeyboardInterrupt: print('服务器结束') socketfd.close()#如果按ctrl+c则关闭套接字 os._exit(0) except Exception: continue#如果是其他异常则继续监听 print('接收到客户端连接>',conn.getpeername()) #创建子进程,子进程处理客户端事件 t = threading.Thread(target = handler,args=(conn,)) t.setDaemon(True)#主线程结束,所有的子线程都结束 t.start() #conn.close()#把主线程中的连接客户端的套接字关闭
socketserver模块(Python2中:SocketServer)
三部分:
多进程/多线程 TCP/UDP streamhandler/datagramhandler
ForkingMixIn TCPServer StreamRequestHandler
ThreadingMixIn UDPServer DatagramRequestHandler
ThreadingTCPServer = ThreadingMixIn + TCPServer
ThreadingUDPServer = ThreadingMixIn + UDPServer
ForkingTCPServer = ForkingMixIn + TCPServer
ForkingUDPServer = ForkingMixIn + UDPServer
创建步骤:
1、创建服务器类
2、创建处理类
3、使用创建的服务器类来创建服务器
4、运行服务器
#fork +tcp 并发 from socketserver import * #创建服务器类 class Server(ForkingMixIn,TCPServer):#等价于Server(ForkingTCPServer) pass #创建处理类 class Handler(StreamRequestHandler): def handle(self): '''当客户端连接进来时候会自动调用该函数处理客户端请求时间''' addr = self.client_address print('connect from ',addr) while True: #self.requset为tcp中为我们自动生成的和客户端交互的套接字 data = self.request.recv(1024) if not data: break print('recv %s from %s'%(data.decode(),addr)) num = self.request.send(b'hello') print('send %s size to %s:'%(num,addr)) #使用创建的服务器类来生产服务器 server = Server(('127.0.0.1',8888),Handler) #运行服务器 server.serve_forever()
#fork + udp from socketserver import * class Server(ForkingUDPServer): pass class Handler(DatagramRequestHandler): #udp无连接所以request的含义不同 def handle(self): data = self.rfile.readline() print('接收到了:',data.decode()) self.wfile.write(b'recevie message') server = Server(('127.0.0.1',8888),Handler) server.serve_forever()
from socket import * import os import sys import signal import time FILE_PATH = '/home/xdl/python/ftp/file_path/' class FtpServer(object): def __init__(self,conn): self.conn = conn def do_list(self): '''服务器端确认请求是否可以执行''' filelist = os.listdir(FILE_PATH) if not filelist or filelist == None: self.conn.send(b'FAIL') else: self.conn.send(b'OK') time.sleep(0.1) for file in filelist: #print(file) if file[0] != '.' and os.path.isfile(FILE_PATH+file): self.conn.send(file.encode()) time.sleep(0.1) self.conn.send(b'##')#告诉客户端我已经发送完毕 print('文件列表发送完毕') return def do_get(self,filename): try: with open(FILE_PATH+filename,'rb') as fd: self.conn.send(b'OK') time.sleep(0.1) for line in fd: self.conn.send(line) except: print('文件打开失败') self.conn.send(b'FAIL') time.sleep(0.1) self.conn.send(b'##') print('文件发送成功') return def do_put(self,filename): try: with open(FILE_PATH+filename,'w') as fd: print(filename) self.conn.send(b'ok') time.sleep(0.1) while True: data = self.conn.recv(1024).decode() if data == '##': break fd.write(data) print('文件接收成功') return except OSError: print('文件打开失败')#可能权限不够导致文件打开失败 self.conn.send(b'FAIL') def main(): if len(sys.argv) != 3:#保证输入的命令是三个部分 print('argv is error') sys.exit(1) HOST = sys.argv[1] PORT = int(sys.argv[2]) ADDR = (HOST,PORT) BUFFERSIZE = 1024 socketfd = socket(AF_INET,SOCK_STREAM)#创建流式套接字 socketfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)#将端口设置为立即可用 socketfd.bind(ADDR) socketfd.listen(5) signal.signal(signal.SIGCHLD,signal.SIG_IGN)#处理僵尸进程 while True: try: conn,addr = socketfd.accept() except KeyboardInterrupt: print('服务器结束')#如果按ctrl+c则关闭套接字 socketfd.close() os._exit(0) except Exception: continue #如果是其他异常则继续监听 print('connect from :',addr) pid = os.fork()#创建子进程 if pid < 0: print('create process failed') continue elif pid ==0: socketfd.close()#把子进程中的流式套接字关闭 ftp = FtpServer(conn) while True:#接收客户请求 data = conn.recv(1024).decode() if data[0] =='L': ftp.do_list() elif data[0] =='G': filename = data.split()[-1] #print(filename) ftp.do_get(filename) elif data[0] =='P': filename = data.split()[-1] print(filename) ftp.do_put(filename) elif data[0] == 'Q': print('客户端退出') sys.exit(0) else: conn.close()#把主进程中的连接客户端的套接字关闭 continue# 主进程继续接收下一个客户端连接请求 if __name__ == '__main__': main()
from socket import * import sys import time FILE_PATH = '/home/xdl/python/ftp/down_file_path/' class FtpClient(object): def __init__(self,connfd): self.connfd = connfd def do_list(self): self.connfd.send(b'L')#发送请求类型 #接收服务器端的确认消息 data = self.connfd.recv(1024).decode() if data == 'OK': while True: data = self.connfd.recv(1024).decode() if data == '##': break print(data) print('文件列表展示完毕') return else: print('文件列表请求失败') return def do_get(self,filename): self.connfd.send(('G '+filename).encode()) data = self.connfd.recv(1024).decode() if data == 'OK': with open(FILE_PATH+filename,'w') as fd: while True: data = self.connfd.recv(1024).decode() if data == '##': break fd.write(data) print('%s下载完成'%filename) return else: print('下载文件失败') return def do_put(self,filename): try:#如果文件打开失败,就不要向服务器发送数据了 with open(FILE_PATH+filename,'rb') as fd: self.connfd.send(('P '+filename).encode()) data = self.connfd.recv(1024).decode() if data == 'ok': for line in fd: self.connfd.send(line) time.sleep(0.1) self.connfd.send(b'##') print("%s文件上传成功"%filename) else: print('文件上传失败') return except OSError: print('文件打开失败') return def do_quit(self): self.connfd.send(b'Q') def main(): if len(sys.argv) != 3:#保证输入的命令是三个部分 print('argv is error') sys.exit(1) HOST = sys.argv[1] PORT = int(sys.argv[2]) ADDR = (HOST,PORT) BUFFERSIZE = 1024 connfd = socket() connfd.connect(ADDR) ftp = FtpClient(connfd) while True: print('***********命令选项***********') print('***********L(展示列表)***********') print('***********G(下载)***********') print('***********P(上传)***********') print('***********Q(退出)***********') data = input('输入命令>>') if data == 'L': ftp.do_list() elif data[0] == 'G': filename = data.split()[-1] ftp.do_get(filename) elif data[0] == 'P': filename = data.split()[-1] ftp.do_put(filename) elif data == 'Q': ftp.do_quit() connfd.close() sys.exit(0) else: print('请输入正确的命令!!!') continue #关闭套接字 connfd.close() if __name__ == '__main__': main()
IO 服务器模型
IO的分类:
1、阻塞IO(效率最低)
2、非阻塞IO(效率相对较高)
1、在遇到原本阻塞的条件时不再阻塞,去执行其他内容,但是往往需要不断轮询阻塞条件是否可以执行
3、IO多路复用
1、同时监控多个IO事件,当IO哪个事件就绪就执行哪个IO事件,形成一种并发的效果
2、select 模块
1、select(win,linux,unix)多路复用
1、select(rlist, wlist, xlist[, timeout]) -> (rlist, wlist, xlist)
2、功能:通过select方法监控IO事件
3、参数:
1、rlist:列表,存放要监控的IO事件,将要处理的IO事件(rlist -- wait until ready for reading)
2、wlist:列表,存放要监控的IO事件,要主动处理的IO事件(wlist -- wait until ready for writing)
3、xlist:列表,存放要监控的IO事件,希望发生异常通知你处理的IO事件(xlist -- wait for an ``exceptional condition'')
4、返回值:当select监控的IO事件中有一个或者多个可以处理的时候结束阻塞,进行返回
1、r:列表,参数rlist中如果有可以处理的IO放在这个列表里面
2、w:列表,参数wlist中如果有可以处理的IO放在这个列表里面
3、x:列表,参数xlist中如果有可以处理的IO放在这个列表里面
#基于select的IO多路复用监听服务器 from socket import * import select #s套接字作为一个IO事件 s = socket() s.bind(('127.0.0.1',8889)) s.listen(5) rlist = [s] wlist = [] xlist = [s] while True: #监听三个关注的列表中的IO事件 rs,ws,es = select.select(rlist,wlist,xlist) print('有IO事件发生了') #通过for循环遍历每个返回列表,去处理IO for r in rs: if r is s: c,addr = s.accept() print("connect from ",addr) #将新的IO事件加入到监控列表 rlist.append(c) else: data = r.recv(1024).decode() if not data: print('客户端退出') rlist.remove(r) r.close() print('收到客户端信息',data) r.send('收到了你的消息'.encode()) for w in ws: pass for e in es: pass s.close()
#基于select的IO多路复用监听服务器 from socket import * import select import sys #s套接字作为一个IO事件 s = socket() s.bind(('127.0.0.1',8889)) s.listen(5) rlist = [s] wlist = [] xlist = [s] while True: #监听三个关注的列表中的IO事件 rs,ws,es = select.select(rlist,wlist,xlist) print('有IO事件发生了') #通过for循环遍历每个返回列表,去处理IO for r in rs: if r is s: c,addr = s.accept() print("connect from ",addr) #将新的IO事件加入到监控列表 rlist.append(c) xlist.append(c)#将与客户端交互的套接字加入到异常列表中 else: data = r.recv(1024).decode() if not data: print('客户端退出') rlist.remove(r) xlist.remove(r) r.close() else: wlist.append(r) print('收到客户端信息',data) for w in ws: r.send('收到了你的消息'.encode()) wlist.remove(w) for e in es: if e is s: s.close() sys.exit(0)#如果发生异常则退出 else: e.close() rlist.remove(e) xlist.remove(e)
2、poll(linux,unix)多路复用
1、p = select.poll():通过创建poll对象
2、p.register(s):加入你关注的IO事件 相当于select中的三个参数
3、p.poll(): 相当于select函数功能
1、功能:监控哪个IO事件
2、参数:无
3、返回值:[(1,event),(2,event),(3,event)....]
1、每一个就绪的IO事件都会在返回值中给出一个对应的元组
2、元组中,第一个元素为就绪的IO的fileno,第二个元素为具体的就绪事件
4、p.unregister(s):将IO事件移除监控范围 相当于select中rlist.rmove()
5、往往需要写一个字典,让IO对象和fileno对应起来
6、在poll 和 epoll中的事件分类:
1、POLLIN (1):rlist
2、POLLOUT(4):wlist
3、POLLERR(8):xlist
4、POLLHUP(16):断开连接
5、POLLPRI(2):紧急处理
6、POLLNVAL(32):无效数据
#基于poll的IO多路复用监听服务器 from socket import * import select #s套接字作为一个IO事件 s = socket() s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind(('127.0.0.1',8888)) s.listen(5) #以每个IO对象的fileno为键,IO对象为值 fdmap = {s.fileno():s} #创建poll对象 p = select.poll() #添加关注的IO p.register(s)#此处添加IO对象的fileno也可以:等价于p.register(s.fileno) while True: #监控关注的IO events = p.poll() #print(events) for fd,event in events: if fd == s.fileno(): c,addr = fdmap[fd].accept() print('Connect from ',addr) #将客户端套接字添加关注并添加到字典中 p.register(c) fdmap[c.fileno()] = c elif event & select.POLLIN :#得到True表示是当前事件类型 data = fdmap[fd].recv(1024).decode() #如果客户端退出则不再关注,并从字典中移除 if not data: p.unregister(fd) del fdmap[fd] else: print(data) fdmap[fd].send('收到你的消息'.encode())
3、epoll(linux,unix)多路复用
3、多路复用的特点:
1、可以同时监听多种IO事件
2、当任意IO事件发生时会处理
3、在处理每个事件时不能死循环(长期占有服务器)
4、IO多路复用,是基于IO的处理,不是多线程或者多进程
4、事件驱动IO
5、异步IO
协程服务器模型
1、协程(微线程,纤程),本质是单线程
2、定义:是一种用户态的轻量级线程
3、特点:
1、轻量级,创建消耗资源非常少
2、不涉及内核
4、优点:
1、无需上下文切换的开销
2、无需同步互斥操作
3、有较高的并发性(IO并发)
4、创建消耗资源少
5、缺点:
1、无法利用计算机的多核资源
2、遇到死循环等待阻塞状态会影响整个程序的运行
6、greenlet模块
1、安装:sudo pip3 install greenlet
from greenlet import greenlet #协程函数 def test1(): print(111111) gr2.switch() print(222222) gr2.switch() def test2(): print(333333) gr1.switch() print(444444) #将函数变为协程 gr1 = greenlet(test1) gr2 = greenlet(test2) gr1.switch()
7、gevent模块
2、安装:sudo pip3 install gevent
import gevent #协程函数 def foo(a,b): print("Running in foo ",a,b) gevent.sleep(3)#模拟遇到IO阻塞的情况 print('switch to foo again') def bar(): print('Running in bar') gevent.sleep(2)#模拟遇到IO阻塞的情况 print('switch to bar again') #注册为协程事件 f = gevent.spawn(foo,1,2) b = gevent.spawn(bar) gevent.joinall([f,b])
import gevent from gevent import monkey #monkey脚本插件使用要在导入socket之前 monkey.patch_all()#修改socket模块的阻塞部分, from socket import * #协程事件 def handler(c,addr): print('Connect from ',addr) while True: data = c.recv(1024).decode() if not data: break print('Recv messge:',data) c.send(b'Recv your msg') c.close() HOST = '127.0.0.1' PORT = 8888 ADDR = (HOST,PORT) s = socket() s.bind(ADDR) s.listen(5) while True: c,addr = s.accept() gevent.spawn(handler,c,addr)
进程 + 协程 方案完成高并发
1、什么是协程(单线程,优点,缺点,高并发量的IO操作)
2、协程如何工作的(在线程栈中进行跳转,是应用层的技术,遇到IO阻塞进行协程选择)
3、写过什么协程代码:协程流式套接字服务器
非阻塞IO 和 超时检测
1、定义:非阻塞IO就是在遇到原本阻塞的IO情形时,不进行阻塞等待,如果满足执行条件即执行,不满足条件就不执行
2、socketfd.setblocking(False)
1、功能:设置一个套接字的阻塞状态
2、参数:
1、默认为True表示套接字为阻塞套接字
2、如果设置为False则表示非阻塞,此时套接字使用阻塞函数时,如果无法正常执行则抛出blocking异常
3、监听套接字设置为非阻塞 accept不再阻塞
4、连接套接字设置为非阻塞 recv不再阻塞
3、定义:在阻塞状态下,设置程序超时时间,当到达时间后进程不再阻塞等待
1、比如:mulitprocessing/threading----->join
2、比如:Queue --->get put
3、比如:select --->select
4、比如:Event ---->wait
4、socketfd.settimeout(5)
1、功能:设置套接字的超时检测时间
2、参数:超时时间
import time from socket import * HOST = '127.0.0.1' PORT = 8888 ADDR = (HOST,PORT) BUFFERSIZE = 1024 socketfd = socket(AF_INET,SOCK_STREAM,proto=0) socketfd.bind(ADDR) socketfd.listen(5) #将socket 变为非阻塞 #socketfd.setblocking(False) #设置等待超时 socketfd.settimeout(5) while True: #time.sleep(8) #套接字等待客户端请求 print('wait for connect.....') try: conn,addr = socketfd.accept() print('connect from ',addr) conn.setblocking(False) while True: data = conn.recv(BUFFERSIZE) if not data: break print('接收到:',data.decode()) n = conn.send(b'Recv your message') print('发送了%d字节的数据'%n) conn.close()#表示和客户端断开连接 except Exception: pass socketfd.close()#清除套接字,不能再使用
网络广播:(是一端发送,多端接收的模式)
1、使用udp数据报套接字
2、广播地址:192.168.43.255(IP地址为最后改为255)
3、设置套接字为可以发送接收广播的套接字:s.setsockopt(SOL_SOCKET,SO_BROADCAST,1)
4、在网络中如果存在大量的广播会产生广播风暴,占用大量带宽
from socket import * HOST = '' PORT = 8888 #使用数据报套接字 s = socket(AF_INET,SOCK_DGRAM) #设置套接字为允许广播 s.setsockopt(SOL_SOCKET,SO_BROADCAST,1) s.bind((HOST,PORT)) while True: try: msg,addr = s.recvfrom(1024) print('get msg from',addr) print('广播通知:',msg.decode()) s.sendto('收到广播'.encode(),addr) except EXception as e: print(e) s.close()
from socket import * import time #设置发送的广播地址,<broadcast> #dest = ('127.0.0.255',8888) dest = ('<broadcast>',8888)#等价于dest = ('127.0.0.255',8888) s = socket(AF_INET,SOCK_DGRAM) s.setsockopt(SOL_SOCKET,SO_BROADCAST,1) while True: time.sleep(2) s.sendto('喜迎两会'.encode(),dest) data, addr = s.recvfrom(1024) print(data.decode()) s.close()
本地套接字:
1、作用:用作本地两个进程间的通信
2、传输方式:字节流的方式进行数据传输
3、socket(AF_UNIX,SOCK_STREAM)
1、功能:创建本地套接字
4、通信介质:通过套接字文件实现通信
from socket import * import os #设置通信的套接字文件 address = './sockfile' try: os.unlink(address)#删除这个目录下的这个文件 except OSError: if os.path.exists(address):#判断文件是否删除成功 raise s = socket(AF_UNIX,SOCK_STREAM) s.bind(address)#绑定本地套接字文件 s.listen(5) while True: conn,addr = s.accept() print('connect from',addr)#addr是一个空的字符串 while True: data = conn.recv(1024).decode() if not data: break print('recv:',data) conn.send(b'recv your msg') conn.close() s.close()
from socket import * address = './sockfile' s = socket(AF_UNIX,SOCK_STREAM) try: s.connect(address) except error as e: print(e) try: msg = b'This is a unix msg' s.send(msg) data = s.recv(1024).decode() print('recv',data) except error as e: print(e) finally: s.close()
http(超文本传输协议)
1、应用层协议----->传输层使用tcp协议
2、用途:
1、网站中网页的传输、数据的传输
2、也可以用作通过编程传输数据
3、特点:
1、支持典型的客户端服务器模式
2、灵活简单
3、几乎支持所有的数据格式(视频,音频,等)
4、是无状态的
5、http1.0 无连接 http1.1 持续连接
请求:
1、格式:
1、请求行:发送什么类型的请求
1、请求方法(
1、get:获取网址(URL)标识的网络资源。
URL:统一资源定位符,指的是网络资源在网络中的位置,网址即为一类的url
2、post:提交一定的附加数据,用以获取相应的返回
3、head:获取URL所标识的响应消息报头
4、put:获取服务器的资源
5、delete:删除一个服务器资源
6、trace:用于测试和诊断
7、connect:保留的请求方法
8、options:请求获取服务器性能,查询相关资源信息
2、请求格式(html,jpg)
3、协议版本(http1.0,http1.1)
2、请求头:对发送请求信息的描述
3、空行:
4、请求体:具体的请求参数或请求内容
1、get请求即为get请求的参数
2、post请求即为post请求提交的内容
网络响应:
1、响应行:反馈响应情况
1、协议版本
2、响应码
1、1XX:提示信息,表示请求已经接收,正在处理
2、2XX:访问成功
1、200:访问成功
3、3XX:重定向,完成任务需要其他操作
1、301:永久重定向
2、302:临时重定向
4、4XX:客户端错误
1、400:客户端请求有语法错误
2、401:访问没有授权
3、403:服务器已收到请求,但是拒绝执行
4、404:请求的服务器资源不存在
5、5XX:服务器端错误
1、500:服务器发生未知错误
2、503:服务器暂时不能执行,请稍后访问再试
3、响应码对应信息
2、响应头:对响应内容的描述
3、空行
4、响应体:根据请求返回给客户端的内容
总结:
1、什么是HTTP协议
2、HTTP协议请求和响应的格式
3、请求方法都有哪些
4、常见的响应吗代表什么
5、get和post的区别
6、http协议的特点
python http server
在python3中为:http.server模块
在python2中为:BaseHTTPServer模块
HTTPServer服务器用来接收客户端的HTTP请求
#导入HTTPServer类,兼容python2和python3 try: from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer#python2 except ImportError: from http.server import BaseHTTPRequestHandler,HTTPServer#python3 class RequestHandler(BaseHTTPRequestHandler): '''具体的请求处理类''' def do_GET(self): print('do method get') fd = open('index.html','rb') content = fd.read() fd.close() #设置响应吗 self.send_response(200) #设置响应头 self.send_header('Content-type','text/html') #响应设置完毕 self.end_headers() #发送响应体 self.wfile.write(content) return def do_POST(self): pass #搭建启动服务器 address = ('127.0.0.1',8888) server = HTTPServer(address,RequestHandler) server.serve_forever()
使用wsgi内置模块实现一个简易的web服务器
from wsgiref.simple_server import make_server def routers(): urlpatterns = ( ('/book', f1), ('/web', f2), ) return urlpatterns def f1(env): return [b'<h1>Hello book</h1>'] def f2(env): return [b'<h1>Hello web</h1>'] def application(environ, start_response): # 第一个参数为状态码, 第二个参数为请求头 start_response('200 OK', [('Content-Type', 'text/html')]) urlpatterns = routers() path = environ['PATH_INFO'] func = None for item in urlpatterns: if item[0] == path: func = item[1] break if func: return func(environ) else: return ["<h1>404</h1>".encode('utf8')] if __name__ == '__main__': port = 9999 httpd = make_server('127.0.0.1', port, application) print(f"Serving HTTP on port {port}") httpd.serve_forever()