并发网络周测题解释版

并发网络周测题

【一】理论篇

1.简述OSI七层协议

  • OSI七层协议(Open Systems Interconnection)是一种用于计算机网络通信的参考模型。
  • 该模型将网络通信过程分解为七个不同的层次,每个层次负责特定的功能和任务,这有助于网络设备和应用程序之间的协作和互操作性。

【1】物理层(Physical Layer):

  • 负责传输数据的物理介质,例如电缆、光纤等。
  • 它处理位与字节之间的传输,提供了关于数据的物理传输方式。
  • 负责通过物理介质传输数据帧,并提供错误检测和纠正,以确保数据在相邻节点之间的可靠传输。

【3】网络层(Network Layer):

  • 负责对数据进行路由和寻址,使数据能够跨越不同的网络节点进行传输。

【4】传输层(Transport Layer):

  • 负责在通信终端之间建立、管理和终止数据传输的连接。
  • 它提供端到端的可靠传输,并进行流量控制和拥塞控制。

【5】会话层(Session Layer):

  • 负责建立、管理和终止会话(或连接)中的通信会话。
  • 它提供会话控制和同步功能,确保应用程序之间的正确通信。

【6】表示层(Presentation Layer):

  • 负责数据的格式化、加密和压缩,以确保不同系统上的应用程序能够正确解释和处理传输的数据。

【7】应用层(Application Layer):

  • 该层为用户提供网络服务和应用程序接口,包括电子邮件、文件传输、远程登录等。
  • 它是最靠近用户的一层,直接与用户进行交互。

2.什么是C/S和B/S架构?

  • C/S架构(Client/Server Architecture)和B/S架构(Browser/Server Architecture)是两种常见的软件架构模式,用于描述客户端和服务器之间的关系。

【1】C/S架构

  • 是指客户端和服务器通过网络进行通信和交互的一种模式。
  • 在这种架构中,客户端应用程序通过网络连接到服务器应用程序,并向服务器发送请求,服务器接收并处理请求,并将结果返回给客户端。
  • 客户端负责提供用户界面和用户输入,而服务器则负责处理业务逻辑和存储数据。
  • 这种架构可以实现分布式计算和相对较高的性能。

【2】B/S架构

  • 是指基于浏览器和服务器之间的通信和交互的一种模式。
  • 在这种架构中,客户端使用Web浏览器作为用户界面,在浏览器中向服务器发送HTTP请求,并接收服务器返回的HTML页面或Web应用程序。
  • 服务器负责处理请求并生成动态的Web内容,然后将其传输到客户端进行展示。
  • B/S架构具有跨平台和易于维护的优势,用户只需拥有一个浏览器即可使用各种功能。

【3】区别:

  • 在C/S架构中,客户端和服务器是独立的应用程序,客户端需要安装特定的客户端软件,而B/S架构中,客户端仅需要一个浏览器即可。
  • C/S架构通常用于需要较高性能和复杂业务逻辑的应用程序,而B/S架构适用于简化部署和维护的应用程序。
  • 在C/S架构中,客户端和服务器之间的通信比较复杂,涉及底层协议和网络编程,而B/S架构中的通信是基于HTTP协议的,更简单和统一。
  • C/S架构通常适用于局域网环境,而B/S架构可以通过互联网进行远程访问。

3.简述TCP三次握⼿、四次挥⼿的流程。

  • TCP(传输控制协议)是一种面向连接的可靠传输协议
    • 建立和终止连接时使用了三次握手和四次挥手的流程。

【1】三次握手(Three-way Handshake):

  • TCP三次握手:
    • 客户端发送连接请求
    • 服务器回复确认并同意连接
    • 客户端再次确认连接,完成连接建立。
  • 第一步:
    • 客户端发送一个SYN(同步)包到服务器,并将自己的初始序列号随机选择。
  • 第二步:
    • 服务器接收到客户端的SYN包后,回复一个SYN和ACK(确认)包作为应答,确认号为客户端的初始序列号+1,并且选择自己的初始序列号。
  • 第三步:
    • 客户端收到服务器的SYN/ACK包后,向服务器发送一个ACK包,确认号为服务器的初始序列号+1。至此,连接建立,双方可以开始进行数据传输。

