python3中網絡編程的編碼問題

在學習python3網絡編程的時候,總是出現“a bytes-like object is required,not ‘str’ ”這種提示,很苦惱,網上也百度了一波,可是還是沒有得到解決,便看了看有關編碼的知識,可是看了之後還是報同樣的問題,感覺應該是send() sendto(),recv() recvfrom() 這幾個函數的返回值和參數值的原因,於是便查了關於這幾個函數的官方文檔,果然我犯的是低級錯誤啊,看類以後編程還是得把函數的用法弄清楚再幹啊!

先來說說編碼問題
關於編碼的詳細介紹請移步廖雪峯的python教程字符串和編碼

下面的關於編碼的介紹是我個人的理解,如果有誤請諒解!

這裏寫圖片描述
>

Unicode編碼使得在一個文本中,各個國家的文字都可以清楚的表示出來
這點ASCII是無法做到的)。(內存中表示)

utf-8 是秉承一種節約磁盤空間或者網絡帶寬,它常用與在傳輸中,
或者是存儲於磁盤中的一種編碼。(硬盤或者需要傳輸的時候表示)

用記事本編輯的時候,從文件讀取的UTF-8字符被轉換爲Unicode字符到內存裏,編輯完成後,保存的時候再把Unicode轉換爲UTF-8保存到文件:
這裏寫圖片描述

瀏覽網頁的時候,服務器會把動態生成的Unicode內容轉換爲UTF-8再傳輸到瀏覽器:
這裏寫圖片描述

python的字符串

在最新的Python 3版本中,字符串是以Unicode編碼的,也就是說,Python的字符串支持多語言。

>>> print('包含中文的string')
包含中文的string

對於單個字符的編碼,Python提供了ord()函數獲取字符的整數表示,chr()函數把編碼轉換爲對應的字符:

>>> ord('a')
97
>>> ord('中')
20013
>>> chr(97)
'a'
>>> chr(20013)
'中'

由於Python的字符串類型是str,在內存中以Unicode表示,一個字符對應若干個字節。如果要在網絡上傳輸,或者保存到磁盤上,就需要把str變爲以字節爲單位的bytes。
Python對bytes類型的數據用帶b前綴的單引號或雙引號表示:b'abc'

以Unicode表示的str通過encode()方法可以編碼爲指定的bytes

>>> 'abc'.encode('utf-8')
b'abc'
>>> 'abc'.encode('ascii')
b'abc'

純英文的str可以用ASCII編碼爲bytes,內容是一樣的,
含有中文的str可以用UTF-8編碼爲bytes。
含有中文的str無法用ASCII編碼,因爲中文編碼的範圍超過了ASCII編碼的範圍,
Python會報錯。

在bytes中,無法顯示爲ASCII字符的字節,用\x##顯示。

>>> '中'.encode('utf8')
b'\xe4\xb8\xad'

>>> '中'.encode()          #python3 默認就是utf-8
b'\xe4\xb8\xad'

>>> '中'.encode('ascii')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character '\u4e2d' in
position 0: ordinal not in range(128)

反過來,如果我們從網絡或磁盤上讀取了字節流,那麼讀到的數據就是bytes。要把bytes變爲str,就需要用decode()方法:

>>> b'\xe4\xb8\xad'.decode('utf-8')
'中'
>>> b'\xe4\xb8\xad'.decode()     #python3 默認就是utf-8   
'中'

如果bytes中包含無法解碼的字節,decode()方法會報錯:
>>>  b'\xe4\xb8\xad\xff'.decode()
  File "<stdin>", line 1
    b'\xe4\xb8\xad\xff'.decode()
    ^
IndentationError: unexpected indent


如果bytes中只有一小部分無效的字節,可以傳入errors='ignore'忽略錯誤的字節:
>>>b'\xe4\xb8\xad\xff'.decode('utf8',error='ignore')

總的來說,我們需要注意bytes,str各自在什麼情況下出現,以及bytes與str的轉換。

在操作字符串時,我們經常遇到str和bytes的互相轉換。爲了避免亂碼問題,應當始終堅持使用UTF-8編碼對str和bytes進行轉換。

