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、使用情况:
            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()#清除套接字,不能再使用
View Code

    客户端
        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()
View Code

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()
View Code

    客户端:
        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()
View Code

总结: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()#清除套接字,不能再使用
View Code

服务器:
    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# 主进程继续接收下一个客户端连接请求,
View Code

        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()#把主线程中的连接客户端的套接字关闭
View Code

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()
View Code
#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()
View Code
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()
ftp_server
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()
ftp_client

  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()
View Code
#基于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)
View Code

                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())
View Code

                3、epoll(linux,unix)多路复用
            3、多路复用的特点:
                1、可以同时监听多种IO事件
                2、当任意IO事件发生时会处理
                3、在处理每个事件时不能死循环(长期占有服务器)
                4、IO多路复用,是基于IO的处理,不是多线程或者多进程
      4、事件驱动IO
      5、异步IO


猜你喜欢

转载自www.cnblogs.com/xdl-smile/p/9393588.html
今日推荐