• Python网络编程(http协议,IO多路复用、select内核监听)


    前言:

    什么是IO?
    • 分为IO设备和IO接口两个部分
    • Linux系统,I/O操作可以有多种方式
    • 比如DIO(DirectI/O)
    • AIO(AsynchronousI/O异步I/O)
    • Memory-MappedI/O(内存映设I/O)等...
    • 不同的I/O方式有不同的实现方式和性能,在不同的应用中可以按情况选择不同的I/O方式。

    补充昨天HTTP:


    from socket import *
    # 接收请求
    # 查看请求
    # 返回客户端段请求内容
    
    
    def handleClient(connfd):
        request = connfd.recv(4096)
        # print("***********")
        # print(request)
        # print("************")
        # 按照行切割请求
        request_lines = request.splitlines()
        for line in request_lines:
            print(line.decode())
    
        try:
            f = open('index.html')
        except IOError:
            response = "HTTP/1.1 303 Not Found
    "
            response += "
    "  # 空行
            response += '''
                    **************************
                    Sorry, not found the page.
                    **************************
                    '''
        else:
            response = "HTTP/1.1 200  OK
    "
            response += '
    '
            response += f.read()
        finally:
            # 发送给浏览器
            connfd.send(response.encode())
    
    
    # 创建套接字,调用handleClient完成功能
    def main():
        # 创建tcp套接字
        sockfd = socket()
        sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        sockfd.bind(('0.0.0.0', 8000))
        sockfd.listen()
        while True:
            print("Listen the port 8000...")
            connfd, addr = sockfd.accept()
            # 处理浏览器发来的请求
            handleClient(connfd)
            connfd.close()
    
    
    if __name__ == "__main__":
        main()
    S.splitlines  
    拆分文件字符串按行分隔
     
     
     
     
    内存中存在数据交换的操作认为是IO操作(输入输出)
    例如:
        内存与磁盘数据交换:文件读写、数据库更新
        内存和终端数据交换:input、print、sys.stdout、sys.stdin、  sys.stder
        内存和网络数据交换:网络连接、recv、send、recvfrom
     
    IO秘集程型序:
         程序执行中有大量的IO操作,而比较少的CPU运算操作
         消耗CPU较少,IO运行时间长
    cpu(计算)密集形程序:
         程序存在大量的CPU运算,IO操作相对较少
         CPU消耗大
     
    IO分类:
        1.阻塞IO:
            程序运行中遇到IO条件没有达成
    或传输情况较慢的情况下会出现阻塞状态
      阻塞IO是IO最简单的逻辑情形,也是默认状态
      阻塞IO是效率很低的IO状态
    阻塞情况:
        1.因为IO条件没有达成
            IO阻塞函数(input、print、recv、recvfrom)
        2.处理IO耗时较长形参阻塞
            文件读写、网络数据发送过程
     
        2.非阻塞IO:
            在程序运行中遇到IO操作不让其产生阻塞
    实现手段:
       改变IO事件的属性,使其变为非阻塞
       通常会和循环一起使用 进行条件的循环监控
     
        3.IO多路复用
            定义:
      通过一个监测,可以同时监控多个IO事件的行为
      当那个IO可以执行,让这个IO事件发生
      同时监控多个IO事件,当哪个IO事件准备就绪就执行哪个IO事件
      此时形成多个IO时间都可以操作的现象,不必逐个等待执行
    IO准备就绪:
      IO事件即将发生时的临界状态不可逆转
      在程序中存在的IO事件中选择要监测的事件
      创建监测,将监测的IO事件注册
      等待监测的IO事件发生判断是什么事件
      处理相应的IO
     
     
        事件驱动IO
        异步IO
        ...
     
    s.setblocking(False)
       功能:
          将套接字设置为非阻塞状态
       参数:
          默认为阻塞, 设置为False为非阻塞状态
     
    超时检测:
        原本阻塞的IO设置一个最长阻塞等待时间
        在规定时间内如果达到条件正常执行
        如果时间到仍未达到条件则结束阻塞
            s.settimeout(sec)
                功能:
                        设置套接字的超时时间
                 参数:
                       时间(
     
     
    select模块
    from select import
    select 支持:Windows、Linux、Unix  
    poll   支持:Linux、Unix
    epoll  支持:Linux、Unix
     
    rs, ws, xs = select(rlist, wlist, xlist[, timeout])
       功能:
          监控IO事件 ,阻塞等待监控的IO事件发生
       参数:
           rlist   列表:
              表示存放我们需要等待处理的IO
           wlist   列表:
              表示存放我们想要主动处理的IO
           xlist   列表:
              表示存放出错希望去处理的IO
           timeout: 超时检测
       返回值:
          rs  列表:
              准备就绪的IO
          ws  列表:
              准备就绪的IO
          xs  列表
              准备就绪的IO
     
    * 在处理IO时不要形成死循环会让一个客户端单独占有服务端
     
    IO多路复兴形成一种可以同时处理多个IO的效果效率较高
     
     
    位运算:
       按照二进制位进行位运算操作
       & 按为与   |  按位或   ^  按位异或
     
       << 左异    >>右移
     
       11  1011
       14  1110
     
       &   1010  有0得0
        |    1111  有1得1
       ^    0101  相同为0不同为1
      
       11 << 2  == 44   右侧补零(乘2乘2次)
       14 >> 2  == 3    挤掉右侧的数字(地板除2除2次)
    使用:
        1.在低层硬件操作寄存器
        2.做标志位的过滤
     
     
     
    poll方法实现IO多路复用:
       1.创建poll对象:
           p = select.poll
       2.注册关注的IO:
           p.register(s, POLLIN | PLLERR)
           不关注:
              p.unregister(s)
           事件类别:
              POLLIN  POLLOUT  POLLERR  POLLHUP   POLLPRI
                rlist           wlist          xlist          断开       紧急处理 
       3.监控IO:
           events = p.poll()
           功能:监控关注的IO事件
           返回值:
               返回发生IO事件
       events是一个列表[(fileno, evnet), (), ()....]
       每个就绪IO对应一个元组描述符就绪事件
               IO地图{s.fileno():s}
       4.处理IO事件
     
    poll方法实现IO多路复用:
    from socket import * 
    from select import *
    
    s = socket()
    s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    s.bind(('0.0.0.0',8888))
    s.listen(5)
    
    #创建poll对象
    p = poll()
    
    #创建地图
    fdmap = {s.fileno():s}
    
    #添加关注
    p.register(s,POLLIN | POLLERR)
    
    while True:
        #进行IO监控
        #[(fileno,evnet),...]
        events = p.poll()
        for fd,event in events:
            if fd == s.fileno():
                #从地图中找到fd对应的对象
                c,addr = fdmap[fd].accept()
                print("Connect from",addr)
                #注册新的IO 维护地图
                p.register(c,POLLIN)
                fdmap[c.fileno()] = c 
            else:
                data = fdmap[fd].recv(1024)
                if not data:
                    p.unregister(fd) #从关注移除
                    fdmap[fd].close()
                    del fdmap[fd]  #从地图删除
                else:
                    print(data.decode())
                    fdmap[fd].send('收到了'.encode())
     
     
    select IO多路复用服务器端:
     
    from socket import *
    from select import select
    # 创建套接字
    s = socket()
    # 设置端口重用
    s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    # 绑定地址
    s.bind(('0.0.0.0', 8888))
    # 设置队列
    s.listen(5)
    # 注册监听套接字
    rlist = [s]
    wlist = []
    xlist = [s]
    
    while True:
        print("等待IO发生")
        rs, ws, xs = select(rlist, wlist, xlist)
        # 循环遍历rs准备就绪列表
        for r in rs:
            # 如果有新的客户端链接请求
            if r is s:
                # 链接客户端并返回客户端套接字
                connfd, addr = r.accept()  # r==s 原套接字
                print("Connect from", addr)
                # 将绑定客户端套接字加入监听列表
                rlist.append(connfd)
            # 表示客户端连接套接字准备就绪
            else:
                # 如果是客户端套接字发送数据 则接收
                data = r.recv(1024)
                if not data:  # 如果客户端断开链接
                    # 从关注列表移除connfd
                    rlist.remove(r)
                    r.close()  # 关闭套接字
                else:
                    print("Receive:", data.decode())
                    # 讲客户端套接字放入wlist
                    wlist.append(r)
                    # wlist列表会直接返回
        # 循环遍历ws准备就绪列表
        for w in ws:
            # 消息回复
            w.send("这是一条回复消息".encode())
            # 删除并取消监听已处理消息
            wlist.remove(w)
        # xs列表:待处理异常
        for x in xs:
            if x is s:
                s.close()


     
     
     
     
    sys.stdin
     
     
     
     
     
    客户端:
     

    from socket import *
    
    #  创建套接字
    sockfd = socket()
    
    #  发起连接
    sockfd.connect(('127.0.0.1', 8888))
    
    while True:
        #  消息收发
        msg = input("Msg>>")
        if not msg:
            break
        sockfd.sendall(msg.encode())
        data = sockfd.recv(1024)
        print(data.decode())
    
    sockfd.close()

    应用 :
    select服务端,同时关注客户端连接
    客户端发送和终端输入。将客户端发送和终端输入的内容全都写入到一个文件中
     

    # myserver.py
    # 应用 :
    # select服务端,同时关注客户端连接
    # 客户端发送和终端输入。将客户端发送和终端输入的内容全都写入到一个文件中
    
    from socket import *
    from select import *
    from sys import stdin
    
    sock = socket()
    sock.getsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    sock.bind(("127.0.0.1", 6666))
    sock.listen(5)
    rlist = [sock, stdin]
    wlist = []
    xlist = []
    file = open("selectdemo.txt", "w+b")
    while True:
        rs, ws, xs, = select(rlist, wlist, xlist)
        for r in rs:
            if r == sock:
                connfd, addr = r.accept()
                print("已连接......")
                rlist.append(connfd)
            elif r == stdin:
                data = stdin.readline()
                file.write(data.encode())
                file.flush()
            else:
                data = r.recv(4096)
                if not data:
                    rlist.remove(r)
                    r.close()
                else:
                    file.write(data + "
    ".encode())
                    file.flush()
                    print("已经接收内容并写如select>>>.txt文件内
     内容:", data.decode())
                    r.send("接收成功!".encode())
    file.close()


    
    
    from socket import *
    
    #  创建套接字
    sockfd = socket()
    
    #  发起连接
    sockfd.connect(('127.0.0.1', 6666))
    
    while True:
        #  消息收发
        msg = input("Msg>>")
        if not msg:
            break
        sockfd.sendall(msg.encode())
        data = sockfd.recv(1024)
        print(data.decode())
    
    sockfd.close()


     
     
     
  • 相关阅读:
    【shell】sed指定追加模式空间的次数
    【shell】sed后向引用替换文本
    【c++】一道关于继承和析构的笔试题
    【curl】cookie的分隔符
    【shell】grep使用正则表达式
    【leetcode】Remove Duplicates from Sorted Array
    【shell】awk格式对齐文本
    【shell】sed处理多行合并
    【leetcode】Permutations
    BWSAP BW Performance Tuning URLS LIST
  • 原文地址:https://www.cnblogs.com/ParisGabriel/p/9446617.html
Copyright © 2020-2023  润新知