Python socket网络编程

Python socket网络编程前言:平常我们会接触到两种框架,分别是:b/s架构与c/s架构。Socket属于CS架构,它也称为:套接字,应用程序通常使用套接字通过网络,实现与另一台计算机进行通讯。。

前言:

平常我们会接触到两种架构,分别是:b/s架构与c/s架构

b/s架构(brownser浏览器/server服务器):通过浏览器与服务器进行数据交互,如:购物等网页数据,我们只需要通过浏览器就可以访问了。其实B/S架构也是CS的一种,只不过客户端是浏览器

c/s架构(client客户端/server服务端):通过网络向服务器发送数据请求,然后服务端回应客户端的请求数据。如:平常所使用的软件:QQ、VX都是客户端的形式。

CS架构,例如一个游戏客户端只能与之对应的服务端进行交互。而BS架构,通过浏览器可以访问任何在上面呈现的数据,它们来自不同的服务端

我们这里所学习的Socket就是基于c/s架构,我们可以写出一个服务端与和一个客户端数据交互。

什么是Socket?

Socket也可以称为:套接字,应用程序通常使用套接字通过网络,实现与另一台计算机进行通讯。

socket套接字:集合了OSI模型七层,传输层以下的所有功能

在这里插入图片描述

既然是网络通信,那么我们需要了解网络上的数据传输协议

TCP协议/UDP协议

TCP协议:可靠的数据传输协议,基于字节在网络上通信。当与另一台计算机进行TCP协议传输数据,需要经过3次握手建立连接。
在这里插入图片描述
syn表示发起连接请求,并发送一个序列号(上面使用了x代替)。服务端接收到了以后将用户的请求放入了半链接池,当服务端有空了再去半链接拿到请求,然后ack回应那个请求,需要在客户端发送的序列号基础上加上1,以确定没有错误,且也向客户端发起syn连接请求。然后客户端回应ack表示连接成功。

所谓洪水攻击就是:大量客户端向服务端发送连接请求,被服务端放于半链接池后,待服务端有空时,拿到半链接池的请求时,回应该请求,且向这个客户端也发送syn连接请求,那么此时客户端不会回应服务端。TCP协议会在多尝试几次向客户端发送syn,再次不回应则会抛弃这个请求,但此时也耽误了一些时间。而半链接池也是有上限的,当被这些恶意的请求所占用满后,合法的用户想要向服务端建立连接就会直接失败。

当连接成功后,就可以进行数据交互了。

TCP协议传输数据,如果目标未收到,会尝试发送多次。多次未果才会放弃发送

当数据传输完毕以后,就会进行4次挥手,断开连接。
在这里插入图片描述
为何要4次挥手?当客户端向服务端发送完数据后,请求断开连接。此时服务端被动断开了客户端与服务端之间的连接。但是服务端向客户端的数据还没有发送完毕呢。所以待服务端发送完数据以后,才会主动向客户端发送断开连接请求。

UDP协议:也可以称为不可靠的数据传输协议,向目标发送数据不在乎目标是否收到。

优势在于:在数据传输过程中延迟小、数据传输效率高,适合对可靠性要求不高的应用程序,或者可以保障可靠性的应用程序。

socket()


在这里插入图片描述

TCP协议通信

在Python中,我们需要通过socket模块来创建套接字。

语法格式:

socket.socket([family[, type[, proto]]])
  • family:套接字家族 AF_UNIX(进程间通信)、AF_INET(TCP/UDP通信)
  • type:SOCK_STREAM(数据流,一般是TCP协议)、SOCK_DGRAM(数据包,udp协议)
  • proto:一般不填,默认为0

实例:我们需要创建两个文件,一个服务端、一个客户端

TCP需要提前建立连接才能进行通信,故而先写服务端

服务端

import socket

# 创建套接字对象,AF_INET基于TCP/UDP通信,SOCK_STREAM以数据流的形式传输数据,这里就可以确定是TCP了
server = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)

# server = socket.socket() 等同于上面的写法

# 绑定ip地址和端口,127.0.0.1代表回环地址,只能当前计算机访问
server.bind(('127.0.0.1',8080))

# 建立半链接池,允许存放5个请求
server.listen(5)

# 等待建立连接请求,会返回两个值,一个是连接状态,一个是连接的客户端IP与端口
conn,ip_addr = server.accept()

# 接收客户端传递的数据,只接收1024个字节数据
res = conn.recv(1024)

# 查看一下连接客户端的IP与端口
print(ip_addr)

# 接收到的是Bytes类型,需要解码
print(res.decode('utf-8'))

# 关闭与客户端的连接
conn.close()

# 关闭套接字
server.close()

