• 01. 网络编程


    一、网络编程

    1. 模型

    1.1 OSI七层模型

    制定组织

    ISO(国际标准化组织)

    作用

    使网络通信工程的工作流程标准化

    内容

    应用层:提供用户服务,具体功能由应用呈现;

    表示层:数据的压缩、优化、加密;

    会话层:建立用户级的连接,选择适当的传输服务(由软件开发商决定);

    传输层:提供传输服务,进行流量监控;

    网路层:路由选择,网络互联(路由的寻址);

    链路层:进行数据交换,控制具体数据发送;

    物理层:提供数据传输的物理保证、传输介质

    优点

    1. 建立了统一的工作流程;
    2. 各部分功能清晰,各司其职;
    3. 降低耦合度,方便开发

    1.2 四层模型

    即(TCP/IP协议)

    背景:在实际工作当中,七层模型太过细致,难以实践,逐渐演化成实际工作中应用的四层。

    应用层:集中了原来的应用层、表示层、会话层的功能

    传输层

    网络层

    物理链路层

    1.2.1 TCP三次握手

    1.2.2 TCP 四次挥手

    1.2.3 UDP

    UDP传输数据不建立连接,直接发送消息

    1.3 套接字类型

    流式套接字(SOCK_STREAM):以字节流的方式进行数据传输,实现TCP网络传输方案。

    数据报套接字(SOCK_DGRAM):以数据报形式传输数据,实现UDP网络传输方案。

    2. TCP套接字编程

    2.1 服务端流程

    socket() --> bind() --> listen() --> accept() --> recv()/send() --> close()

    (1) 创建套接字

    sockfd = socket.socket(socket_family=AF_INET,socket_type=SOCK_STREAM,proto=0)

    参数:

    socket_family : 网络地址类型(AF_INET ==> ipv4)

    socket_type : 套接字类型(SOCK_STREAM> 流式套接字; SOCK_DGRAM> 数据报)

    proto : 子协议类型,通常为0

    返回值:

    套接字对象

    (2) 绑定地址

    sockfd.bind(addr)

    参数:

    元组(ip,port) ==> ('127.0.0.1',8080)

    (3) 设置监听端口

    sockfd.listen(n)

    功能:将套接字设置为监听套接字,创建监听队列

    参数:监听队列大小(即)

    (4) 等待处理客户端请求

    connfd,addr = sockfd.accept()

    功能:阻塞等待客户端处理请求

    返回值:

    connfd 客户端套接字

    addr 连接的客户端地址

    *阻塞函数:程序运行过程中遇到阻塞函数暂停执行,直到某种条件下才继续执行。

    (5) 收发消息

    data = connfd.recv(buffersize)

    功能:接收客户端消息

    参数: 每次接受消息最大的字符

    返回值:收到的消息内容

    n = connfd.send(data)

    功能:发送消息

    参数:要发送的消息(bytes格式)

    返回值:发送了多少字节

    字符串转字节串 str ==>bytes str.encode()

    字节串转字符串 bytes==>str bytes.decode()

    (5)关闭套接字

    socket.close()

    功能:关闭套接字

    整体代码

    import socket
    
    # 创建套接字
    sock_fd = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # 绑定地址
    sock_fd.bind(('0.0.0.0',8080))
    # 设置监听端口
    sock_fd.listen(5)
    # 阻塞等待处理客户端的连接
    print("Waiting ...")
    conn,addr = sock_fd.accept()
    print("Connect from ",addr[0]) # 客户地址
    # 消息收发
    data = conn.recv(1024)
    print("Receive message:",data.decode())
    
    n = conn.send(b"Hello World !")
    print("Send %d bytes"%n)
    # 关闭连接
    conn.close()
    sock_fd.close()
    

    2.2 客户端流程

    socket() --> connect() --> send/recv --> close()

    (1) 创建套接字

    sockfd = socket.socket()

    只有相同类型套接字才可以通信。

    (2) 请求连接

    sockfd.connect(addr)

    功能:连接服务端

    参数:元组

    (3) 消息收发

    消息的收发须看自己的情况而定。

    (4) 关闭套接字

    sockfd.close()

    整体代码

    from socket import *
    
    # 创建套接字
    socket_fd = socket()
    # 发起连接
    server_addr = ('127.0.0.1',8080)
    socket_fd.connect(server_addr)
    
    # 收发消息
    data = input(">>")
    socket_fd.send(data.encode())
    data = socket_fd.recv(1024)
    print("From server : ",data.decode())
    # 关闭套接字
    socket_fd.close()
    

    当接收数据大小受限时,多余的数据分批接收,这样的情况为粘包

    2.3 TCP套接字传输特点

    1. tcp连接中当一端退出,另一端如果阻塞在recv,则recv会立即返回一个空字符串;

    2. tcp链接中如果另一端已经不存在,再试图使用send向其发送内容时会出现BrokenPipeError(管道破裂);

    3. 网络收发缓冲区

    • 缓冲区有效的协调了消息的收发速度;
    • send、recv实际是向缓冲区发送接收消息,当缓冲区不为空的时候recv就不会阻塞

    实现循环收发消息

    #server.py
    import socket
    
    server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    print("正在等待客户端连接...")
    #waiting client connect ...
    conn,addr = server.accept()
    print('Connect from ',addr[0])
    while True:
     data = conn.recv(1024)
     if not data:
         break
     print('收到%s的消息:%s'%(addr[0],data.decode()))
     n = conn.send("已经收到您的消息".encode())
     print('发送%s字节'%n)
    
    conn.close()
    server.close()
    
    import socket
    
    client = socket.socket() #创建套接字
    server_addr = ('127.0.0.1',8080) #绑定IP及端口
    client.connect(server_addr) # 连接服务端
    while True:
        data = input(">>>")
        client.send(data.encode()) #发送消息
        if not data:
            break
        data = client.recv(1024) #接收数据
        print("来自服务器的消息",data.decode())
    
    client.close()
    

    实现循环连接客户端

    #server.py
    import socket
    
    server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    
    while True:
        print("正在等待客户端连接...")
        #waiting client connect ...
        conn,addr = server.accept()
        print('Connect from ',addr[0])
    
        while True:
            data = conn.recv(1024)
            if not data:
                break
            print('收到%s的消息:%s'%(addr[0],data.decode()))
            n = conn.send("已经收到您的消息".encode())
            print('发送%s字节'%n)
        conn.close()
    #关闭套接字
    server.close()
    
    #client.py
    import socket
    
    client = socket.socket() #创建套接字
    server_addr = ('127.0.0.1',8080) #绑定IP及端口
    client.connect(server_addr) # 连接服务端
    while True:
        data = input(">>>")
        client.send(data.encode()) #发送消息
        if not data:
            break
        data = client.recv(1024) #接收数据
        print("来自服务器的消息",data.decode())
    
    client.close()
    

    2.4 粘包(zhan bao)

    原因

    TCP以字节流方式传输数据,没有消息边界,多次发送的内容如果被一次性的接收就会形成粘包。

    影响

    如果每次发送的内容是需要独立解析的含义,此时沾包会对消息的解析产生影响。

    处理

    1. 人为添加消息边界;
    2. 控制发送速度(发送少量消息适用)

    3. UDP套接字编程

    3.1 服务端流程

    (1) 创建套接字

    socketfd = AF_INET,SOCK_DGRAM

    (2) 绑定地址

    socketfd.bind(addr)

    (3) 收发消息

    data,addr = sockfd.recvfrom (buffersize)

    功能:接收UDP消息

    参数:每次接收多少字节流

    返回值:data-->接收到的消息 addr-->消息发送方地址

    n = sockfd.sendto(data,addr)

    功能:发送UDP消息

    参数:data-->发送的消息(bytes格式) addr-->目标地址

    (4) 关闭套接字

    socketfd.close()

    作用

    1. 释放端口;
    2. 释放内存

    整体代码

    from socket import *
    
    #创建数据报套接字,即创建UDP套接字
    server = socket(AF_INET,SOCK_DGRAM)
    #绑定地址
    server.bind(('127.0.0.1',8080))
    #收发消息
    while True:
        data,addr = server.recvfrom(1024)
        print("收到消息来自%s的消息:%s"%(addr,data.decode()))
        server.sendto('谢谢你的消息'.encode(),addr)
    #关闭套接字
    serve.close()
    

    3.2 客户端流程

    (1) 创建UDP套接字

    client = socket(AF_INET,SOCK_DGRAM)

    (2) 发送接收套接字

    client.sendto(data.encode(),ADDR)

    (3) 关闭套接字

    client.close()

    整体代码

    from socket import *
    #服务端地址
    HOST = '127.0.0.1'
    PORT = 8080
    ADDR = (HOST,PORT)
    #创建套接字
    client = socket(AF_INET,SOCK_DGRAM)
    # 收发消息
    while True:
        data = input(">>")
        if data == 'bye':
            break
        client.sendto(data.encode(),ADDR)
        msg,addr = client.recvfrom(2014)
        print("来自服务器的消息:",msg.decode())
    # 关闭套接字
    client.close()
    

    当接收内容大小受限的时候,多出的包直接丢失,并不会分批接收

    常见问题

    在收发消息的时候,会发现服务端收到的消息有时是前半部分,有时是后半部分,这是因为多个客户端发送消息的时候,如果正好同时发送,而其中一个包大,则服务端就会接收前边的部分。

    2. TCP与UDP的区别

    1. 流式套接字以字节流方式传输数据,数据报套接字以数据报形式传输数据;
    2. TCP套接字会有粘包问题存在,UDP套接字因为有边界而无粘包问题;
    3. TCP套接字保证了消息的完整性,UDP无法保证,会丢包;
    4. TCP套接字依赖listen accept完成连接才能进行数据收发,UDP套接字不需要连接即可收发消息;
    5. TCP 使用send recv收发消息,UDP使用sendto recvfrom;

    二、SOCKET模块

    1. 部分socket模块方法

    import socket
    
    socket.gethostname() #本机获取主机名
    socket.gethostbyname() #通过主机名获取IP地址
    socket.getservbyname() #获取指定服务的端口
    socket.getservbyport() #获取指定端口的服务
    
    socket.inet_aton('192.168.1.1') #将IP转换为字节串,也就是转换为16进制
    socket.inet_ntoa(b'\xc0\xa8\x01\x01') #将字节串转换为IP
    
    socket.htons(5) #网络制式,不同计算机之间通信的一种方式
    socket.ntohs(1280) 
    

    2. 套接字属性

    *号的为需要掌握的内容

    from socket import *
    s = socket()
    s.bind(('127.0.0.1',8888))
    
    print(s.family) #地址类型
    print(s.type) #套接字类型
    
    print(s.getsockname()) #绑定地址
    
    * print(s.getpeername()) #获取连接客户端的IP地址(需要在套接字连接之后使用)
    
    * print(s.fileno()) #获取文件描述符
    
    * s.setsockopt(level,option,value) # 设置套接字选项(参数:level设置选项类别,option具体选项内容,value具体的值)
    s.setsockopt(SOL_SOCKET,SO_REUSEADDR,True) #设置端口立即重用
    
    
    
    

    描述符

    调用计算机的某个接口完成自己的实际需求,而这种操作是基于操作系统的,并非python,所有IO操作都会分配一个证书作为编号,该整数即为这个IO的文件描述符

    特点

    每个IO文件的描述符不会发生重复

    三、广播

    定义

    一端接收,多端发送。

    # server.py
    from socket import *
    client = socket(AF_INET,SOCK_DGRAM)
    client.setsockopt(SOL_SOCKET,SO_BROADCAST,True)
    
    #选择接收地址
    client.bind(('0.0.0.0',8080))
    
    while True:
        msg,addr = client.recvfrom(1024)
        print(msg.decode())
    
    # client.py
    from socket import *
    from time import sleep
    
    # 目标地址
    dest = ('127.0.0.1',8080)
    s = socket(AF_INET,SOCK_DGRAM)
    
    # 设置可以发生和接收广播
    s.setsockopt(SOL_SOCKET,SO_BROADCAST,True)
    
    data = """
    ****************************************
    ****************清明********************
    ******清明时节雨纷纷,路上行人欲断魂******
    ******借问酒家何处有,牧童遥指杏花村******
    ****************************************
    """
    while True:
        sleep(2)
        s.sendto(data.encode(),dest)
    

    四、 HTTP协议

    1. HTTP协议

    即超文本传输协议

    1.1 用途

    网页的传输,数据传输

    1.2 特点

    • 应用层协议;

    • 传输层使用TCP服务;

    • 简单、灵活、无状态;

    • 请求类型多样;

    • 数据格式支持全面

    点击查看HTTP状态码详解

    1.3 请求格式

    GET /sample.jsp HTTP/1.1
    Accept : image/gif.image/jpeg,*/*
    Accept-Language : zh-cn
    Connection : Keep-Alive
    Host : localhost
    User-Agent : Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
    Accept-Encoding : gzip,deflate
    username = jinqiao&password=1234
    

    (1) 请求行

    具体的请求类别和请求内容

    格式:GET / HTTP/1.1分别为(请求类别、请求内容、协议版本)

    请求类别:

    1. GET : 获取网络资源;

    2. POST : 提交一定的信息,得到反馈;

    3. HEAD : 只获取网络资源响应头;

    4. PUT :更新服务器资源;

    5. DELETE:删除服务器资源;

    6. CONNECT:预留协议;

    7. TRACE:测试;

    8. OPTIONS:获取服务器性能信息

    (2) 请求头

    对请求的进一步描述和解释。

    格式:键 : 值

    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36

    声明浏览器标识等......

    (3) 空体

    标准的规定,必须留空

    (4) 请求体

    请求参数或者提交内容

    # 监听8080端口
    from socket import *
    
    s = socket()
    s.bind(('0.0.0.0',8080))
    s.listen(5)
    c,addr = s.accept()
    print("连接到".encode(),addr)
    data = c.recv(4096)
    print(data)
    
    c.close()
    s.close()
    

    1.4 响应格式

    响应行、响应头、空体、响应体

    (1) 响应行

    反馈基本的响应情况

    HTTP/1.1 200 OK (协议版本、响应码、附加信息)

    响应码(详细的响应码信息见跳转链接):

    1xx 表示提示信息,请求被接受

    2xx 表示响应成功

    3xx 表示响应需要进一步处理

    4xx 表示客户端错误

    5xx 服务端错误

    (2) 响应头

    对响应内容的描述(以键值对作为描述)

    Content-Type : text/html #声明网页类型

    (3) 响应体

    协议必须加,无内容

    (4)响应体

    响应的主体内容信息

    1.5 应用

    1.5.1 简单的网页

    # 监听8080端口
    from socket import *
    
    s = socket()
    s.bind(('0.0.0.0',8080))
    s.listen(5)
    c,addr = s.accept()
    print("连接到",addr)
    data = c.recv(4096)
    print(data)
    data = '''
    HTTP/1.1 200 OK
    Content-Type : text/html
    
    Hello Chancey !
    '''
    c.send(data.encode())
    print(data)
    
    c.close()
    s.close()
    

    打开浏览器,打开URL127.0.0.1:8080

    1.5.2 上传文件

    # recv.py
    from socket import *
    
    s = socket()
    s.bind(('0.0.0.0',8809))
    s.listen(5)
    c,addr = s.accept()
    print("来自",addr[0],'的连接')
    
    f = open('demo.png','wb')
    
    while True:
        data = c.recv(1024)
        if not data:
            break
        f.write(data)
    
    f.close()
    c.close()
    s.close()
    
    # send.py
    from socket import *
    
    s = socket()
    s.connect(('127.0.0.1',8809))
    
    f = open('pic.png','rb')
    while True:
        data = f.read(1024)
        if not data:
            break
        s.send(data)
    
    f.close()
    s.close()
    

    1.5.3 前置网页文件

    pass

    2. HTTPS协议

    pass

    五、IO

    即输入输出,在内存中发生数据交换的情况,程序不可缺少的一部分。

    1. 定义

    在内存中存在数据交换的操作都认为是IO操作

    和终端交互:inputprint

    和磁盘交互:readwrite

    和网络交互:recvsend

    分类

    1. IO密集型:在程序中存在大量的IO,而CPU运算较少,消耗CPU资源少,耗时长,效率不高

    2. 计算密集:在程序中存在大量的计算操作,IO行为较少,CPU消耗多,执行速度快

    2. IO模型

    阻塞情况

    1. 因为某种条件没有达到而形成阻塞(accept、input、recv);
    2. 处理IO的时间太长产生的阻塞情况(网络传输、大文件的读写过程);

    2.1 阻塞IO

    默认形态

    定义:在执行操作时,由于不足满某些条件形成的阻塞形态,阻塞IO时IO的默认形态。

    效率:非常低

    逻辑:简单

    2.2 非阻塞IO

    定义:通过修改IO的属性行为,使原本阻塞的IO变为非阻塞的状态。

    2.2.1 设置方法

    1. 设置socket为非阻塞套接字

      sockfd.setblocking(bool)

      功能:设置套接字为非阻塞套接字

      参数:True表示套接字IO阻塞,False表示非阻塞

    2. 设置超时检测

      阻塞等待指定的时间,超时后不再阻塞。

      sockfd.settimeout(sec)

      功能:设置套接字超时时间

      参数:超时时间

    设置非阻塞和设置超时不会同时出现。要设置超时必须是阻塞。

    2.2.1 示例

    from socket import *
    from time import sleep,ctime
    
    #创建一个tcp套接字
    sockfd = socket()
    sockfd.bind(('127.0.0.1',8809))
    sockfd.listen(5)
    #设置非阻塞(True为阻塞、False为非阻塞)
    sockfd.setblocking(False)
    while True:
        print("等待连接......")
        conn,addr = sockfd.accept()
    
    #运行结果
    >>>等待连接......
    Traceback (most recent call last):
      File "D:/project/demo/网络编程/IO/block_io.py", line 12, in <module>
        connfd,addr = sockfd.accept()
      File "C:\Users\Administrator\AppData\Local\Programs\Python\Python36\lib\socket.py", line 205, in accept
        fd, addr = self._accept()
    BlockingIOError: [WinError 10035] 无法立即完成一个非阻止性套接字操作。
        
        
        
        
     
    from socket import *
    from time import sleep,ctime
    
    #创建一个tcp套接字
    sockfd = socket()
    sockfd.bind(('127.0.0.1',8809))
    sockfd.listen(5)
    #设置超时时间
    sockfd.settimeout(3)
    while True:
        print("等待连接......")
        try: #捕获异常,设置异常
            conn,addr = sockfd.accept()
        except BlockingIOError:
            sleep(2)
            print(ctime(),"连接错误")
            continue
        except timeout:
            print("超时连接")
        else:
            print("已连接至",addr[0])
            data = conn.recv(1024)
    #运行之后,在有连接的情况下将不会再等待连接
    

    报错是IO模块错误,所以使用try语句,捕获异常

    2.3 IO多路复用

    定义

    同时监听多个IO事件,当哪个IO事件准备就绪就执行哪个IO,以此形成可以同时处理多个IO的行为,避免一个IO阻塞造成的其他IO无法执行,提高IO执行效率。

    具体方案

    1. select(windows、linux、unix)

    2. poll(linux、unix)

    3. epoll(Linux专属)

    2.3.1 SELECT

    概念

    ra,ws,xs = select(rlist, wlist, xlist[, timeout])

    功能

    监控多个IO时间,阻塞等待IO的发生

    参数

    rlist : 列表(存放关注的等待发生的事件)

    wlist : 列表(存放要主动处理的IO事件)

    xlist : 列表(存放发生异常要处理的事件)

    timeout : 超时时间

    返回值

    rs : 列表 rlist 中准备就绪的IO

    ws : 列表 wlist 中准备就绪的IO

    xs : 列表 xlist 中准备就绪的IO

    select(...)
        select(rlist, wlist, xlist[, timeout]) -> (rlist, wlist, xlist)
        Wait until one or more file descriptors are ready for some kind of I/O.
        等待一个或多个文件描述符准备好进行某种I/O。
    The first three arguments are sequences of file descriptors to be waited for:
        前三个参数是等待的文件描述符序列:
    rlist -- wait until ready for reading
    rlist——等待阅读准备就绪
    wlist -- wait until ready for writing
    wlist——等到准备好写作
    xlist -- wait for an ``exceptional condition''
    xlist——等待“异常情况”
    If only one kind of condition is required, pass [] for the other lists.
    如果只需要一种条件,则为其他列表传递[]。
    A file descriptor is either a socket or file object, or a small integer gotten from a fileno() method call on one of those.
    文件描述符可以是套接字或文件对象,也可以是一个小整数。从其中一个的fileno()方法调用中获取。
    The optional 4th argument specifies a timeout in seconds; it may be a floating point number to specify  ractions of seconds.  If it is absent or None, the call will never time out.
    可选的第4个参数指定以秒为单位的超时;它可能是用于指定秒分数的浮点数。如果它不在或者没有,电话永远不会超时。
    The return value is a tuple of three lists corresponding to the first three arguments; each contains the  ubset of the corresponding file descriptors that are ready.
    返回值是与前三个列表对应的三个列表的元组参数;每个包含相应文件描述符的子集准备好了。
    *** IMPORTANT NOTICE ***
    ***重要通知***
    On Windows, only sockets are supported; on Unix, all file descriptors can be used.
    在Windows上,只支持套接字;在Unix上,所有文件可以使用描述符。
    

    简单使用

    from select import select
    from socket import *
    
    # 创建套接字作为关注的IO
    s = socket()
    s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    s.bind(('0.0.0.0',8888))
    s.listen(5)
    
    #添加到关注列表
    rlist = [s]
    wlist = []
    xlist = []
    
    while True:
        # 监控IO
        rs,ws,xs = select(rlist,wlist,xlist)
        for r in rlist:
            # s 就绪说明有客户端连接
            if r is rs:
                c,addr = r.accept()
                print("连接来自",addr[0])
                # 将客户端加入到关注列表里面
                rlist.append(c)
            # 如果是c就绪则表示对应的客户端发送消息
            else:
                data = r.recv(1024)
                if not data:
                    rlist.remove(r)
                    r.close()
                    continue
                print(data.decode())
                # r.send(b'OK')
                # 当r放进wlist中时希望主动去处理
                wlist.append(r)
        for w in wlist:
            w.send(b'OK')
            wlist.remove(w)
        for x in xlist:
            pass
    

    2.3.2 POLL

    步骤

    (1) p = select.poll()

    功能:创建poll对象,该函数返回一个实例对象

    返回值:poll对象

    (2) p.register(fd,event)

    功能:注册关注的IO

    返回值:

    参数

    fd : 关注的IO

    event : 关注IO事件的类型(读事件、写事件、异常事件)

    # 常用IO事件类型的划分及写法
    POLLIN # 读IO(rlist)
    POLLOUT # 写IO(wlist)
    POLLERR # 异常IO(xlist)
    POLLHUP # 断开连接
    
    # 多个事件类型的写法
    p.register = sockfd(POLLIN | POLLOUT)
    

    (3) p.unregister(fd)

    功能:取消对IO的关注

    参数: IO对象或者IO对象的fileno(文件描述符)

    (4) events = p.pull()

    功能:阻塞等待监控IO事件的发生

    返回值: 就绪的IO事件

    events格式:[ ( files , event ) , ( ) , ( ) , ......]

    需要通过fileno寻找对应的IO对象,以操作IO事件,建立字典作为查找地图。{ fileno : io_obj }

    实例 ①

    from socket import *
    from select import *
    
    # 创建套接字作为监控的IO事件
    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}
    
    # 关注IO事件的维护,取关和关注
    p.register(s,POLLIN | POLLERR) # 关注IO事件
    
    # 循环监控IO
    while True:
        events = p.poll()
        for fd,event in events: # 遍历events,处理IO
            if fd == s.fileno():
                c,addr = fdmap[fd].accept() # 通过键找到值,该值则为IO事件
                print("来自%s的连接"%addr[0])
                # 添加新的关注
                p.register(c,POLLIN | POLLHUP)
                fdmap[c.fileno()] = c
            elif event & POLLHUP:
                print("客户端退出")
                p.unregister(fd)
                fdmap[fd].close()
                del fdmap[fd]
            elif event & POLLIN:
                data = fdmap[fd].recv(1024)
                print(data.decode())
                fdmap[fd].send(b'OK')
    
    

    实例 ②

    '''
    需求说明:
    创建一个服务端、一个客户端 ,在client连接server时记录日志,并且保留所有的连接记录和消息记录
    '''
    from select import select
    from socket import *
    import sys
    from time import ctime
    
    # 日志文件
    f = open('log.txt','a')
    
    s = socket()
    s.bind(('',8888)) # 该处如果为空,则为0.0.0.0
    s.listen(5)
    
    rlist = [s,sys.stdin]
    wlist = []
    xlist = []
    
    while True:
        rs,ws,xs = select(rlist,wlist,xlist)
        for r in rs:
            if r is s:
                c,addr = r.accept()
                rlist.append(c)
            elif r is sys.stdin:
                name = 'Server'
                time = ctime()
                msg = r.readline()
                f.write('%s %s %s \n'%(name,time,msg))
                f.flush() # 清除缓存
            else:
                addr = r.getpeername()
                time = ctime()
                msg = r.recv(1024).decode()
                f.write('%s %s %s \n' % (addr, time, msg))
                f.flush()
    
    f.close()
    s.close()
    

    2.3.3 EPOLL

    使用方法:基本与poll相同

    • 生成对象改为epoll()
    • 将所有事件类型改为epoll事件

    特点:

    1. 效率比poll高;
    2. 可以同时监控io的数量比poll多;
    3. 触发方式比poll更多;
    4. 只能 在Linux下使用

    epoll触发方式

    边缘触发:IO事件就绪之后不做处理,则会跳过该阻塞

    水平触发:在某一个IO准备就绪之后,就会一直在该处阻塞,直到该IO处理完成

    3. STRUTE

    3.1 原理

    将一组简单的数据进行打包,转换为bytes格式发送,或者将一组bytes转换为python数据类型,实现不同的语言之间的互动。

    3.2 接口使用

    (1) st = Struct(fmt)

    功能:生成结构化数据

    参数:定制的数据结构

    要组织的数据:1 b'chancey' 1.75

    fmt : 'i4sf'

    解释:一个整型、四个字节型、一个浮点型

    (2) st.pack(v1,...)

    不定参,可以传多个参数

    功能:将一组数据按照一定格式打包转换

    返回值:bytes字节串

    (3) st.unpack(bytes_data)

    功能:将bytes按照格式解析

    返回值:解析后的数据元组

    In [2]: import struct                    
                                             
    In [3]: st = struct.Struct('i4sf')       
                                             
    In [4]: data = st.pack(1,b'chancey',1.68)
                                             
    In [5]: data                             
    Out[5]: b'\x01\x00\x00\x00chan=\n\xd7?'  
                                             
    In [6]: st.unpack(data)                  
    Out[6]: (1, b'chan', 1.6799999475479126) 
                                             
    In [7]:                                  
    

    (4) struct.pack( fmt , v1 , ... )

    struct.unpack(fmt,bytes)

    说明:使用struct模块直接调用packunpack,第一个参数直接传入fmt

    In [1]: import struct
    
    In [3]: data = struct.pack('7si',b'chancey',18) # 打包
    
    In [5]: struct.unpack('7si',data) # 解包
    Out[5]: (b'chancey', 18)
    
    In [6]:
    

    3.3 实例

    需求:从客户端输入学生ID、姓名、年龄、成绩,打包发送给服务端,服务端将其存入一个数据表中

    
    

    4. 本地套接字

    1. 功能

    本地两个程序之间的通信

    2. 原理

    对一个内存对象进行读写操作,完成两个程序之间的数据交互

    3. 步骤

    (1) 创建本地套接字

    sockfd = socket(AF_UNIX,SOCK_STREAM)

    (2) 绑定套接字文件

    sockfd.bind(file)

    (3) 监听、连接、收发消息

    listenacceptrecv/send

    实例

    # server.py
    from socket import *
    import os
    
    # 本地套接字文件
    sock_file = './sock'
    
    # 判断文件是否存在,存在返回True,反之亦然
    if os.path.exists(sock_file):
        os.remove(sock_file) # 删除文件
    
    # 创建本地套接字
    sockfd = socket(AF_UNIX,SOCK_STREAM)
    # 绑定文件
    sockfd.bind(sock_file)
    sockfd.listen(5)
    
    while True:
        c,addr = sockfd.accept()
        while True:
            data = c.recv(1024)
            if not data:
                break
            print(data.decode())
        c.close()
    sockfd.close()
    
    
    # client.py
    
    from socket import *
    
    # 两边必须使用同一个套接字
    sock_file = './sock'
    
    sockfd = socket(AF_UNIX,SOCK_STREAM)
    sockfd.connect(sock_file)
    
    while True:
        msg = input('>>>')
        if not msg:
            break
        sockfd.send(msg.encode())
    sockfd.close()
    
  • 相关阅读:
    Custom vs. Automation Interface
    ModBus功能码
    Computer telephony integration
    Computersupported telecommunications applications
    Petri网可覆盖性树的构造算法 whl
    petri网学习笔记stochastic petri net分类 whl
    webpy猫腻之session with reloader
    Continuous Integration and Code Review 工具
    python中的操作符重载示例
    Software version rules
  • 原文地址:https://www.cnblogs.com/chancey/p/11246688.html
Copyright © 2020-2023  润新知