wsgi 協議

来源:https://www.cnblogs.com/zuanzuan/archive/2019/01/08/10241006.html
-Advertisement-
Play Games

wsgi 協議 前言 本來沒打算這麼早就學習 wsgi 的,因為想要學習python 是如何處理網路請求的繞不開 wsgi,所以只好先學習一下 wsgi。先對 wsgi 有個印象,到了學習 Django 運行方式以及如何處理網路請求數據的時候就會感覺很順暢了。本文參考 "" 什麼是 WSGI wsg ...


wsgi 協議

前言

本來沒打算這麼早就學習 wsgi 的,因為想要學習python 是如何處理網路請求的繞不開 wsgi,所以只好先學習一下 wsgi。先對 wsgi 有個印象,到了學習 Django 運行方式以及如何處理網路請求數據的時候就會感覺很順暢了。本文參考

什麼是 WSGI

wsgi 的全稱是Web Server Gateway Interface,這是一個規範,描述了 web server 如何與 web application 交互、web application 如何處理請求。該規範的具體描述在 PEP3333。WSGI 既要實現 web server,也要實現 web application。在 Django 中的 app 其實就是 web application,而 web server其實在使用命令行輸入python manage.py runserver或者使用 pycharm 開啟 Django 項目的時候就把runserver當做參數傳給了 manage.py裡面

經過判斷然後執行execute_from_command_line(sys.argv),sys.argv就是 runserver命令,進入該函數,發現執行了utility.execute()函數,進入函數查看源碼:

def execute(self):
    """
    Given the command-line arguments, this figures out which subcommand is
    being run, creates a parser appropriate to that command, and runs it.
    """
    try:
        subcommand = self.argv[1]
    except IndexError:
        subcommand = 'help'  # Display help if no arguments were given.

    # Preprocess options to extract --settings and --pythonpath.
    # These options could affect the commands that are available, so they
    # must be processed early.
    parser = CommandParser(None, usage="%(prog)s subcommand [options] [args]", add_help=False)
    parser.add_argument('--settings')
    parser.add_argument('--pythonpath')
    parser.add_argument('args', nargs='*')  # catch-all
    try:
        options, args = parser.parse_known_args(self.argv[2:])
        handle_default_options(options)
    except CommandError:
        pass  # Ignore any option errors at this point.

    try:
        settings.INSTALLED_APPS
    except ImproperlyConfigured as exc:
        self.settings_exception = exc

    if settings.configured:
        # Start the auto-reloading dev server even if the code is broken.
        # The hardcoded condition is a code smell but we can't rely on a
        # flag on the command class because we haven't located it yet.
        if subcommand == 'runserver' and '--noreload' not in self.argv:
            try:
                autoreload.check_errors(django.setup)()
            except Exception:
                # The exception will be raised later in the child process
                # started by the autoreloader. Pretend it didn't happen by
                # loading an empty list of applications.
                apps.all_models = defaultdict(OrderedDict)
                apps.app_configs = OrderedDict()
                apps.apps_ready = apps.models_ready = apps.ready = True

                # Remove options not compatible with the built-in runserver
                # (e.g. options for the contrib.staticfiles' runserver).
                # Changes here require manually testing as described in
                # #27522.
                _parser = self.fetch_command('runserver').create_parser('django', 'runserver')
                _options, _args = _parser.parse_known_args(self.argv[2:])
                for _arg in _args:
                    self.argv.remove(_arg)

        # In all other cases, django.setup() is required to succeed.
        else:
            django.setup()

    self.autocomplete()

    if subcommand == 'help':
        if '--commands' in args:
            sys.stdout.write(self.main_help_text(commands_only=True) + '\n')
        elif len(options.args) < 1:
            sys.stdout.write(self.main_help_text() + '\n')
        else:
            self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0])
    # Special-cases: We want 'django-admin --version' and
    # 'django-admin --help' to work, for backwards compatibility.
    elif subcommand == 'version' or self.argv[1:] == ['--version']:
        sys.stdout.write(django.get_version() + '\n')
    elif self.argv[1:] in (['--help'], ['-h']):
        sys.stdout.write(self.main_help_text() + '\n')
    else:
        self.fetch_command(subcommand).run_from_argv(self.argv)