客户端

import socket

# 创建套接字对象,AF_INET基于TCP/UDP通信,SOCK_STREAM以数据流的形式传输数据,这里就可以确定是TCP了
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)

# 连接服务端
client.connect(('127.0.0.1',8080))

# 向服务端发送数据,需要转换成Bytes类型发送
client.send('Hello'.encode('utf-8'))

# 套接字关闭
client.close()

先运行服务端,再运行客户端

执行结果

# 服务端
('127.0.0.1', 51767)
'Hello'

这里我们的服务端已经可以接收到客户端发送的数据了,那么此时只能输入和接收一次,我们稍加优化,可以持续发送数据。

服务端

import socket

# 创建套接字对象,AF_INET基于TCP/UDP通信,SOCK_STREAM以数据流的形式传输数据,这里就可以确定是TCP了
server = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)

# 绑定ip地址和端口,127.0.0.1代表回环地址,只能当前计算机访问
server.bind(('127.0.0.1',8080))

# 建立半链接池,允许存放5个请求
server.listen(5)

# 等待建立连接请求,会返回两个值,一个是连接会话,一个是连接的客户端IP与端口
conn,ip_addr = server.accept()

while True:
    # 接收客户端传递的数据,不能超过1024字节
    res = conn.recv(1024)

    # 将客户端的数据接收到以后,转换成大写再编码后发送给客户端
    conn.send(res.decode('utf-8').upper().encode('utf-8'))

    # 注意:close不能放在这里面,因为在这里面的话,只能一次后就关闭了

# 关闭与客户端的连接
conn.close()

# 关闭套接字
server.close()

客户端

import socket

# 创建套接字对象,AF_INET基于TCP/UDP通信,SOCK_STREAM以数据流的形式传输数据,这里就可以确定是TCP了
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)

# 连接服务端
client.connect(('127.0.0.1',8080))

while True:
    inp = input('>>>:').strip()
    # 向服务端发送数据,需要转换成Bytes类型发送
    client.send(inp.encode('utf-8'))

    # 接收服务端回应给客户端的数据,不能超过1024字节
    res = client.recv(1024)

    print(res.decode('utf-8'))

# 套接字关闭
client.close()

查看一下执行结果
在这里插入图片描述
到了这里,我们已经实现了持续的数据发送,且接收到了服务端的回应。但有没有发现一个问题,如果客户端直接回车,发送一个空的内容过去,会是什么效果?

原因:

服务端会卡住,因为服务端在等待我们客户端发送数据,但是发送的是一个空内容,此时服务端就会阻塞在原地持续等待。

在这里插入图片描述 在这里插入图片描述

问题解决:在客户端输入后,判断是否为空就可以了

客户端

while True:
    inp = input('>>>:').strip()
    
    # 输入内容不能为空!
    if len(inp) == 0:
        continue
    # 向服务端发送数据,需要转换成Bytes类型发送
    client.send(inp.encode('utf-8'))

    # 接收服务端回应给客户端的数据,不能超过1024字节
    res = client.recv(1024)

    print(res.decode('utf-8'))

执行结果
在这里插入图片描述
但是还会遇到一个问题,就是当客户端主动断开连接时,服务端也会因为没有连接而随之关闭,Window应该会直接产生报错,而Mac | Linux 则是无限循环,且recv接收到的数据为0

Mac | Linux 会产生的效果
在这里插入图片描述

我们需要继续优化的是:当检测到客户端断开连接时,让服务端重新进入等待连接请求的状态。

服务端

while True:
    # 等待建立连接请求,会返回两个值,一个是连接状态,一个是连接的客户端IP与端口
    conn,ip_addr = server.accept()

    while True:

        # 接收客户端传递的数据
        res = conn.recv(1024)
        
        # 当res为0,说明客户端断开了连接,我们中断当前while
        # 因为当前的conn保存的是断开连接客户的信息,既然它走了,就没必要保留了
        if len(res) == 0: 
            break

        # 将客户端的数据接收到以后,转换成大写编码后,再发送给客户端
        conn.send(res.decode('utf-8').upper().encode('utf-8'))

	# 接收一个新的连接
	conn.close()   

上面的优化针对的是客户端断开连接,服务端不会报错的系统

windows可以使用以下优化:

while True:
    # 等待建立连接请求,会返回两个值,一个是连接状态,一个是连接的客户端IP与端口
    conn,ip_addr = server.accept()

    while True:   
        try:
            # 接收客户端传递的数据
            res = conn.recv(1024)
    
            # 将客户端的数据接收到以后,转换成大写编码后,再发送给客户端
            conn.send(res.decode('utf-8').upper().encode('utf-8'))
    
        except Exception:
            break
    # 关闭当前的,接收一个新的连接 
	conn.close()

