免费观看又色又爽又黄的小说免费_美女福利视频国产片_亚洲欧美精品_美国一级大黄大色毛片

自定義socket實(shí)現(xiàn)HTTP

自定義socket實(shí)現(xiàn)HTTP

Web服務(wù)的本質(zhì)3
之前已經(jīng)帶過(guò)一點(diǎn)了,下面使用socket發(fā)一個(gè)請(qǐng)求并且接收返回的數(shù)據(jù)。去掉模塊的封裝,從比較底層的層面了解一下其中的過(guò)程。

員工經(jīng)過(guò)長(zhǎng)期磨合與沉淀,具備了協(xié)作精神,得以通過(guò)團(tuán)隊(duì)的力量開(kāi)發(fā)出優(yōu)質(zhì)的產(chǎn)品。創(chuàng)新互聯(lián)堅(jiān)持“專(zhuān)注、創(chuàng)新、易用”的產(chǎn)品理念,因?yàn)椤皩?zhuān)注所以專(zhuān)業(yè)、創(chuàng)新互聯(lián)網(wǎng)站所以易用所以簡(jiǎn)單”。公司專(zhuān)注于為企業(yè)提供成都網(wǎng)站制作、網(wǎng)站建設(shè)、外貿(mào)網(wǎng)站建設(shè)、微信公眾號(hào)開(kāi)發(fā)、電商網(wǎng)站開(kāi)發(fā),成都微信小程序,軟件按需定制網(wǎng)站等一站式互聯(lián)網(wǎng)企業(yè)服務(wù)。

HTTP/1.0

用socket自定義http請(qǐng)求:

import socket
from bs4 import BeautifulSoup

client = socket.socket()
# 連接
client.connect(('edu.51cto.com', 80))
# 發(fā)送
header = b'GET / HTTP/1.0\r\nHost: edu.51cto.com\r\n\r\n'
client.sendall(header)
# 接收
data = client.recv(1024)
content = b''
while data:
    content += data
    data = client.recv(1024)
head, body = content.split(b'\r\n\r\n')
print(head)
print(len(body), body)
soup = BeautifulSoup(body.decode(), features='html.parser')
title = soup.find('title')
print(title)

這里發(fā)送的請(qǐng)求頭里有HTTP的版本 "HTTP/1.0" ,所以返回的響應(yīng)頭里有這個(gè) “Connection: close” ,這是一個(gè)短連接,接收數(shù)據(jù)就是上面的方式可以判斷服務(wù)端是否傳完了。接收數(shù)據(jù)到最后會(huì)收到一個(gè)空,就表示收完了。這個(gè)空應(yīng)該是socket連接斷開(kāi)時(shí)發(fā)送的。

HTTP/1.1

如果發(fā)的HTTP請(qǐng)求版本是 “HTTP/1.1” ,返回的響應(yīng)頭里會(huì)有這些 “Transfer-Encoding: chunked\r\nConnection: keep-alive\r\n” 。然后在響應(yīng)頭和響應(yīng)體之間會(huì)是這個(gè) “\r\n\r\n2b0\r\n” ,前面的 “\r\n\r\n” 是響應(yīng)頭和響應(yīng)體的分隔符,關(guān)鍵是中間的數(shù)字,這個(gè)是之后要發(fā)送的16進(jìn)制字節(jié)數(shù)。也就是說(shuō)這里的數(shù)據(jù)是分段發(fā)送的。每段數(shù)據(jù)都是前面是字節(jié)數(shù),后面是數(shù)據(jù),并且這里的分隔符也是 “\r\n” 。大概是這個(gè)樣子的:

響應(yīng)頭
\r\n\r\n
2b0\r\n(0x2b0個(gè)字符)\r\n
27e4\r\n(0x27e4個(gè)字符)\r\n
1c7c\r\n(0x1c7c個(gè)字符)\r\n
0\r\n\r\n

像上面這樣,最后是會(huì)發(fā)一個(gè)空的,所以是以 "\r\n0\r\n\r\n" 結(jié)尾。下面是自己寫(xiě)的實(shí)現(xiàn)拼接head和body的方法:

import socket
from bs4 import BeautifulSoup

client = socket.socket()
# 連接
client.connect(('edu.51cto.com', 80))
# 發(fā)送
header = b'GET / HTTP/1.1\r\nHost: edu.51cto.com\r\n\r\n'
client.sendall(header)
# 接收
data = client.recv(1024)
body = b''