由於Python源代碼也是一個文本文件,所以,當你的源代碼中包含中文的時候,在保存源代碼時,就需要務必指定保存爲UTF-8編碼。當Python解釋器讀取源代碼時,爲了讓它按UTF-8編碼讀取,我們通常在文件開頭寫上這兩行:

#!/usr/bin/env python3  
# -*- coding: utf-8 -*-

第一行註釋是爲了告訴Linux/OS X系統,這是一個Python可執行程序,Windows系統會忽略這個註釋;

第二行註釋是爲了告訴Python解釋器,按照UTF-8編碼讀取源代碼,否則,你在源代碼中寫的中文輸出可能會有亂碼。

申明瞭UTF-8編碼並不意味着你的.py文件就是UTF-8編碼的,必須並且要確保文本編輯器正在使用UTF-8 。

以我正在使用的sublime text3爲例:
單擊Preferences->Settings

這裏寫圖片描述

Socket的幾個函數

終於把編碼問題搞清楚了,下面就看看網絡編程中用到的這幾個函數吧

recv()
---------------------------------------------
socket.recv(bufsize[, flags])

Receive data from the socket. 
The return value is a bytes object representing the data received. 
The maximum amount of data to be received at once is specified by bufsize. 

返回的是bytes類型數據
bufsize是一次能接受的最大數據大小


recvfrom()
---------------------------------------------
socket.recvfrom(bufsize[, flags])

Receive data from the socket. 
The return value is a pair (bytes, address)
where bytes is a bytes object representing the data received 
and address is the address of the socket sending the data. 

返回的是一個二元元組,第一個元素是bytes的數據,
第二個元素是發送方的地址


send()
----------------------------------------------
socket.send(bytes[, flags])

Send data to the socket.
The socket must be connected to a remote socket.
Returns the number of bytes sent

參數是bytes類型,返回值是字節數。


sendto()
---------------------------------------------
socket.sendto(bytes, address)

Send data to the socket. 
The socket should not be connected to a remote socket, 
since the destination socket is specified by address

參數是bytes類型和一個地址

弄清楚這些之後,再寫實現tcp/udp網絡編程時就沒有出現問題了

我在電腦上開了兩臺虛擬機,Ubuntu17(192.168.217.132)和centos7
其中Ubuntu作爲服務端,centos作爲客戶端,下面是具體的實現代碼。代碼就不講了吧!

tcp服務端(Ubuntu)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from socket import *
from time import ctime

host = ''
port = 12345
addr = (host,port)

serverSocket = socket(AF_INET,SOCK_STREAM)
serverSocket.bind(addr)
serverSocket.listen(5)

while True:
    print('waiting for connect...')
    clientSocket,addr1 = serverSocket.accept()
    print(addr1,'connected')

    while True:
        data = clientSocket.recv(1024)
        if not data:
            break
    replyMsg = data.decode().upper() + ' [' + ctime() + ']'
        clientSocket.send(replyMsg.encode())
    clientSocket.close()

serverSocket.close()

tcp客戶端(centos)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from socket import *
from time import ctime

host = '192.168.217.132'
port = 12345
addr = (host,port)

clientSocket = socket(AF_INET,SOCK_STREAM)
clientSocket.connect(addr)

while True:
        data = input('enter messsage:')
        if not data:
                break

        clientSocket.send(data.encode())
        data = clientSocket.recv(1024)
        if not data:
                break
        print('the message of server send: ',data.decode())
clientSocket.close()

udp服務端

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from socket import *
from time import ctime

host = ''
port = 12345
addr = (host,port)

serverSocket = socket(AF_INET,SOCK_DGRAM)
serverSocket.bind(addr)

while True:
    print('waiting for connect...')
    data,addr = serverSocket.recvfrom(1024)
    if not data:
        break
    replyMsg = data.decode().upper() + ' [' + ctime() + ']'
    serverSocket.sendto(replyMsg.encode(),addr)

serverSocket.close()

udp客戶端

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from socket import *
from time import ctime

host = '192.168.217.132'
port = 12345
addr = (host,port)

clientSocket = socket(AF_INET,SOCK_DGRAM)

while True:
        data = input('>>')
        if not data:
                break
        clientSocket.sendto(data.encode(),addr)
        data,addr = clientSocket.recvfrom(1024)
        if not data:
                break
        print('the message of server send: ',data.decode())
clientSocket.close()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章