有时候我们在重启服务端时,会出现端口被占用的情况。这是因为虽然连接已经关闭,但是端口还没来得及释放。
在这里插入图片描述
两种解决方案:

  1. 更换端口。

  2. bind绑定IP之前添加一个参数,来达到端口复用的目的。

import os

server = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)

server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

# 绑定ip地址和端口,127.0.0.1代表回环地址,只能当前计算机访问
server.bind(('127.0.0.1',8080))

UDP协议通信

udp是无连接的,所以发送数据前不需要先建立连接。

我们可以使用多个客户端与服务端进行数据发送,服务端可以逐个回复

udp发送数据采用的是数据报模式,数据会一次性全部发过去,如果未接收到也不会保存,安全性没有保障,但传输速率较快。

服务端

import socket

# 此时后面数据的传输就要选择SOCK_DGRAM,代表UDP形式传输
server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

# 绑定ip与端口
server.bind(('127.0.0.1',8000))

while True:
    # 接收客户端发送的数据,以及发送者的ip以及端口(UDP不需要建立连接)
    data,addr = server.recvfrom(1024)
    print(f'来自{ 
      addr}发送的一条信息:',data.decode('utf-8'))

    send_data = input('回复信息:')

    # 向发送者回复数据
    server.sendto(send_data.encode('utf-8'),addr)

客户端

import socket

client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

server_ip = ('127.0.0.1',8000)

# 不需要与服务端建立连接

while True:
    send_data = input('输入发送的数据:').strip()

	# 不需要判断是否为空,因为udp每次发送都不只是单纯的数据,还有ip和端口信息 

	# 直接向写好的ip和端口发送数据
    client.sendto(send_data.encode('utf-8'),server_ip)
	
	# 接收它返回的数据
    receive_data,addr = client.recvfrom(1024)

    print(f'来自{ 
      addr}的回信:{ 
      receive_data.decode("utf-8")}')

执行结果

在这里插入图片描述
在这里插入图片描述

我们可以使用服务端回复多个客户端信息
客户端2
也可以使用服务端只接收客户端信息

服务端

import socket

# 此时后面数据的传输就要选择SOCK_DGRAM,代表UDP形式传输
server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

server.bind(('127.0.0.1',8000))

print('正在接收数据中....')
while True:
    # 接收客户端发送的数据,以及发送者的ip以及端口(UDP不需要建立连接)
    data,addr = server.recvfrom(1024)
    print(f'来自{ 
      addr}发送的一条信息:',data.decode('utf-8'))

客户端

import socket

client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

server_ip = ('127.0.0.1',8000)

while True:
    send_data = input('输入发送的数据:').strip()

    client.sendto(send_data.encode('utf-8'),server_ip)

执行结果
在这里插入图片描述

配合subprocess模块实现远程执行命令

这里subprocess模块就不过多介绍了,详细可了解:Python 常用内置模块

主要学习它与套接字的搭配使用,通过客户端执行服务端的系统命令

下面使用的是tcp协议进行连接的,当然使用udp或许会更简单些,但这里需要解决一个tcp协议发送数据的遗留问题

服务端

import socket
import subprocess

# 创建套接字
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # SOCK_STREAM表示Tcp协议传输数据
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 重用端口

# 绑定地址
server.bind(('127.0.0.1',8082))

# 监听状态,允许半链接池存在5个连接
server.listen(5)

# 等待建立连接、有建立连接请求过来后,开始Tcp3次握手协议,建立成功
while True:
    conn,ipadd = server.accept()

    while True:
        # 接收建立连接的用户发送数据
        cmds = conn.recv(1024)
        if len(cmds) == 0:
            break
        cmds = subprocess.Popen(cmds.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)

        stdout = cmds.stdout.read() # 获取执行的正确结果
        stderr = cmds.stderr.read() # 获取执行的错误结果
        
        conn.send(stdout+stderr) # 发送给客户端
    # 关闭建立的链接
    conn.close()

# 关闭套接字
server.close()

客户端

import socket

# 创建套接字
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

# 连接服务端
client.connect(('127.0.0.1',8082))

while True:
    cmds = input('>>>:').strip()
    if len(cmds) <= 0:
        print('输入内容不能为空!')
        continue
        
    # 向服务端发送数据
    client.send(cmds.encode('utf-8'))

    # 接收服务端返回数据
    data = client.recv(1024)

    print(data.decode('utf-8'))

# 关闭套接字
client.close()

执行结果,使用客户端输入命令:
在这里插入图片描述