# 獲取請(qǐng)求頭
while len(data.split(b'\r\n\r\n', 1)) != 2:
    data += client.recv(1024)
head, data = data.split(b'\r\n\r\n', 1)
print('HEAD', head)

# 拼接body
while data != b'0\r\n\r\n':
    while len(data.split(b'\r\n', 1)) != 2:
        data += client.recv(1024)
    l, b = data.split(b'\r\n', 1)
    length = int(l, base=16)
    if len(b) <= length:
        body += b
        length -= len(b)
        # 一下子把整段數(shù)據(jù)剩余的部分都讀完
        # length 可能會(huì)很長(zhǎng),但是可能一次收不全,所以得用循環(huán)和計(jì)數(shù)直到收完
        while length:
            b = client.recv(length)
            body += b
            length -= len(b)
        data = b''
    else:
        body += b[:length]
        data = b[length:]

    # 把下一段body開(kāi)頭的 b'\r\n' 切掉
    while len(data) < 2:
        data += client.recv(1024)
    else:
        # 這個(gè)斷言可以驗(yàn)證之前的邏輯是否有問(wèn)題
        assert data[0:2] == b'\r\n', b'data error: %d %b' % (len(data), data)
        data = data[2:]

print(len(body), body)
soup = BeautifulSoup(body.decode(), features='html.parser')
title = soup.find('title')
print(title)

驗(yàn)證body接收是否正確,可以和上面的HTTP/1.0的結(jié)果對(duì)比一下,看一下body的長(zhǎng)度。

自定義異步IO實(shí)現(xiàn)HTTP

先補(bǔ)充點(diǎn) selector 模塊的知識(shí),再用異步的 socket 實(shí)現(xiàn) HTTP 請(qǐng)求。

補(bǔ)充知識(shí)(fileno() 方法)

select 和 selectors 模塊里,需要把創(chuàng)建的socket實(shí)例放到監(jiān)聽(tīng)的列表里。這里,可以添加到監(jiān)聽(tīng)列表里的可以不是原生的socket實(shí)例。這里可以是 fd 也可以是一個(gè)擁有 fileno() 方法的對(duì)象。
fd : 文件描述符,是一個(gè)整數(shù),它是文件對(duì)象的 fileno() 方法的返回值。
這里,我們不僅要把socket對(duì)象加到監(jiān)聽(tīng)列表里,還需要給它綁定一些別的屬性。這就需要對(duì)socket封裝一下。寫(xiě)一個(gè)自己類(lèi),加一寫(xiě)自己的屬性以及一個(gè)socket對(duì)象的實(shí)例屬性。關(guān)鍵是在類(lèi)里實(shí)現(xiàn)一個(gè) fileno() 方法,該方法原樣返回 socket 實(shí)例的 fileno() 方法就可以了:

class HttpResponse(object):
    """接收一個(gè)實(shí)例化好的socket對(duì)象,在封裝一些別的數(shù)據(jù)"""

    def __init__(self, sk, item):
        self.sk = sk
        self.item = item

    def fileno(self):
        """請(qǐng)求sockect對(duì)象的文件描述符,用于select監(jiān)聽(tīng)"""
        return self.sk.fileno()

selector 模塊

官方文檔:https://docs.python.org/3/library/selectors.html

模塊定義了一個(gè) BaseSelector 的抽象基類(lèi),以及它的子類(lèi),包括:SelectSelector,PollSelector,EpollSelector,DevpollSelector,KqueueSelector。
另外還有一個(gè)DefaultSelector類(lèi),它其實(shí)是以上其中一個(gè)子類(lèi)的別名而已,它自動(dòng)選擇為當(dāng)前環(huán)境中最有效的Selector,所以平時(shí)用 DefaultSelector類(lèi)就可以了,其它用不著。

# 用之前先創(chuàng)建實(shí)例
sel = selectors.DefaultSelector()

模塊定義了兩個(gè)常量,在注冊(cè)事件的時(shí)候定義響應(yīng)哪類(lèi)事件:

  • EVENT_READ : 表示可讀的; 它的值其實(shí)是1
  • EVENT_WRITE : 表示可寫(xiě)的; 它的值其實(shí)是2

上面兩個(gè)常量是位掩碼,是這樣定義的:

# generic events, that must be mapped to implementation-specific ones
EVENT_READ = (1 << 0)
EVENT_WRITE = (1 << 1)

所以應(yīng)該也可以同時(shí)監(jiān)聽(tīng)兩個(gè)事件, EVENT_READ+EVENT_WRITE ,也就是3。

抽象基類(lèi)中的注冊(cè)事件的方法
fileobj上一小節(jié)講了,傳入socket對(duì)象或者是其他實(shí)現(xiàn)了 fileno() 方法對(duì)象。events參數(shù)就是上面的兩個(gè)常量。data參數(shù)在select方法里會(huì)返回:

register(fileobj, events, data=None)  # 注冊(cè)一個(gè)文件對(duì)象
unregister(fileobj)  # 注銷(xiāo)一個(gè)已經(jīng)注冊(cè)過(guò)的文件對(duì)象
modify(fileobj, events, data=None)  # 用于修改一個(gè)注冊(cè)過(guò)的文件對(duì)象,比如從監(jiān)聽(tīng)可讀變?yōu)楸O(jiān)聽(tīng)可寫(xiě)。

一個(gè)文件對(duì)象只能注冊(cè)一個(gè)事件。注冊(cè)的事件可以調(diào)用上面的 unregister 方法注銷(xiāo)。
另外如果要改變文件對(duì)象監(jiān)聽(tīng)的 event ,則調(diào)用上面的 modify 方法。它其實(shí)就是 register + unregister,但是使用modify更高效。
關(guān)于data對(duì)象,可以傳入任何東西。selector本身應(yīng)該不會(huì)操作data,只是事件注冊(cè)時(shí)傳入的data,等到該事件返回的時(shí)候,data也一起返回了。一種做法是傳入回調(diào)函數(shù),等事件返回的時(shí)候獲取data調(diào)用一下做處理。還有一個(gè)做法是傳入數(shù)據(jù),等到事件返回的時(shí)候,再對(duì)對(duì)應(yīng)的數(shù)據(jù)做處理。也可以以上2個(gè)都有,自己想辦法吧所有的東西封裝一下就是了。

抽象基類(lèi)中的其他方法
select(timeout=None) :用于選擇滿足我們監(jiān)聽(tīng)的event的文件對(duì)象。
這個(gè)方法如果不設(shè)置參數(shù)就是阻塞的。返回1個(gè)元組 (key, mask)
key 就是一個(gè)SelectorKey類(lèi)的實(shí)例,
key.fileobj 就是注冊(cè)方法的第一個(gè)參數(shù),也就是傳入的文件對(duì)象,比如socket對(duì)象。
key.data 就是注冊(cè)方式的第三個(gè)參數(shù),一般可以把回調(diào)函數(shù)傳進(jìn)去。
mask 就是 EVENT 事件的常量,1、2或者也可能是3。

close() : 關(guān)閉 selector。

get_key(fileobj) : 返回注冊(cè)文件對(duì)象的 key,返回的是 SelectorKey 類(lèi)的實(shí)例,同select方法里的key。

get_map() 方法
這個(gè)也是上面的抽象類(lèi)中的方法,不過(guò)單獨(dú)講。該方法返回的所有注冊(cè)的對(duì)象。返回的類(lèi)型如下:

<class 'selectors._SelectorMapping'>

不過(guò)基本上就是個(gè)字典。可以遍歷,也有 .keys()、.values()、.items() 這些方法。
字典的key,就是文件描述符fd的數(shù)字
字典的value,里面有4個(gè)屬性,注冊(cè)時(shí)傳入的內(nèi)容都在這里:

  • fileobj : 注冊(cè)方法傳進(jìn)來(lái)的fileobj參數(shù),就是socket對(duì)象(或者是其他實(shí)現(xiàn)了fileno方法的對(duì)象)
  • fd : 就是文件描述符的數(shù)字
  • events : 注冊(cè)方法傳進(jìn)來(lái)的events參數(shù)
  • data : 注冊(cè)方法傳進(jìn)來(lái)的data參數(shù)。

由于主要內(nèi)容都在value里,所以可以遍歷 sel.get_map().values() 進(jìn)行操作。但是要注意遍歷的時(shí)候不能操作被遍歷的對(duì)象,所以不能在for循環(huán)里做注冊(cè)或者注銷(xiāo)。
可以判斷是否有注冊(cè)對(duì)象,還有沒(méi)有注冊(cè)任何對(duì)象:

if sel.get_map():  # 有注冊(cè)方法
    pass

while sel.get_map():  # 可以用這個(gè)邏輯,在沒(méi)有任何注冊(cè)事件的時(shí)候退出循環(huán)
    pass

還可以檢查注冊(cè)了多少方法,控制注冊(cè)事件的數(shù)量。事件太多了也就不能再注冊(cè)了:

print(len(sel.get_map()))

實(shí)現(xiàn)異步 IO 的 HTTP

import selectors
import socket
from bs4 import BeautifulSoup

url_list = [
    {'host': 'edu.51cto.com', 'port': 80, },
    {'host': 'www.baidu.com', 'port': 80, },
    {'host': 'www.python-requests.org', 'port': 80, 'url': '/en/master/'},
    {'host': 'open-falcon.org', 'port': 80, 'url': '/'},
    {'host': 'www.jetbrains.com', 'port': 80},
]

class HttpSocket(object):
    """接收一個(gè)實(shí)例化好的socket對(duì)象,在封裝一些別的數(shù)據(jù)"""

    def __init__(self, sk, item):
        self.sk = sk
        self.item = item
        self.host = self.item.get('host')
        self.port = self.item.get('port', 80)
        self.method = self.item.get('method', 'GET')
        self.url = self.item.get('url', '/')
        self.body = self.item.get('body', '')
        self.callback = self.item.get('callback')
        self.buffer = []  # 請(qǐng)求的返回值記錄在這里

    def fileno(self):
        """請(qǐng)求sockect對(duì)象的文件描述符,用于select監(jiān)聽(tīng)"""
        return self.sk.fileno()

    def create_request_header(self):
        """創(chuàng)建請(qǐng)求信息"""
        request = '%s %s HTTP/1.0\r\nHost: %s\r\n\r\n%s' % (self.method.upper(), self.url, self.host, self.body)
        return request.encode('utf-8')

    def write(self, data):
        """把接收到的數(shù)據(jù)寫(xiě)入 self.buffer"""
        self.buffer.append(data)

    def finish(self):
        """接收完畢后執(zhí)行的函數(shù)"""
        content = b''.join(self.buffer)
        head, body = content.split(b'\r\n\r\n', 1)
        print(head)
        print(len(body), body)
        soup = BeautifulSoup(body.decode(), features='html.parser')
        title = soup.find('title')
        print(title)

class AsyncRequest(object):

    def __init__(self):
        self.sel = selectors.DefaultSelector()

    def add_request(self, item):
        """創(chuàng)建連接請(qǐng)求"""
        host = item.get('host')
        port = item.get('port')
        client = socket.socket()
        client.setblocking(False)
        try:
            client.connect((host, port))
        except BlockingIOError as e:
            pass  # 至此,已經(jīng)向服務(wù)器發(fā)出連接請(qǐng)求了
        hsk = HttpSocket(client, item)
        self.sel.register(hsk, selectors.EVENT_WRITE, self.connect)
        # 不同同時(shí)注冊(cè)2個(gè)事件,下面的注冊(cè)要等到連接建立之后執(zhí)行
        # self.sel.register(sk, selectors.EVENT_READ, self.accept)

    def connect(self, hsk, mask):
        """建立連接后的回調(diào)函數(shù)
        發(fā)送請(qǐng)求,然后注冊(cè) EVENT_READ 事件
        """
        print("連接成功:", hsk.item)
        content = hsk.create_request_header()
        print("發(fā)送請(qǐng)求:", content)
        hsk.sk.sendall(content)
        self.sel.modify(hsk, selectors.EVENT_READ, self.accept)

    def accept(self, hsk, mask):
        """接收請(qǐng)求返回的內(nèi)容"""
        # print("返回信息:", hsk.item)
        data = hsk.sk.recv(1024)
        if data:
            hsk.write(data)
        else:
            print("接收完畢", hsk.item)
            hsk.finish()
            self.sel.unregister(hsk)

    def run(self):
        """主函數(shù)"""
        while self.sel.get_map():
            events = self.sel.select()
            for key, mask in events:
                callback = key.data  # key.data就是sel.register里的第三個(gè)參數(shù)
                callback(key.fileobj, mask)  # key.fileobj就是sel.register里第一次參數(shù)