【2】四次挥手(Four-way Handshake):

  • TCP四次挥手:
    • 客户端请求关闭连接
    • 服务器回复确认,并通知客户端可以关闭连接
    • 服务器关闭连接
    • 客户端回复确认后关闭连接。
  • 第一步:
    • 当客户端确定不再发送数据时,发送一个FIN(结束)包,进入关闭等待状态(FIN_WAIT_1)。
  • 第二步:
    • 服务器接收到客户端的FIN包后,发送一个ACK包作为回应,确认号为客户端的序列号加1,并进入关闭等待状态(CLOSE_WAIT)。
  • 第三步:
    • 当服务器也不再发送数据时,向客户端发送一个FIN包,进入最后确认等待状态(LAST_ACK)。
  • 第四步:
    • 客户端收到服务器的FIN包后,发送一个ACK包作为回应,确认号为服务器的序列号加1。服务器收到ACK包后,进入关闭状态(CLOSED),连接关闭。

4.TCP和UDP的区别?为何基于tcp协议的通信⽐基于udp协议的通信更可靠?

TCP(传输控制协议)和UDP(用户数据报协议)是两种常见的传输层协议

【1】可靠性:

  • TCP提供可靠的数据传输,通过实现数据的分段、序号、确认和重传机制来确保数据的可靠性。
  • UDP则是一种无连接的协议,不提供可靠性保证,数据包的发送和接收没有确认和重传机制,可能导致丢包或乱序。

【2】连接方式:

  • TCP是面向连接的协议,通信前需要建立连接,通过三次握手来进行连接的建立,并且在通信结束后需要进行四次挥手来关闭连接。
  • UDP则是无连接的协议,每个数据包都是独立的,不需要事先建立连接,发送和接收之间没有复杂的握手和挥手过程。

【3】速度和效率:

  • UDP比TCP更快并且效率更高。
    • 因为TCP需要进行连接的建立和关闭,以及数据的确认和重传等机制,这些额外的开销会影响传输速度。
    • 而UDP没有这些额外的开销,可以更快地传输数据,并且由于无连接的特性,也能够支持广播和多播的应用场景。

【4】总结

  • TCP是可靠的、面向连接的协议,提供数据的分段、序号、确认和重传等机制;
  • UDP是不可靠的、无连接的协议,没有确认和重传机制。
  • 基于TCP的通信更可靠,但速度较慢;
  • 而基于UDP的通信速度快但不可靠。
  • 基于TCP协议的通信相比基于UDP协议的通信更可靠的原因是TCP提供了可靠性保证的机制,如数据分段、序号、确认和重传等,可以保证数据的完整性和正确性。
  • UDP则更适用于一些对可靠性要求不高但速度和效率要求较高的应用场景,如音频和视频传输。

5.什么是socket?简述基于tcp协议的套接字通信流程

  • Socket是计算机网络通信的接口,可实现不同主机之间的数据传输。
  • 基于TCP协议的套接字通信流程包括:
    • 服务端创建并监听连接请求;
    • 客户端建立连接;
    • 双方通过套接字进行数据的发送和接收;
    • 传输过程中TCP会进行重传;
    • 双方都可以关闭连接。
  • 这种通信方式可实现可靠、有序的数据传输。

【1】Socket(套接字)

  • Socket(套接字)是计算机网络中用于实现不同主机之间通信的一种机制。
  • 它可以被看作是网络通信的接口,通过它可以进行建立、传输和接收数据的操作。

【2】基于TCP协议的套接字通信流程如下:

  • 服务端创建套接字并绑定到一个端口上,然后开始监听连接请求。
  • 客户端创建一个套接字,并与服务端指定的IP地址和端口号建立连接。
  • 一旦客户端连接成功,服务端接受连接请求,并为该连接创建一个新的套接字来处理与该客户端的通信。
  • 客户端与服务端之间可以通过各自的套接字进行数据的发送和接收。数据在发送时会被分成多个报文段进行传输。
  • 当一方完成数据的发送,它会发送一个结束标记给另一方,表示数据传输完毕。
  • 数据传输过程中,如果发生错误或丢包,TCP协议会进行重传以保证数据的可靠传输。
  • 通信完成后,需要关闭连接,释放资源。双方都可以调用close()函数来关闭相应的套接字。

