标签:type 体系 ... .so 很多 rsize 缓冲 安全 mha
网络
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、使用情况:
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
标签:type 体系 ... .so 很多 rsize 缓冲 安全 mha
原文地址:https://www.cnblogs.com/xdl-smile/p/9393588.html