在學習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()