1. 程式人生 > >python3中網路程式設計的編碼問題

python3中網路程式設計的編碼問題

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

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

這裡寫圖片描述
>

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()