6.简述进程、线程、协程的区别

  • 进程是操作系统的执行单位
    • 线程是进程内的执行单位
      • 而协程是程序控制的执行单位。
  • 进程之间相互独立,切换开销较大;
    • 线程共享进程的资源,切换开销较小;
      • 协程在同一个线程内切换执行,切换开销极小。

【1】进程(Process):

  • 进程是操作系统中的一个独立执行单元,具有独立的地址空间和资源。
  • 每个进程都拥有自己的代码、数据和堆栈,并且可以通过进程间通信机制进行通信。
  • 不同进程之间相互独立,各自拥有自己的资源和状态,进程间切换开销较大。

【2】线程(Thread):

  • 线程是在进程内部运行的一个独立执行单元,与同一进程中的其他线程共享代码和数据,拥有独立的堆栈。
  • 多线程可以实现并发执行,线程间通信较为方便。
  • 线程可以看作是轻量级的进程,切换开销较小。

【3】协程(Coroutine):

  • 协程是一种用户态的轻量级线程,由程序控制调度,可以在同一个线程中切换执行。
  • 不同于线程的抢占式调度,协程是协作式调度,需要主动让出执行权。
  • 协程间切换开销极小,适用于高并发、I/O密集型的任务。

7.什么是GIL锁?

GIL锁是一种用于保护共享资源的机制,在某些解释型语言中实现。

它通过限制同一时间只有一个线程执行解释器的字节码来避免数据不一致问题。

在多核处理器上会成为性能瓶颈,因为只有持有GIL锁的线程才能执行字节码,其他线程需要等待。

因此,在需要充分利用多核处理器的并发任务中,需要考虑其他并发机制。

【1】什么是GIL锁

  • GIL锁(全局解释器锁,Global Interpreter Lock)是一种用于保护共享资源(例如内存管理)的机制,它存在于某些解释型语言(如CPython)的实现中。

【2】GIL锁的作用

  • 在同一时间只允许一个线程执行解释器的字节码,防止多个线程同时修改共享资源而导致数据不一致的问题。
  • GIL锁的设计主要为了简化解释器的实现,并且在单核处理器上可以提供良好的性能。
  • 然而,在多核处理器上,GIL锁可能成为CPU利用率低下的瓶颈,因为只有拥有GIL锁的线程才能真正执行解释器的字节码,其他线程只能等待。
  • 因此,在需要充分利用多核处理器的并发任务中,使用多进程、多线程或其他非受GIL锁限制的并发机制可能更加合适。

8.进程之间如何进⾏通信?

进程间通信有多种方式,包括管道、共享内存、信号量、消息队列和套接字。

【1】管道(Pipe):

管道是一种半双工的通信机制,可以在父进程和子进程之间传递数据。其中一个进程写入管道,另一个进程从管道中读取。

【2】共享内存(Shared Memory):

共享内存是指多个进程共享同一块物理内存空间,进程可以直接读写这块共享内存来进行通信。需要注意的是,在使用共享内存时需要考虑同步问题,以确保数据的一致性。

【3】信号量(Semaphore):

信号量用于进程间的同步和互斥操作。通过对信号量的操作,进程可以等待或释放资源,实现进程之间的同步。信号量也可以用于进程间的互斥,防止多个进程同时访问共享资源。

【4】消息队列(Message Queue):

消息队列是一种通过消息传递进行进程间通信的机制。进程可以向消息队列发送消息,并可以从消息队列中接收消息。

【5】套接字(Socket):

套接字是网络编程中常用的通信方式,它可以在不同的主机间进行进程间通信。进程可以通过套接字建立网络连接,并进行数据的发送和接收。

9.什么是并发和并⾏?

并发是指多个任务交替进行,每个任务都有进展;

并行是指多个任务同时执行,每个任务在独立的处理器上执行。

【1】并发

  • 指的是系统中同时执行多个独立的任务或操作的能力。
  • 在并发中,任务可以交替进行,并且每个任务都能够作出进展,但不一定是同时执行的。
  • 并发通常与多线程编程相关,通过多个线程来实现同时执行多个任务,提高系统的吞吐量和响应性。

