zoukankan      html  css  js  c++  java
  • 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()
    
  • 相关阅读:
    字符串替换
    字符串查找
    字符串比较
    字节与字符串相互转换
    1365. How Many Numbers Are Smaller Than the Current Number
    1486. XOR Operation in an Array
    1431. Kids With the Greatest Number of Candies
    1470. Shuffle the Array
    1480. Running Sum of 1d Array
    【STM32H7教程】第56章 STM32H7的DMA2D应用之刷色块,位图和Alpha混合
  • 原文地址:https://www.cnblogs.com/chancey/p/11246688.html
Copyright © 2011-2022 走看看