虽然实现了效果,但这里已经出现了问题,那就是我们客户度只接收1024个字节数据,那么如果服务端发送过来的数据超过1024字节。那该怎么办?此时就需要了解一下tcp协议产生的粘包问题,以及如何解决。

粘包问题


粘包指的是:数据全部粘在一起,如果一次性未取完,下一次接着上一次未取完的数据部分接着取

tcp:recv(1024)

udp:recvfrom(1024)

粘包是由tcp产生的,由于tcp是数据流的形式接收,所有发送数据都会粘着一起,如果接收数据超过我们指定的,则会出现粘包现象,那么此时这个数据还会保存在缓冲区,这里也就是recv内,待我们下次再次recv时,取到的就会是上次没有取完的数据。

udp:如果接收数据超过我们指定的,则放弃那些数据。下次再次接收又是全新的数据,所以不会出现粘包现象。

tcp粘包实例:

服务端

import socket

# 创建套接字对象,AF_INET基于TCP/UDP通信,SOCK_STREAM以数据流的形式传输数据,这里就可以确定是TCP了
server = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)


# 绑定ip地址和端口,127.0.0.1代表回环地址,只能当前计算机访问
server.bind(('127.0.0.1',8080))

# 建立半链接池,允许存放5个请求
server.listen(5)

while True:
    # 等待建立连接请求,会返回两个值,一个是连接状态,一个是连接的客户端IP与端口
    conn,ip_addr = server.accept()

    while True:
	   	# 接收客户端传递的数据
	    res = conn.recv(1024)
	
	    if len(res) == 0:
	        break
	
	    # 将客户端的数据接收到以后,转换成大写编码后,再发送给客户端
	    conn.send(res.decode('utf-8').upper().encode('utf-8'))

    # 关闭与客户端的连接
    conn.close()

# 关闭套接字
server.close()

客户端

import socket

# 创建套接字对象,AF_INET基于TCP/UDP通信,SOCK_STREAM以数据流的形式传输数据,这里就可以确定是TCP了
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)

# 连接服务端
client.connect(('127.0.0.1',8080))

while True:
    inp = input('>>>:').strip()
    if len(inp) == 0:
        continue
    # 向服务端发送数据,需要转换成Bytes类型发送
    client.send(inp.encode('utf-8'))

    # 将接收字节数量调整为10个
    res = client.recv(10)

    print(res.decode('utf-8'))

# 套接字关闭
client.close()

执行结果
在这里插入图片描述

可以看到由于上一次未取完的数据,导致下一次取的时候会接着上一次的继续取数据。

可能会有疑问,把接收字节的长度调高一点不就行了。但忽略的是:接收字节也有上限,可能设置超过几万字节就会报错了,所以这是一种治标不治本的方法。

我们要从真正意义上的解决粘包问题。首先需要拿到整个数据的长度,再通过循环不断接收数据,直到取完本次数据为止。

那么如何拿到数据长度呢?send两次发送过去?

# 分2次发送,但结果还是存在一个缓冲区内
send('2')
send('ab')

lens = recv(1) # 需要固定知道第一次send过来的数据长度为多少

由于数据是不定长的,我们这时无法手动指定接收到数据长度。所以我们需要一种能稳定接收到数据长度的方法。

Python提供给了我们一种模块,可以很好利用在这个场景里面。那就是对数据进行封包,不管数据的内容是什么,我们只需要定义一个固定的头部长度作为标识,那么只要这个头部标识对上了,就可以把这个包解开,获取我们想要的数据

import struct

# 将整体数据分为两部分:头部,数据


# 头部作为一个标识(固定长度)

header = struct.pack('i',409632)

# 其中i就是标识,占4个字节

print(len(header))
# 打印结果就是4个字节
# 4

# 我们通过头部解header这个数据包(如果头部对上了),就可以得到里面的数据
headers = struct.unpack('i',header)

print(headers[0]) # 返回的是一个元组
# 409632

我们可以使用这种形式来把数据封装后发送,已经知道封装后的数据只占4个字节,所以解决了我们上面的问题

我们直接修改上面subprocess案例里面的代码

服务端

import socket
import subprocess
import struct

# 创建套接字
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM表示Tcp协议传输数据
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 重用端口

# 绑定地址
server.bind(('127.0.0.1', 8082))

# 监听状态,允许半链接池存在5个连接
server.listen(5)