【2】并行

  • 指系统中同时执行多个任务或操作的能力,每个任务在不同的处理器或计算单元上同时进行。
  • 在并行中,每个任务都能够在独立的处理器上实时执行,实现真正的同时执行。
  • 并行通常需要硬件支持,如多核处理器、分布式系统等。

10.⽣产者消费者模型应⽤场景?

生产者消费者模型适用于需要平衡生产和消费速度、缓冲区管理、任务调度、数据处理管道和事件驱动等场景,能提高系统效率和性能。

【1】缓冲区管理:

  • 当生产者生成数据的速度快于消费者处理数据的速度时,使用生产者消费者模型可以通过缓冲区来平衡两者之间的速度差异。
  • 生产者将数据放入缓冲区,消费者从缓冲区中取出数据进行处理。

【2】任务调度:

  • 在任务调度的场景中,任务生成者将任务提交给任务队列,任务消费者从队列中获取任务进行处理。
  • 通过生产者消费者模型可以实现任务的异步执行、任务的调度和分配。

【3】数据处理管道:

  • 涉及多个阶段的数据处理通常可以使用生产者消费者模型。
  • 每个阶段作为一个独立的消费者,负责处理上一阶段生产的数据,并将处理结果传递给下一阶段的生产者,形成一个数据处理管道。
  • 这样可以提高系统的吞吐量和并行性。

[4]事件驱动系统:

  • 在事件驱动系统中,事件的产生者将事件发布到事件队列中,事件的消费者从队列中获取事件进行处理。
  • 通过生产者消费者模型可以实现事件的异步处理,解耦事件的产生和处理逻辑。

11.解释⼀下什么是锁,有哪⼏种锁?

锁是一种并发编程中的机制,用于控制对共享资源的访问。

常见的锁包括表锁、行锁、进程锁、线程锁、互斥锁和GIL锁。

它们可以确保数据的一致性和安全性,但也需要根据具体需求选择合适的锁机制。

死锁是一种可能出现的问题,需要避免资源循环依赖来预防。

【1】锁

  • 锁是一种并发编程中的机制,用于控制对共享资源的访问。
  • 它可以确保在某个线程或进程访问共享资源的时候,其他线程或进程不能同时访问或修改该资源,以维护数据的一致性和线程安全性。

【2】表锁(Table Lock):

  • 在数据库中,表锁是一种粗粒度的锁,对整个表进行加锁,当一个操作需要修改表中的任意行时,需要获得表级锁。
  • 它保证了操作的原子性和隔离性,但也带来了并发性的限制。

【3】行锁(Row Lock):

  • 与表锁相反,行锁是一种细粒度的锁,在数据库中对每一行数据进行加锁。
  • 它允许多个事务同时读取不同的行,提高了并发性能。但在写操作中,只有获取修改行的行锁后才能进行修改,其他事务要等待该行锁释放。

【4】进程锁(Process Lock):

  • 进程锁是一种操作系统级别的锁,用于对进程间的共享资源进行保护。
  • 进程锁可以防止多个进程同时访问或修改共享资源,保证了数据的一致性和安全性。

【5】线程锁(Thread Lock):

  • 线程锁是一种线程级别的锁,用于控制对共享资源的访问。
  • 同一线程在获取到线程锁后,可以进入临界区进行操作,其他线程需要等待该线程释放锁后才能进入。
  • 通过线程锁,可以避免多个线程同时修改共享资源而引发的数据冲突。

【6】互斥锁(Mutex Lock):

  • 互斥锁是一种常见的锁机制,同一时间只允许一个线程持有该锁。
  • 当某个线程获取到互斥锁后,其他线程需要等待该锁的释放。
  • 互斥锁保证了临界区资源的互斥访问,防止并发访问引发的数据不一致性。

【6】GIL锁(Global Interpreter Lock):

  • GIL锁是在Python解释器中使用的锁机制。
  • 由于Python的内存管理机制不是线程安全的,为了保证线程间数据的一致性,Python在解释器级别引入了GIL锁。
  • GIL锁会确保同一时间只有一个线程执行Python字节码,从而保证线程安全。
  • 但也因此,在CPU密集型任务中多线程并不能完全发挥多核处理器的优势。

