Python網(wǎng)絡(luò)編程筆記(四):網(wǎng)絡(luò)數(shù)據(jù)和網(wǎng)絡(luò)錯(cuò)誤

前文講了網(wǎng)絡(luò)之間傳輸協(xié)議TCP和UDP的連接和建立,以及如何域名解析找到雙方主機(jī)∷遥現(xiàn)在該討論如何準(zhǔn)備網(wǎng)絡(luò)傳輸用的數(shù)據(jù),以及可能遇到的錯(cuò)誤洋闽。

字節(jié)和字符串

8個(gè)二進(jìn)制位 (bit) 組成的字節(jié) (Byte) 是IP網(wǎng)絡(luò)上的通用傳輸單元罩缴。文本數(shù)據(jù)最重要的就是選擇一種編碼方式趾牧,將想要傳輸?shù)淖址D(zhuǎn)換成字節(jié)铃在。

字節(jié)字符串遥诉,本質(zhì)上是字符

Python中表示字節(jié)的方法:

  1. 第一種使用一個(gè)正好介于0-255的整數(shù)

  2. 第二種使用字節(jié)字符串. 可以使用 bytes() 將包含數(shù)字的列表轉(zhuǎn)換成字節(jié)字符串须床。

     >>> 0b1100010
     98
     >>> 0b1100010 == 0o142 == 98 == 0x62
     True
    

字節(jié)字符串的打宇砹稀: 使用ASCII碼作為簡(jiǎn)寫形式,如果找不到對(duì)應(yīng)ASCII碼,則顯示使用十六進(jìn)制格式 \xNN 來(lái)表示钠惩。實(shí)際上是字符柒凉,比如 b'\x00\x01bcd', 注意它開(kāi)頭的 b

字符串

字符編碼標(biāo)準(zhǔn):

  • ASCII (American Standard Code for Information InterChange, 美國(guó)標(biāo)準(zhǔn)信息轉(zhuǎn)換碼篓跛,128個(gè))
  • Unicode (Uni code, 已經(jīng)收錄10幾萬(wàn)字符了)

Python 3 內(nèi)部把字符串看做是由 Unicode 字符組成膝捞,已經(jīng)對(duì)我們隱藏了細(xì)節(jié)。要處理的只是文件中或者網(wǎng)絡(luò)上的數(shù)據(jù)愧沟。

操作:

  • 編碼 (Encoding): Unicode 字符 => 字節(jié)字符串
    • 單字節(jié)編碼蔬咬,一個(gè)字節(jié)一個(gè)字符,最多256個(gè)字符
    • 多字節(jié)編碼沐寺,定長(zhǎng)的 UTF-32林艘,不定長(zhǎng)的 UTF-8,BOM表示字節(jié)順序 \xeff
  • 解碼 (Decoding):字節(jié)字符串 => Unicode字符串

錯(cuò)誤:

  • 已編碼的字節(jié)字符串不符合提供的編碼規(guī)則混坞,因此解碼失敗 (UnicodeDecodeError): b'\x80'.decode()
  • 字符無(wú)法使用提供的編碼方式編碼狐援,因此編碼失敗 (UnicodeEncodeError): 'dd'.encode('latin-1')

錯(cuò)誤處理:使用正確編碼,decode()/encode 加參數(shù) ignore/repalce

字節(jié)順序和二進(jìn)制數(shù)

大端序和小端序

操作二進(jìn)制用 struct 模塊究孕。

struct.pack('<i', 4253) // 小端
struct.pack('>i', 4253)  

struc.unpack('<i', b'\x00\x80')

封幀和引用

UDP是數(shù)據(jù)報(bào)啥酱,不存在粘包問(wèn)題。

TCP傳輸流蚊俺,就會(huì)遇到問(wèn)題:接收方何時(shí)停止調(diào)用 recv()? 整個(gè)消息或數(shù)據(jù)何時(shí)完成傳輸完?何時(shí)能將接收到的信息作為一個(gè)整體去操作逛万?

六個(gè)模式確保知道消息何時(shí)結(jié)束

