Python Socket網路程式設計(一)初識Socket和Socket初步使用
目錄
前言
本系列部落格是筆者學習Python Socket的過程筆記,目的在於記錄。其中的解釋都為自己的見解,僅供參考,如有錯誤,還望指出。本篇部落格是對Python Socket的初步瞭解和使用,大佛請移駕。
網路程式設計
網路程式設計本質就是實現兩個裝置之間的資料交換(通訊),通常這個裝置指的是計算機,實際上任何能連線網路的硬體裝置都能實現通訊。也就是說我們的任何能連線網路的裝置都是可通訊的,比如我有一個 LED顯示屏,我現在需要伺服器控制這個 LED螢幕顯示一段文字,一些輪播圖片或者一個視訊,並且控制裝置什麼時間播放什麼內容,播放多長時間等命令。那麼LED和我的伺服器之間就需要通訊,這個時候就不是計算機與計算機之間通訊了。
在網路程式設計中,發起連線的一方被稱作客戶端(Client),等待連線的一方被稱作伺服器(Server)。服務端一般都需要一直啟動,等待客戶端來連線。連線一旦建立,雙方就可以互相傳送資料了。
網路通訊和生活中的打電話類似,我要給某個人打電話我就得知道他的電話號碼,在網路中也是如此,我要和伺服器建立通訊就需要知道伺服器的在網路中的位置。計算機在網路中的位置由IP地址(具體概念檢視IP百度百科
一臺計算機上(裝置)可以執行多個程式(多個程序),為了區分這些程式就設計了埠(Port)的概念。裝置最多有216=65536個埠,一個埠可以對應一個唯一程式,無論是服務端還是客戶端,每個程式都對應一個或多個埠。其中0-1024之間的大多埠已經被作業系統佔用,我們的程式一般就使用之後的埠號(僅僅針對計算機裝置)。也就是說通過IP和埠號就可以實現兩個裝置的某個程式之間進行通訊了。
對於建立了連線的兩個裝置以何種方式進行資料的傳輸呢,傳輸資料的方式無論是有線傳輸還是無線傳輸,一般就兩種傳輸協議方式:
1. TCP(Transfer Control Protocol)
傳輸控制協議方式,該傳輸方式是一種穩定可靠的傳送方式,類似於顯示中的打電話。只需要建立一次連線,就可以多次傳輸資料。就像電話只需要撥一次號,就可以實現一直通話一樣,如果你說的話不清楚,對方會要求你重複,保證傳輸的資料可靠。使用該種方式的優點是穩定可靠,缺點是建立連線和維持連線的代價高,傳輸速度不快。
2. UDP(User Datagram Protocol)
使用者資料報協議方式,該傳輸方式不建立穩定的連線,類似於發簡訊息。每次傳送資料都直接傳送。傳送多條簡訊,就需要多次輸入對方的號碼。該傳輸方式不可靠,資料有可能收不到,系統只保證盡力傳送。使用該種方式的優點是開銷小,傳輸速度快,缺點是資料有可能會丟失。
協議(Protocol)在網路裡是一個重要的概念,平時所說的協議實際就是指網路協議,網路協議就是裝置實現通訊的規定,雙方必須遵守這個規定才能獲取到正確的通訊資訊。比如在建立連線的時候應該何種方式連線,怎麼互相判別,傳輸的資料格式(也就是要按照一定的格式來發送和接收資料)是什麼。只有遵守這個規定,裝置之間才能相互通訊交流。它的三要素是:語法、語義、時序。
為了使資料在網路上從源到達目的,網路通訊的參與方必須遵循相同的規則,這套規則稱為協議(protocol),它最終體現為在網路上傳輸的資料包的格式。
Socket
Socket即是套接字,Python將低級別的網路服務封裝成了一個模組,通過socket就可以將網路中的兩個裝置的某一程序(應用程式)以TCP或者UDP協議方式建立連線,在建立連線過後就可以實現裝置程序與裝置程序之間的通訊了。
我們匯入socket(使用import socket
)模組,使用s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
來建立返回一個基於IPv4地址流式TCP傳輸協議的套接字s,我們需要建立套接字最主要考慮前兩個引數,第一個是IP地址簇,第二個是傳輸協議型別,下面列了常用的幾個可選值,想要了解全部請前往Python3 socket。
- family:IP地址簇引數
可選值 | 描述 |
---|---|
socket.AF_INET | IPv4(預設) |
socket.AF_INET6 | IPv6 |
socket.AF_UNIX | 用於Unix系統程序間通訊 |
socket.AF_… | … |
- type:型別引數
可選值 | 描述 |
---|---|
socket.SOCK_STREAM | 流式socket,TCP(預設) |
socket.SOCK_DGRAM | 資料報式socket,UDP |
socket.SOCK_… | … |
函式 | 型別 | 描述 |
---|---|---|
s.bind() | 服務端用 | 繫結地址和埠到套接字,引數address為元組形式的(host, port) |
s.listen() | 服務端用 | 開始監聽繫結的地址埠程式(程序),引數backlog指定在拒絕連線之前,可以掛起的最大連線數量。 |
s.accept() | 服務端用 | 等待客戶端的連線(阻塞式)。 |
s.connect() | 客戶端用 | 主動去連線伺服器,引數address為元組形式的(host, port),如果連接出錯,返回socket.error錯誤。 |
s.connect_ex() | 客戶端用 | 主動去連線伺服器,引數address為元組形式的(host, port),出錯時返回出錯碼,而不是丟擲異常。 |
s.recv() | 公共使用 | 接收資料,返回位元組型字串資料,引數bufsize指定接收資料的最大長度。 |
s.send() | 公共使用 | 傳送資料,返回傳送成功的位元組數,引數data是要傳送的位元組型字串資料。 |
s.sendall() | 公共使用 | 完整發送資料。內部迴圈呼叫send直至資料傳送完整,引數data是要傳送的位元組型字串資料。 |
s.recvfrom() | 公共使用 | 接收資料,返回值是(data, address)。data是包含接收資料的字串,address是傳送資料的套接字地址。 |
s.sendto() | 公共使用 | 傳送資料,將資料傳送到套接字,address形式為(host, port)的元組,指定遠端地址。返回值是傳送位元組數。 |
s.close() | 公共使用 | 關閉套接字 |
s.getpeername() | 公共使用 | 返回連線套接字的遠端地址。返回值通常是元組(host, port)。 |
s.getsockname() | 公共使用 | 返回套接字自己的地址。通常是一個元組(host, port)。 |
s.setsockopt() | 公共使用 | 設定給定套接字選項的值。 |
s.getsockopt() | 公共使用 | 返回套接字選項的值。 |
s.settimeout() | 公共使用 | 設定套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。 |
s.gettimeout() | 公共使用 | 返回當前超時期的值,單位是秒,如果沒有設定超時期,則返回None。 |
s.fileno() | 公共使用 | 接收資料,返回位元組型字串資料,引數bufsize指定接收資料的最大長度。 |
s.setblocking() | 公共使用 | flag為0非阻塞,否則為阻塞模式(預設)。非阻塞模式調recv()或send()沒有資料,將引起socket.error異常。 |
s.makefile() | 公共使用 | 建立一個與該套接字相關連的檔案。 |
初步使用
由於要實現的是兩個程序間的通訊(同一個裝置),那麼需要兩個程式,我們分別叫客戶端程式和服務端程式。
在這個初級使用例項中,實現的是一臺計算機中兩個程序之間的通訊(一個程序為等待客戶端連線的服務端程式,一個程序為主動去連線服務端的客戶端程式)。也就是說客戶端和服務端程式都執行在本地的一臺計算機上,服務端建立socket,監聽本機IP的6688埠等待客戶端來連線,一旦有一個客戶端來連線了就列印連線資訊,然後等待連線的客戶端傳送資料,接收到客戶端傳來的資料後,就反饋給客戶端收到資訊了;客戶端程式先建立socket套接字,然後去連線本機的IP和6688埠程序,連線成功後等待控制檯輸入資料,接受到輸入的資料後傳送到服務端,然後結束連線。
先新建一個server.py檔案,這個檔案裡寫服務端功能程式,如下:
import socket
# 建立一個socket套接字,該套接字還沒有建立連線
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 繫結監聽埠
server.bind(('localhost', 6688))
# 開始監聽,並設定最大連線數
server.listen(5)
# 獲取未建立連線的服務端的IP和埠資訊
print(server.getsockname())
# 下面註釋掉的是獲取未建立連線的服務端套接字的遠端IP和埠資訊,執行下面語句會報錯,原因就是現在還沒有遠端客戶端程式連線
# print(server.getpeername())
print(u'waiting for connect...')
# 等待連線,一旦有客戶端連線後,返回一個建立了連線後的套接字和連線的客戶端的IP和埠元組
connect, (host, port) = server.accept()
# 現在建立連線就可以獲取這兩個資訊了,注意server和connect套接字的區別,一個是未建立連線的套接字,一個是已經和客戶端建立了連線的套接字
peer_name = connect.getpeername()
sock_name = connect.getsockname()
print(u'the client %s:%s has connected.' % (host, port))
print('The peer name is %s and sock name is %s' % (peer_name, sock_name))
# 接受客戶端的資料
data = connect.recv(1024)
# 傳送資料給客戶端告訴他接收到了
connect.sendall(b'your words has received.')
print(b'the client say:' + data)
# 結束socket
server.close()
然後建立一個client.py檔案,實現客戶端程式,內容如下:
import socket
# 建立一個socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 主動去連線本機IP和埠號為6688的程序,localhost等效於127.0.0.1,也就是去連線本機埠為6688的程序
client.connect(('localhost', 6688))
# 接受控制檯的輸入
data = input()
# 對資料進行編碼格式轉換,不然報錯
data = data.encode('utf-8')
# 傳送資料
client.sendall(data)
# 接收服務端的反饋資料
rec_data = client.recv(1024)
print(b'form server receive:' + rec_data)
client.close()
其中左邊為服務端執行結果,右邊為客戶端執行結果,右邊的hello是手動輸入然後回車。
結語
到此初步的使用已經差不多了,不過上述程式碼只能是一對一的通訊,一對多不是本篇部落格的內容,因為客戶端一般都不會只有一個,所以這個一對一不能滿足我們的常用需求,後面的部落格將會介紹多執行緒實現多客戶端連線伺服器,與伺服器通訊。在下一篇部落格會講解和實現區域網內兩臺裝置,廣域網之間,區域網與廣域網之間裝置的通訊。