【7】死锁(Deadlock):

  • 死锁是在并发编程中可能出现的一种问题,指两个或多个进程或线程相互等待对方释放资源而无法继续执行的情况。
  • 死锁可能导致系统无法正常运行,需要通过合理的设计和避免资源循环依赖来预防。

12.线程是并发还是并⾏,进程是并发还是并⾏?

线程是并发的,进程可以是并发的或者是并行的。

  • 并发是指多个任务同时存在,并且能够在一段时间内交替执行。

    • 在并发模式下,多个线程共享同一个进程的资源。
    • 每个线程可以独立执行不同的任务,但它们共享进程内的内存空间和其他资源。
  • 并行是指多个任务同时执行,其中每个任务都具有自己的独立处理器或者计算核心。

    • 在并行模式下,多个进程可以同时执行不同的任务,每个进程拥有独立的内存空间和其他资源。
  • 总结起来,线程是并发的,而进程可以是并发的或者是并行的

13.有了GIL锁,为什么还要互斥锁?

尽管GIL确保了解释器级别单线程的安全性,但互斥锁仍然是必需的。

因为GIL无法保护扩展模块、I/O操作等,而且互斥锁还可以实现更细粒度的同步控制和提高多核系统的性能。

所以,在多线程编程中同时需要使用GIL和互斥锁来确保线程安全和共享资源的正确访问。

【1】GIL(全局解释器锁)

  • 是一种在CPython中使用的机制,用于确保在解释器级别上同一时间只有一个线程执行Python字节码。
  • 虽然GIL能够确保线程安全,但它也限制了多线程并行执行Python代码的能力。

【2】互斥锁(Mutex)

  • 是一种同步机制,用于防止多个线程同时访问共享资源,从而避免数据竞争和不一致的结果。
  • 即使使用了GIL,仍然需要互斥锁来保护临界区,因为在多线程环境下,访问共享资源可能存在竞争条件(Race Condition),导致数据的不正确操作或不一致状态。

【3】需要互斥锁的几个主要原因:

  • GIL只是确保解释器级别单线程的安全性,对于扩展模块、I/O操作等并不能提供保护。

    • 因此,在多线程使用这些扩展模块或进行I/O操作时,仍然需要互斥锁来保护共享资源的访问。
  • 互斥锁可以用于实现更细粒度的同步控制。

    • 比如,当需要实现一些特定的同步策略
      • 例如生产者-消费者问题中的缓冲区限制

      • 或者需要确保某个操作的原子性时, 使用互斥锁会更加灵活。

  • 在多核或多处理器系统中,使用多线程并行执行计算密集型任务时,GIL会限制多线程的性能提升。

    • 通过使用互斥锁,可以避免对共享资源的并发访问,从而充分利用多核或多处理器的优势,提高程序的并行性和性能。

【4】总结

  • 虽然GIL提供了一定程度的线程安全性,但在多线程编程中仍然需要使用互斥锁来保护共享资源和实现细粒度的同步控制。
  • 互斥锁可以确保数据的完整性、一致性和线程之间的安全交互,并且在一些特定场景下能够提供更好的灵活性和性能。

【二】代码实战篇

1.写⼀个服务端和客户端,加上通信和链接循环,互相发送消息.

【1】客户端

import socket

def run_client():
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_host = '127.0.0.1'  # 服务器IP地址
    server_port = 8888  # 服务器端口号

    try:
        client_socket.connect((server_host, server_port))
        print('成功连接到服务器')
    except ConnectionRefusedError:
        print('无法连接到服务器')
        return

    while True:
        message = input('请输入要发送给服务器的消息:')
        client_socket.send(message.encode())  # 发送消息给服务器

        data = client_socket.recv(1024)  # 接收来自服务器的数据
        if not data:
            break
        print('收到来自服务器的消息:', data.decode())

    client_socket.close()

if __name__ == '__main__':
    run_client()

【2】服务端

import socket