模式一:只涉及數(shù)據(jù)發(fā)送泳猬,不關(guān)注響應(yīng)。

發(fā)送方循環(huán)發(fā)送數(shù)據(jù)宇植,直到所有數(shù)據(jù)都被傳給 sendall(), 然后 close();
接收方一直調(diào)用 recv(), 直至 recv() 返回空得封。

模式二:一的變種,只不過(guò)兩個(gè)方向上都發(fā)送

先通過(guò)流在一個(gè)方向發(fā)送指郁,然后關(guān)閉該方向忙上。接著在另一個(gè)方向發(fā)送。

模式三: 定長(zhǎng)消息

雙方約定好一個(gè)length闲坎。

模式四:使用特殊字符劃分消息邊界疫粥。

  • 定界符要選用傳輸字符之外的字符,比如傳輸ASCII字符腰懂,用空字符串 \0 定界梗逮。
  • 任意消息的話,可以使用轉(zhuǎn)義绣溜,不過(guò)要處理事情太多慷彤,不建議。

模式五:每個(gè)消息前加上其長(zhǎng)度作為前綴,流行選擇底哗。長(zhǎng)度可以使用定長(zhǎng)的二進(jìn)制整數(shù)或者變長(zhǎng)的整數(shù)字符串后加上一個(gè)文本定界符表示岁诉。

模式六:解決五中不知道消息長(zhǎng)度的問(wèn)題。將一條消息分為多個(gè)數(shù)據(jù)塊發(fā)送跋选,每個(gè)數(shù)據(jù)塊前加上數(shù)據(jù)長(zhǎng)度涕癣。信息結(jié)尾處,與發(fā)送方約定一個(gè)信號(hào)野建,比如長(zhǎng)度為0的數(shù)據(jù)塊属划。

塊傳輸代碼

#!/usr/bin/env python3
# Foundations of Python Network Programming, Third Edition
# https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter05/blocks.py
# Sending data over a stream but delimited as length-prefixed blocks.

import socket, struct
from argparse import ArgumentParser

// I 表示使用32位無(wú)符號(hào)整數(shù),4B
header_struct = struct.Struct('!I')  # messages up to 2**32 - 1 in length

def recvall(sock, length):
    blocks = []
    while length:
        block = sock.recv(length)
        if not block:
            raise EOFError('socket closed with {} bytes left'
                           ' in this block'.format(length))
        length -= len(block)
        blocks.append(block)
    return b''.join(blocks)

def get_block(sock):
    data = recvall(sock, header_struct.size)
    (block_length,) = header_struct.unpack(data)
    return recvall(sock, block_length)

// 這里為什么不用 sendall? 如果知道數(shù)據(jù)多長(zhǎng)候生,是否一次發(fā)送無(wú)所謂了同眯。
def put_block(sock, message):
    block_length = len(message)
    sock.send(header_struct.pack(block_length))
    sock.send(message)

def server(address):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(address)
    sock.listen(1)
    print('Run this script in another window with "-c" to connect')
    print('Listening at', sock.getsockname())
    sc, sockname = sock.accept()
    print('Accepted connection from', sockname)
    sc.shutdown(socket.SHUT_WR)
    while True:
        block = get_block(sc)
        if not block:
            break
        print('Block says:', repr(block))
    sc.close()
    sock.close()

def client(address):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(address)
    sock.shutdown(socket.SHUT_RD)
    put_block(sock, b'Beautiful is better than ugly.')
    put_block(sock, b'Explicit is better than implicit.')
    put_block(sock, b'Simple is better than complex.')
    put_block(sock, b'')
    sock.close()

if __name__ == '__main__':
    parser = ArgumentParser(description='Transmit & receive blocks over TCP')
    parser.add_argument('hostname', nargs='?', default='127.0.0.1',
                        help='IP address or hostname (default: %(default)s)')
    parser.add_argument('-c', action='store_true', help='run as the client')
    parser.add_argument('-p', type=int, metavar='port', default=1060,
                        help='TCP port number (default: %(default)s)')
    args = parser.parse_args()
    function = client if args.c else server
    function((args.hostname, args.p))

pickle 與自定義定界符的格式