# 等待建立连接、有建立连接请求过来后,开始Tcp3次握手协议,建立成功
while True:
    conn, ipadd = server.accept()

    while True:
        # 接收建立连接的用户发送数据
        cmds = conn.recv(1024)
        if len(cmds) == 0:
            break
        cmds = subprocess.Popen(cmds.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

        stdout = cmds.stdout.read()  # 获取执行的正确结果
        stderr = cmds.stderr.read()  # 获取执行的错误结果

        # 将数据使用固定长度,封装一下
        header = struct.pack('i', len(stdout) + len(stderr))

        conn.send(header)

        conn.send(stdout + stderr)  # 发送给客户端
    # 关闭建立的链接
    conn.close()

# 关闭套接字
server.close()

客户端

import socket
import struct

# 创建套接字
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 连接服务端
client.connect(('127.0.0.1', 8082))

while True:
    cmds = input('>>>:').strip()
    if len(cmds) <= 0:
        print('输入内容不能为空!')
        continue

    # 向服务端发送数据
    client.send(cmds.encode('utf-8'))

    # 接收到这个固定长度的数据包
    headers = client.recv(4)

    # 解析这个数据包,获取里面的内容,也就是数据长度
    data_len = struct.unpack('i',headers)[0]

    count = 0

    # 不断循环获取缓冲区里面的数据,直到获取完毕为止
    while count < data_len:
        data = client.recv(1024) # 每次都获取1024个字节
        count += len(data)
        print(data.decode('utf-8'))

# 关闭套接字
client.close()

查看一下代码效果
在这里插入图片描述
与上面进行对比,已经达到了我们预期的效果
在这里插入图片描述

来看一下使用udp的话,发送字节超过接收字节的效果是什么样的

服务端

import socket

server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

server.bind(('127.0.0.1',8080))


while True:
    data,addr = server.recvfrom(5) # 只接受5个字节的数据

    server.sendto(data,addr) # 将接收到的字节发送给客户端

客户端

import socket

client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

server_ip_port = ('127.0.0.1',8080)

while True:
    data = input('>>>>:').strip()

    client.sendto(data.encode('utf-8'),server_ip_port)

    print(client.recvfrom(1024)[0].decode('utf-8'))

执行效果
在这里插入图片描述
可以看到,当定义了只接收5个字节的时候,数据超出的将被丢弃,下一次再接收的又是全新的数据。而windows超过接收字节以后则会报错,也就是说,以上的案例用windows执行会报错。

socketserver模块

我们使用udp,开启一个服务端,多个客户端可以同时向它发送数据,至少在表面上可以实现了并发的效果。但是如果真的有成千上万的同时发送数据,那么就会明显看到效率降低。

在上面我们使用的tcp连接一次只能与一个客户端建立连接,而这里我们了解到一种新的模块,可以同时与多个客户端进行连接,实现并发效果。

服务端

import socketserver

class MyRequestHandler(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            try:
                data = self.request.recv(1024)  # 最大接收的字节数
                if len(data) == 0:
                    break
                self.request.send(data.upper())
            except Exception:
                break
        self.request.close()

server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyRequestHandler, bind_and_activate=True)
server.serve_forever()

可以建立多个客户端

客户端

import socket

# 创建套接字对象,AF_INET基于TCP/UDP通信,SOCK_STREAM以数据流的形式传输数据,这里就可以确定是TCP了
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)

# 连接服务端
client.connect(('127.0.0.1',8080))

while True:
    inp = input('>>>:').strip()
    if len(inp) == 0:
        continue
        
    # 向服务端发送数据,需要转换成Bytes类型发送
    client.send(inp.encode('utf-8'))

    res = client.recv(1024)

    print(res.decode('utf-8'))

# 套接字关闭
client.close()

# 套接字关闭
client.close()

执行结果
在这里插入图片描述
在这里插入图片描述
上面的服务端是通过多线程实现,多个客户端可以与服务端进行连接,并同时发送数据,且它们都是基于tcp协议

以及这个模块也提供了UDP通信实现并发效果

服务端

import socketserver

class MyRequestHandler(socketserver.BaseRequestHandler):
    def handle(self):
        data,server = self.request
        server.sendto(data,self.client_address)


server = socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyRequestHandler,bind_and_activate=True)
server.serve_forever()

客户端

import socket

client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

server_ip = ('127.0.0.1',8080)

while True:
    send_data = input('输入发送的数据:').strip()

    client.sendto(send_data.encode('utf-8'),server_ip)

    data,addr = client.recvfrom(1024)

    print(data.decode('utf-8'))

以上就是笔者所了解的socket模块相关内容,如有疑问,请评论区留言!

各位读者大大,see you next time~

技术小白记录学习过程,有错误或不解的地方请指出,如果这篇文章对你有所帮助请点赞 收藏+关注 子夜期待您的关注,谢谢支持!

今天的文章Python socket网络编程分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/28225.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注