def run_server():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_host = '127.0.0.1'  # 服务器IP地址
    server_port = 8888  # 服务器端口号
    server_socket.bind((server_host, server_port))
    server_socket.listen(1)

    print('等待客户端连接...')
    client_socket, client_address = server_socket.accept()
    print('与客户端建立连接:', client_address)

    while True:
        data = client_socket.recv(1024)  # 接收来自客户端的数据
        if not data:
            break
        print('收到来自客户端的消息:', data.decode())

        message = input('请输入要发送给客户端的消息:')
        client_socket.send(message.encode())  # 发送消息给客户端

    client_socket.close()
    server_socket.close()

if __name__ == '__main__':
    run_server()

2.写⼀个基于TCP协议的服务端和客户端⾼并发程序.

# 普通要求:服务端使⽤多进程实现

【1】服务端

# -*-coding: Utf-8 -*-
# @File : 服务端 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/7/8
import socket
from multiprocessing import Process


def handle_client(client_socket):
    while True:
        data = client_socket.recv(1024)
        if not data:
            break
        print('收到来自客户端的消息:', data.decode())
        client_socket.send(data)

    client_socket.close()


def run_server():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_host = '127.0.0.1'  # 服务器IP地址
    server_port = 8888  # 服务器端口号
    server_socket.bind((server_host, server_port))
    server_socket.listen(5)

    print('等待客户端连接...')

    while True:
        client_socket, client_address = server_socket.accept()
        print('与客户端建立连接:', client_address)

        p = Process(target=handle_client, args=(client_socket,))
        p.start()

    server_socket.close()


if __name__ == '__main__':
    run_server()

【2】客户端

# -*-coding: Utf-8 -*-
# @File : 客户端 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/7/8
import socket


def run_client():
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_host = '127.0.0.1'  # 服务器IP地址
    server_port = 8888  # 服务器端口号

    try:
        client_socket.connect((server_host, server_port))
        print('成功连接到服务器')
    except ConnectionRefusedError:
        print('无法连接到服务器')
        return

    while True:
        message = input('请输入要发送给服务器的消息:')
        client_socket.send(message.encode())  # 发送消息给服务器

        data = client_socket.recv(1024)  # 接收来自服务器的数据
        if not data:
            break
        print('收到来自服务器的消息:', data.decode())

    client_socket.close()


if __name__ == '__main__':
    run_client()

# 拔⾼要求:服务端使⽤协程使⽤

基于模块 asyncioaiohttp

pip install asyncio
pip install aiohttp

【1】客户端

# -*-coding: Utf-8 -*-
# @File : 客户端 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/7/8
try:
    import asyncio
    import aiohttp
except ImportError:
    print(f'当前程序基于模块 `asyncio` 和 `aiohttp`')
    print(f'''
    ******************安装命令******************
            >>>>: pip install asyncio
            >>>>: pip install aiohttp
    ******************安装命令******************
    ''')


async def run_client():
    reader, writer = await asyncio.open_connection(
        '127.0.0.1', 8866)

    while True:
        message = input('请输入要发送给服务器的消息(q结束):')
        if message == 'q':
            break
        writer.write(message.encode())
        await writer.drain()

        data = await reader.read(1024)
        print('收到来自服务器的消息:', data.decode())

    writer.close()


if __name__ == "__main__":
    asyncio.run(run_client())

【2】服务端

# -*-coding: Utf-8 -*-
# @File : 服务端 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/7/8
try:
    import asyncio
    import aiohttp
except ImportError:
    print(f'当前程序基于模块 `asyncio` 和 `aiohttp`')
    print(f'''
    ******************安装命令******************
            >>>>: pip install asyncio
            >>>>: pip install aiohttp
    ******************安装命令******************
    ''')


async def handle_client(reader, writer):
    while True:
        data = await reader.read(1024)
        if not data:
            break
        message = data.decode()
        print('收到来自客户端的消息:', message)
        writer.write(data)
        await writer.drain()

    writer.close()


async def run_server():
    server = await asyncio.start_server(
        handle_client, '127.0.0.1', 8866)

    addr = server.sockets[0].getsockname()
    print('等待客户端连接:', addr)

    async with server:
        await server.serve_forever()


if __name__ == "__main__":
    asyncio.run(run_server())
posted @ 2023-07-08 11:04  Chimengmeng  阅读(30)  评论(0编辑  收藏  举报
/* */