源碼太長了。。。我把關鍵地方摳出來:

if settings.configured:
    # Start the auto-reloading dev server even if the code is broken.
    # The hardcoded condition is a code smell but we can't rely on a
    # flag on the command class because we haven't located it yet.
    if subcommand == 'runserver' and '--noreload' not in self.argv:
        try:
            autoreload.check_errors(django.setup)()
        except Exception:
            # The exception will be raised later in the child process
            # started by the autoreloader. Pretend it didn't happen by
            # loading an empty list of applications.
            apps.all_models = defaultdict(OrderedDict)
            apps.app_configs = OrderedDict()
            apps.apps_ready = apps.models_ready = apps.ready = True

            # Remove options not compatible with the built-in runserver
            # (e.g. options for the contrib.staticfiles' runserver).
            # Changes here require manually testing as described in
            # #27522.
            _parser = self.fetch_command('runserver').create_parser('django', 'runserver')
            _options, _args = _parser.parse_known_args(self.argv[2:])
            for _arg in _args:
                self.argv.remove(_arg)

    # In all other cases, django.setup() is required to succeed.
    else:
        django.setup()

這裡也是註釋最多的地方,可以看到有runserver這條命令,然後這裡面在經過一系列的判斷最後要執行最後一行代碼:

self.fetch_command(subcommand).run_from_argv(self.argv)

這行代碼等學習 Django 處理流程的時候在詳細解釋,反正只要知道目前經過這個函數的執行,Django 的 web server 成功運行了。

實現了 WSGI 的模塊/庫有 wsgiref(python 內置,下麵也是用這個來舉例)、werkzeug.serving、twisted.web等。

當前運行在 wsgi 之上的 web 框架有 Bottle、Flask、Django 等。WSGI server 所做的工作僅僅是將客戶端收到的請求傳遞給 WSGI application,然後將 WSGI application 的返回值作為相應傳給客戶端。WSGI application 可以是棧式的,這個棧的中間部分叫做中間件,兩端是必須要實現的 application 和 server。所以對客戶端來說,中間件扮演伺服器;對伺服器來說,中間件扮演客戶端。在 Django 中wsgi 收到的數據用 request對象表示,要傳給客戶端的數據用 Httpresponse對象表示。

搭建一個 wsgi 服務

在上章節說了 python 有個內置的 WSGI 庫叫 wsgiref。

首先看下項目結構:

# templates為模板(HTML)文件夾
# start.py 為項目入口,
# urls.py 為路由配置
# views.py 為具體處理路由邏輯代碼

start 文件

# start.py文件
from wsgiref.simple_server import make_server
from urls import urls

def app(env, response):
    # 在這裡,
    print(env)
    route = env['PATH_INFO']
    print(response)
    
    # 設置狀態碼與響應頭
    response('200 OK', [('Content-type', 'text/html')])
    
    # 設置錯誤處理
    data = urls['/error']()
    
    # 設置路由處理
    if route in urls:
        data = urls[route]()
        
    # 返回二進位響應體
    return [data]

if __name__ == '__main__':
    
    # 創建伺服器對象
    server = make_server('', 8808, app)
    print('服務:http://localhost:8808')
    
    # 服務保持運行狀態
    server.serve_forever()
    
    # WSGI server 是一個 web server,其處理一個 HTTP 請求的邏輯如下:
    # iterable = app(env, response)
    #     for date in iterable:
    #         send data to client

其實這個模塊底層使用了 sockserver 模塊,我前面的博客也有介紹。經過 make_server就成功開啟了wsgi server,然後server_forever()是為了將伺服器持續接收客戶端請求,採用的是輪詢方法,該方法裡面的參數 poll_interval=0.5,採用的是0.5秒輪詢一次,輪詢採用的是 selector學名叫多路復用技術。