if __name__ == '__main__':
    obj = AsyncRequest()
    for url_dic in url_list:
        obj.add_request(url_dic)
    obj.run()

接收完畢之后,最后執(zhí)行的函數(shù),這里是調(diào)用finish函數(shù)。這個(gè)函數(shù)最好可以自定義,那么就需要在搞一個(gè)callback參數(shù)。思路大概是這樣的,最后就在finish函數(shù)里先可以做一些處理。然后判斷一下,如果有callback,則調(diào)用callback。否則繼續(xù)之后finish里之后的代碼。
這個(gè)callback參數(shù)在哪里設(shè)置似乎在實(shí)現(xiàn)上都沒(méi)問(wèn)題:
可以在url_list里加,在HttpSocket的構(gòu)造函數(shù)里提取出來(lái)。
或者是先給 AsyncRequest 類(lèi)的構(gòu)造函數(shù),然后在add_request方法里實(shí)例化HttpSocket的時(shí)候再傳過(guò)去。
再或者給add_request再加個(gè)參數(shù),也是在add_request方法里實(shí)例化HttpSocket的時(shí)候再傳過(guò)去。

socket 掃描端口

鞏固 selector 模塊的知識(shí),又寫(xiě)了一個(gè)端口掃描的程序:

import socket
import selectors
import time

class ScanTask(object):
    def __init__(self, host, start, end):
        self.host = host
        self.start = start
        self.end = end
        self.port = start

    def __str__(self):
        return "%s:(%s-%s)" % (self.host, self.start, self.end)

class AsyncScanPort(object):
    def __init__(self, start=1, end=65535, timeout=1, interval=0.01, pool=100):
        self.sel = selectors.DefaultSelector()
        self.start = start
        self.end = end
        self.timeout = timeout
        self.interval = interval
        self.pool = pool
        self.scan_list = []
        self.scan_index = 0

    def add_scan_task(self, host, start=None, end=None):
        start = start or self.start
        end = end or self.end
        obj = ScanTask(host, start, end)
        self.scan_list.append(obj)

    def check_timeout(self):
        for i in self.sel.get_map().values():
            t = i.data['time']
            if time.time() > t + self.timeout:
                self.sel.unregister(i.fileobj)
                return True
            # 只要判斷第一個(gè)就好了
            return False

    def add_to_scan(self):
        if len(self.sel.get_map()) >= self.pool:
            res = self.check_timeout()
            if not res:
                return False
        if self.scan_index >= len(self.scan_list):
            return False
        obj = self.scan_list[self.scan_index]
        if obj.port > obj.end:
            self.scan_index += 1
            print("\r單個(gè)地址加載完畢:", obj)
            return self.add_to_scan()
        client = socket.socket()
        client.setblocking(False)
        try:
            client.connect((obj.host, obj.port))
        except BlockingIOError as e:
            pass
        self.sel.register(client, selectors.EVENT_WRITE, {'obj': obj, 'port': obj.port, 'time': time.time()})
        print('\r正在掃描 ==> %s:%s' % (obj.host, obj.port), end='', flush=True)
        obj.port += 1
        return True

    def run(self):
        """主函數(shù)"""
        self.add_to_scan()
        while self.sel.get_map():
            events = self.sel.select(self.interval)
            if events:
                for key, mask in events:
                    self.callback(key.fileobj, mask, key.data)  # key.fileobj就是sel.register里第一次參數(shù)
            self.add_to_scan()
        print("掃描完畢")
        self.sel.close()

    def callback(self, sk, mask, data):
        try:
            sk.getpeername()
        except OSError as e:
            # 沒(méi)有掃描到端口
            self.sel.unregister(sk)
            return
        self.sel.unregister(sk)
        print("\r掃描到端口:", data['obj'], data['port'])

if __name__ == '__main__':
    hosts = ['192.168.1.1', '192.168.1.2', '192.168.1.3']
    obj = AsyncScanPort()
    for h in hosts:
        obj.add_scan_task(h)
    obj.run()

分享名稱(chēng):自定義socket實(shí)現(xiàn)HTTP
網(wǎng)頁(yè)鏈接:http://m.newbst.com/article46/pohgeg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供軟件開(kāi)發(fā)建站公司用戶體驗(yàn)定制開(kāi)發(fā)網(wǎng)站設(shè)計(jì)公司網(wǎng)站排名

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

綿陽(yáng)服務(wù)器托管