有的數(shù)據(jù)本身已有定界符,不需要封幀唯鸭。pickle 可以將數(shù)據(jù)結(jié)構(gòu)保存起來(lái)须蜗,以便在另一臺(tái)機(jī)器使用。

import pickle
    
pickle.dump()
pickle.loads()

pickle 使用 . 作為結(jié)束符目溉,loads 時(shí) .之后的內(nèi)容不會(huì)讀取明肮,文件指針停留在此處,可以從此處用文件指針讀缭付。

數(shù)據(jù)格式

XML 與 JSON都很流行柿估,文檔的話 XML 更好,有結(jié)構(gòu)陷猫。

二進(jìn)制格式 Thrift秫舌, ProtoBuf

壓縮

必要性:因?yàn)閿?shù)據(jù)傳輸?shù)臅r(shí)間遠(yuǎn)遠(yuǎn)多于 CPU 準(zhǔn)備數(shù)據(jù)的時(shí)間

zlib.compress()
zlib.decompressobj()

zlib自己提供封幀,一般會(huì)在外面包一層封幀绣檬。

網(wǎng)絡(luò)異常

針對(duì)套接字的異常:

  • OSERROR: 網(wǎng)絡(luò)傳輸所有階段都可能遇到足陨。
  • socket.gaierror: getaddrinfo() 失敗后返回, gai 是 get addr info 縮寫。
  • socket.timeout: 設(shè)置了超時(shí)參數(shù)

拋出異常

有兩種思路:

  • 完全不處理網(wǎng)絡(luò)異常

  • 將網(wǎng)絡(luò)錯(cuò)誤包裝我們自己的異常
    取決于我們的程序定位是庫(kù)還是工具

      class DestiError(Exception):
          def __str__(self):
              return '%s: %s' % (self.arg[0], self.__cause__.error)
    

捕捉和報(bào)告網(wǎng)絡(luò)異常

兩種方法:

  • granular 異常處理娇未,對(duì)于每個(gè)網(wǎng)絡(luò)調(diào)用都使用 try...except
  • blanket 異常處理: 在一個(gè)代碼塊或功能塊使用 try...except墨缘,然后打印自己定義的錯(cuò)誤。在頂層捕捉 FatalError
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末零抬,一起剝皮案震驚了整個(gè)濱河市镊讼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌平夜,老刑警劉巖狠毯,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異褥芒,居然都是意外死亡嚼松,警方通過(guò)查閱死者的電腦和手機(jī)嫡良,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)献酗,“玉大人寝受,你說(shuō)我怎么就攤上這事『辟耍” “怎么了很澄?”我有些...
    開(kāi)封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)颜及。 經(jīng)常有香客問(wèn)我甩苛,道長(zhǎng),這世上最難降的妖魔是什么俏站? 我笑而不...
    開(kāi)封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任讯蒲,我火速辦了婚禮,結(jié)果婚禮上肄扎,老公的妹妹穿的比我還像新娘墨林。我一直安慰自己,他們只是感情好犯祠,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布旭等。 她就那樣靜靜地躺著,像睡著了一般衡载。 火紅的嫁衣襯著肌膚如雪搔耕。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天痰娱,我揣著相機(jī)與錄音弃榨,去河邊找鬼。 笑死猜揪,一個(gè)胖子當(dāng)著我的面吹牛惭墓,可吹牛的內(nèi)容都是我干的坛梁。 我是一名探鬼主播而姐,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼划咐!你這毒婦竟也來(lái)了拴念?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤褐缠,失蹤者是張志新(化名)和其女友劉穎政鼠,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體队魏,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡公般,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年万搔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片官帘。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瞬雹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出刽虹,到底是詐尸還是另有隱情酗捌,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布涌哲,位于F島的核電站胖缤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏阀圾。R本人自食惡果不足惜哪廓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望稍刀。 院中可真熱鬧撩独,春花似錦、人聲如沸账月。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)局齿。三九已至剧劝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抓歼,已是汗流浹背讥此。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谣妻,地道東北人萄喳。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蹋半,于是被迫代替她去往敵國(guó)和親他巨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容