urls 文件

# urls.py文件
from views import *
urls = {
    '/index': index, # 函數地址
    '/error': error
}

該文件就是處理路由的,然後將對應的路由映射到相應的邏輯處理函數。

views 文件

# 處理請求的功能函數(處理結果返回的都是頁面 => 功能函數)
# 利用 jinja2來渲染模板,將後臺數據傳給前臺

from jinjia2 import Template

# 處理主頁請求
def index():
    with open('templates/index.html', 'r') as f:
        dt = f.read()
    tem = Template(dt)
    
    # 將後臺數據通過模板渲染功能渲染傳給前臺頁面
    resp = tem.render(name='主頁')
    return resp.encode('utf-8')

# 處理圖標請求
def ico():
    with open('favicon.ico', 'rb') as f:
        dt = f.read()
    return dt

# 處理錯誤請求
def error():
    return b'404'

templates

該文件夾裡面放的偽要返回給前端相關資源,比如index.html

測試

  • index 測試

  • error 測試

WSGI application介面

在上面wsgi 服務中的 app 就是 wsgi 中的 application,該介面應該實現為一個可調用對象,例如函數、方法、類、含__call__方法的實例。這個可調用對象可以接收兩個參數:

  • 一個字典,該字典可以包含了客戶端請求的信息以及其他信息,可以認為是請求上下文,一般叫做 environment(在這裡我取名為 env);
  • 一個用於發送 HTTP 狀態碼與響應頭的回調函數。(具體怎麼回調的還不清楚)

同時,可調用對象的返回值是響應體(response body),響應正文是可迭代的、並包含了多個字元串。(加了中括弧可以減少迭代次數,提高效率)

把上面的 app 代碼拷下來:

def app(env, response):
    # 在這裡,
    print(env)
    route = env['PATH_INFO']
    print(response)
    
    # 設置狀態碼與響應頭
    response('200 OK', [('Content-type', 'text/html')])
    
    # 設置錯誤處理
    data = urls['/error']()
    
    # 設置路由處理
    if route in urls:
        data = urls[route]()
        
    # 返回二進位響應體
    return [data]

當我對服務端發起請求時,會列印出 env,如下:

{'PATH': '/Users/jingxing/Virtualenv/py3-env1/bin:/Users/jingxing/.nvm/versions/node/v4.9.1/bin:/Library/Frameworks/Python.framework/Versions/3.6/bin:/python_study/mongodb/bin://Volumes/python_study/mongodb/bin:/Library/Frameworks/Python.framework/Versions/3.6/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/VMware Fusion.app/Contents/Public:/python_study/Applications/mongodb-osx-x86_64-3.6.3/bin::/usr/local/mysql/bin', 'PS1': '(py3-env1) ', 'VERSIONER_PYTHON_VERSION': '2.7', 'LS_OPTIONS': '--color=auto', 'LOGNAME': 'jingxing', 'XPC_SERVICE_NAME': 'com.jetbrains.pycharm.23248', 'PWD': '/Users/jingxing/django_project/day01', 'PYCHARM_HOSTED': '1', 'NODE_PATH': '/Users/jingxing/.nvm/versions/node/v4.9.1/lib/node_modules', 'PYCHARM_MATPLOTLIB_PORT': '62845', 'PYTHONPATH': '/Users/jingxing/django_project/day01:/Users/jingxing/django_project/day04:/Users/jingxing/django_project/day02:/Users/jingxing/PycharmProjects/youku/youkusecond:/Users/jingxing/django_project/day03:/Applications/PyCharm.app/Contents/helpers/pycharm_matplotlib_backend', 'NVM_CD_FLAGS': '', 'NVM_DIR': '/Users/jingxing/.nvm', 'SHELL': '/bin/bash', 'LSCOLORS': 'CxfxcxdxbxegedabagGxGx', 'PYTHONIOENCODING': 'UTF-8', 'VERSIONER_PYTHON_PREFER_32_BIT': 'no', 'USER': 'jingxing', 'CLICOLOR': 'Yes', 'TMPDIR': '/var/folders/yl/3drd7wf93f90sfkgpc2zg9cr0000gn/T/', 'SSH_AUTH_SOCK': '/private/tmp/com.apple.launchd.ujA3r16JUC/Listeners', 'VIRTUAL_ENV': '/Users/jingxing/Virtualenv/py3-env1', 'XPC_FLAGS': '0x0', 'PYTHONUNBUFFERED': '1', '__CF_USER_TEXT_ENCODING': '0x1F5:0x0:0x0', 'Apple_PubSub_Socket_Render': '/private/tmp/com.apple.launchd.gOrXw3Il2u/Render', 'LC_CTYPE': 'en_US.UTF-8', 'NVM_BIN': '/Users/jingxing/.nvm/versions/node/v4.9.1/bin', 'HOME': '/Users/jingxing', 'SERVER_NAME': 'jingxingdeMacBook-Pro.local', 'GATEWAY_INTERFACE': 'CGI/1.1', 'SERVER_PORT': '8808', 'REMOTE_HOST': '', 'CONTENT_LENGTH': '', 'SCRIPT_NAME': '', 'SERVER_PROTOCOL': 'HTTP/1.1', 'SERVER_SOFTWARE': 'WSGIServer/0.2', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/', 'QUERY_STRING': '', 'REMOTE_ADDR': '127.0.0.1', 'CONTENT_TYPE': 'text/plain', 'HTTP_HOST': '127.0.0.1:8808', 'HTTP_CONNECTION': 'keep-alive', 'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 'HTTP_USER_AGENT': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36', 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7', 'HTTP_COOKIE': 'csrftoken=YjPgsyb6TW4fen2fxjy6DHzZYFlBU4SsAuE9AVqWRjLIhymeAlukqjVBpL7KTPPH', 'wsgi.input': <_io.BufferedReader name=7>, 'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>, 'wsgi.version': (1, 0), 'wsgi.run_once': False, 'wsgi.url_scheme': 'http', 'wsgi.multithread': True, 'wsgi.multiprocess': False, 'wsgi.file_wrapper': <class 'wsgiref.util.FileWrapper'>}

這些參數值得關註的為:

  • PATH_INFO:路由信息;
  • SERVER_PORT:埠;
  • HTTP_HOST:ip;
  • SERVER_PROTOCOL:伺服器端通信協議

可迭代響應

在 app 中向客戶端返回數據時,寫的為

return [data],如果改為return date,這將會導致 WSGI 程式的響應變慢。原因是字元串date也是可迭代的,它的每一次迭代只能得到 1bytes 的數據量,這也意味著每一次只向客戶端發送1bytes 的數據,直到發送完畢為止。所以推薦使用return [data]。這裡的數據是怎麼返回的目前還不清楚,保留疑問。。。

如果可迭代響應含有多個字元串,那麼Content-Length應該是這些字元串長度之和。

解析 GET 請求

運行 start.py文件,在瀏覽器中訪問http://localhost:8808/?id=1&name=musibii,可以在響應內容中找到到:

'QUERY_STRING': 'id=1&name=musibii'
'REQUEST_METHOD': 'GET'

cgi.parse_qs()函數可以很方便的處理 QUERY_STRING,同時需要cgi.escape()處理特殊字元以防止腳本註入,如下:

from cgi import parse_qs, escape

QUERY_STRING = 'id=1&name=musibii'
d = parse_qs(QUERY_STRING)
print(d.get('id', [''])[0]) # ['']是預設值,如果在QUERY_STRING中沒找到則返回預設值
print(d.get('name',[]))

print(escape('<script>alert(123);</script>'))

運行結果:

1
['musibii']
&lt;script&gt;alert(123);&lt;/script&gt;

處理 GET 請求的動態網頁

from wsgiref.simple_server import make_server
from cgi import parse_qs, escape

# html中 form 的 method 預設為 get,action 是當前頁面
html = '''
<html>
<body>
   <form method="get" action="">
        <p>
           Age: <input type="text" name="age" value="%(age)s">
        </p>
        <p>
            Hobbies:
            <input
                name="hobbies" type="checkbox" value="software"
                %(checked-software)s
            > Software
            <input
                name="hobbies" type="checkbox" value="tunning"
                %(checked-tunning)s
            > Auto Tunning
        </p>
        <p>
            <input type="submit" value="Submit">
        </p>
    </form>
    <p>
        Age: %(age)s<br>
        Hobbies: %(hobbies)s
    </p>
</body>
</html>
'''

def app(env, response):
    
    # 解析QUERY_STRING
    d = parse_qs(env['QUERY_STRING'])
    
    age = d.get('age', [''])[0] # 返回 age 對應的值
    hobbies = d.get('hobbies', []) # 以 list 形式返回所有的 hobbies
    
    # 防止腳本註入
    age = escape(age)
    hobbies = [escape(hobby) for hobby in hobbies]
    
    response_body = html% {
        'checked-software': ('', 'checket')['software' in hobbies],
        'checked-tunning': ('', 'checked')['tunning' in hobbies],
        'age': age or 'Empty',
        'hobbies': ','.join(hobbies or ['No Hobbies?'])
    }
    
    status = '200 OK'
    
    response_body = [
        ('Content-Type', 'text/html'),
        ('Content-Length', str(len(response_body)))
    ]
    
    start_response(status, response_headers)
    return [response_body]

httpd = make_server('', 8088, app)

httpd.serve_forever()

處理 POST 請求的動態網頁

對於POST 請求,查詢字元串是放在 HTTP 請求正文(request body)末尾的,不是顯式在 url 中。請求正文在 env 字典變數中鍵為wsgi.input對應的值中,這是一個類似 file 的變數:

'wsgi.input': <_io.BufferedReader name=7>

我看源碼看暈了還是沒找到這個 name 具體是什麼意思,經過 google 猜測這個應該是個標識符。

from wsgiref.simple_server import make_server
from cgi import parse_qs, escape

# html中form的method是post
html = """
<html>
<body>
   <form method="post" action="">
        <p>
           Age: <input type="text" name="age" value="%(age)s">
        </p>
        <p>
            Hobbies:
            <input
                name="hobbies" type="checkbox" value="software"
                %(checked-software)s
            > Software
            <input
                name="hobbies" type="checkbox" value="tunning"
                %(checked-tunning)s
            > Auto Tunning
        </p>
        <p>
            <input type="submit" value="Submit">
        </p>
    </form>
    <p>
        Age: %(age)s<br>
        Hobbies: %(hobbies)s
    </p>
</body>
</html>
"""

def application(environ, start_response):

    # CONTENT_LENGTH 可能為空,或者沒有
    try:
        request_body_size = int(environ.get('CONTENT_LENGTH', 0))
    except (ValueError):
        request_body_size = 0

    request_body = environ['wsgi.input'].read(request_body_size)
    d = parse_qs(request_body)

    # 獲取數據
    age = d.get('age', [''])[0] 
    hobbies = d.get('hobbies', []) 

    # 轉義,防止腳本註入
    age = escape(age)
    hobbies = [escape(hobby) for hobby in hobbies]

    response_body = html % { 
        'checked-software': ('', 'checked')['software' in hobbies],
        'checked-tunning': ('', 'checked')['tunning' in hobbies],
        'age': age or 'Empty',
        'hobbies': ', '.join(hobbies or ['No Hobbies?'])
    }

    status = '200 OK'

    response_headers = [
        ('Content-Type', 'text/html'),
        ('Content-Length', str(len(response_body)))
    ]

    start_response(status, response_headers)
    return [response_body]

httpd = make_server('localhost', 8051, application)

httpd.serve_forever()

中間件

中間件位於 WSGI server 和 WSGI application 之間。所以對客戶端來說,中間件扮演伺服器;對伺服器來說,中間件扮演客戶端。在 Django 中wsgi 收到的數據用 request對象表示,要傳給客戶端的數據用 Httpresponse對象表示。

示例:

from wsgiref.simple_server import make_server

def application(environ, start_response):

    response_body = 'hello world!'

    status = '200 OK'

    response_headers = [
        ('Content-Type', 'text/plain'),
        ('Content-Length', str(len(response_body)))
    ]

    start_response(status, response_headers)
    return [response_body]

# 中間件
class Upperware:
   def __init__(self, app):
      self.wrapped_app = app

   def __call__(self, environ, start_response):
      for data in self.wrapped_app(environ, start_response):
        yield data.upper()

wrapped_app = Upperware(application)

httpd = make_server('localhost', 8051, wrapped_app)

httpd.serve_forever()

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 對於輸入的一個正整數,輸出其反轉形式 要求使用c++ class編寫程式。可以創建如下class 輸入描述一個正整數a ,且1=<a<=1,000,000,000 輸出描述a的反轉形式 樣例輸入1011 樣例輸出1101 ...
  • C語言提供了另一種用於多分支選擇的switch語句(常用於開關),一般形式為: switch ( 常量表達式 ) { case 常量1 :語句; case 常量2 :語句; case 常量3 :語句; ... case 常量n:語句; default :語句; } 語義:計算常量表達式的值,並逐個與 ...
  • java單例模式(Singleton)以及實現 java單例模式(Singleton)以及實現 java單例模式(Singleton)以及實現 一. 什麼是單例模式 因程式需要,有時我們只需要某個類同時保留一個對象,不希望有更多對象,此時,我們則應考慮單例模式的設計。 二. 單例模式的特點 1. 單 ...
  • 本節為JVM垃圾收集的基礎理論,一個GC過程在邏輯上需要經過兩個步驟,即先判斷哪些對象是存活的、哪些對象是死亡的,然後對死亡的對象進行回收。 一、關於回收目標 在前面我們已經瞭解到,JVM的記憶體模型劃分為多個區域,由於不同區域的實現機制以及功能不同,那麼各自的回收目標也不同。一般來說,記憶體回收主要涉 ...
  • 前言: 畢設項目還要求加了這個做大數據搜索,正好自己也比較感興趣,就一起來學習學習吧! Elasticsearch 簡介 Elasticsearch 是一個分散式、RESTful 風格的搜索和數據分析引擎,能夠解決不斷涌現出的各種用例。作為 Elastic Stack 的核心,它集中存儲您的數據,幫 ...
  • 1. 給定a = [1,2,[3,4,[5,6,7,[8,9,[10,11]]]]],要求列印輸出:1,2,3,4,5,6,7,8,9,10,11 使用遞歸函數遍歷a,當a的值為list,繼續調用遞歸函數,一層一層的取值 2.在第1題的基礎上將生成結果為一個列表 3.遞歸寫一個方法輸出n,n-1.. ...
  • 題意 "題目鏈接" Sol 開始想的dp,發現根本不能轉移(貌似只能做鏈) 根據期望的線性性,其中$ans = \sum_{1 f(x)}$ $f(x)$表示刪除$x$節點的概率,顯然$x$節點要被刪除,那麼它的祖先都不能被刪除,因此概率為$\frac{1}{deep[x]}$ cpp includ ...
  • 今天我們來學習Spring整合Mybatis。 開發環境:Ide:MyEclipse 2017 CI JDK:1.8 首先我們簡單的認識下這兩個框架 1、Mybatis MyBatis是一個支持普通SQL查詢,存儲過程和高級映射的優秀持久層框架。MyBatis消除了幾乎所有的JDBC代碼和參數的手工 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...