socket是網路程式設計的乙個抽象概念。通常我們用乙個socket表示「開啟了乙個網路鏈結」,而開啟乙個socket需要知道目標計算機的ip位址和埠號,再指定協議型別即可。
大多數連線都是可靠的tcp連線。建立tcp連線時,主動發起連線的叫客戶端,被動響應連線的叫伺服器。
所以,我們要建立乙個基於tcp連線的socket,可以這樣做:
# 匯入socket庫:
import socket
# 建立乙個socket:
s = socket.socket(socket.af_inet, socket.sock_stream)
# 建立連線:
s.connect(('www.sina.com.cn', 80))
建立socket
時,af_inet
指定使用ipv4協議,如果要用更先進的ipv6,就指定為af_inet6
。sock_stream
指定使用面向流的tcp協議,這樣,乙個socket
物件就建立成功,但是還沒有建立連線。
s.connect(('www.sina.com.cn', 80))
注意引數是乙個tuple
,包含位址和埠號。
# 傳送資料:
tcp連線建立的是雙向通道,雙方都可以同時給對方發資料。但是誰先發誰後發,怎麼協調,要根據具體的協議來決定。例如,http協議規定客戶端必須先發請求給伺服器,伺服器收到後才發資料給客戶端。
# 接收資料:
buffer =
while true:
# 每次最多接收1k位元組:
d = s.recv(1024)
if d:
else:
break
data = b''.join(buffer)
接收資料時,呼叫recv(max)
方法,一次最多接收指定的位元組數,因此,在乙個while迴圈中反覆接收,直到recv()
返回空資料,表示接收完畢,退出迴圈。
當我們接收完資料後,呼叫close()
方法關閉socket,這樣,一次完整的網路通訊就結束了:
# 關閉連線:
s.close()
接收到的資料報括http頭和網頁本身,我們只需要把http頭和網頁分離一下,把http頭列印出來,網頁內容儲存到檔案:
header, html = data.split(b'\r\n\r\n', 1)
print(header.decode('utf-8'))
# 把接收的資料寫入檔案:
with open('sina.html', 'wb') as f:
f.write(html)
和客戶端程式設計相比,伺服器程式設計就要複雜一些。
伺服器程序首先要繫結乙個埠並監聽來自其他客戶端的連線。如果某個客戶端連線過來了,伺服器就與該客戶端建立socket連線,隨後的通訊就靠這個socket連線了。
所以,伺服器會開啟固定埠(比如80)監聽,每來乙個客戶端連線,就建立該socket連線。由於伺服器會有大量來自客戶端的連線,所以,伺服器要能夠區分乙個socket連線是和哪個客戶端繫結的。乙個socket依賴4項:伺服器位址、伺服器端口、客戶端位址、客戶端埠來唯一確定乙個socket。
但是伺服器還需要同時響應多個客戶端的請求,所以,每個連線都需要乙個新的程序或者新的執行緒來處理,否則,伺服器一次就只能服務乙個客戶端了。
我們來編寫乙個簡單的伺服器程式,它接收客戶端連線,把客戶端發過來的字串加上hello
再發回去。
首先,建立乙個基於ipv4和tcp協議的socket:
s = socket.socket(socket.af_inet, socket.sock_stream)
然後,我們要繫結監聽的位址和埠。伺服器可能有多塊網絡卡,可以繫結到某一塊網絡卡的ip位址上,也可以用0.0.0.0
繫結到所有的網路位址,還可以用127.0.0.1
繫結到本機位址。127.0.0.1
是乙個特殊的ip位址,表示本機位址,如果繫結到這個位址,客戶端必須同時在本機執行才能連線,也就是說,外部的計算機無法連線進來。
埠號需要預先指定。因為我們寫的這個服務不是標準服務,所以用9999
這個埠號。請注意,小於1024
的埠號必須要有管理員許可權才能繫結:
# 監聽埠:
s.bind(('127.0.0.1', 9999))
緊接著,呼叫listen()
方法開始監聽埠,傳入的引數指定等待連線的最大數量:
s.listen(5)
print('waiting for connection...')
接下來,伺服器程式通過乙個永久迴圈來接受來自客戶端的連線,accept()
會等待並返回乙個客戶端的連線:
while true:
# 接受乙個新連線:
sock, addr = s.accept()
# 建立新執行緒來處理tcp連線:
t = threading.thread(target=tcplink, args=(sock, addr))
t.start()
每個連線都必須建立新執行緒(或程序)來處理,否則,單執行緒在處理連線的過程中,無法接受其他客戶端的連線:
def tcplink(sock, addr):
print('accept new connection from %s:%s...' % addr)
sock.send(b'welcome!')
while true:
data = sock.recv(1024)
time.sleep(1)
if not data or data.decode('utf-8') == 'exit':
break
sock.send(('hello, %s!' % data.decode('utf-8')).encode('utf-8'))
sock.close()
print('connection from %s:%s closed.' % addr)
連線建立後,伺服器首先發一條歡迎訊息,然後等待客戶端資料,並加上hello
再傳送給客戶端。如果客戶端傳送了exit
字串,就直接關閉連線。
要測試這個伺服器程式,我們還需要編寫乙個客戶端程式:
s = socket.socket(socket.af_inet, socket.sock_stream)
# 建立連線:
s.connect(('127.0.0.1', 9999))
# 接收歡迎訊息:
print(s.recv(1024).decode('utf-8'))
for data in [b'michael', b'tracy', b'sarah']:
# 傳送資料:
s.send(data)
print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()
我們需要開啟兩個命令列視窗,乙個執行伺服器程式,另乙個執行客戶端程式,就可以看到效果了:
46 python學習筆記
之前用python跑過深度學習的 用過一段時間的jupiter和tensorflow 最近在ubuntu下搭建起了vscode anaconda的python開發環境,感覺很好用,尤其是用來做演算法驗證簡直舒服得一匹。遂單獨開一貼,記錄一下python學習與使用中的一些好玩的點。python是弱引數...
Python學習筆記(46) join 函式
作用 將序列中的元素以指定的字元連線生成乙個新的字串.語法 a join b 引數說明 a 分隔符。可以為空 b 要連線的元素序列 字串 元組 字典 返回值 返回乙個以分隔符a連線各個元素後生成的字串 簡單說來就是 以a作為分隔符,將b所有的元素合併成乙個新的字串 demo usr bin pyth...
Python學習46 使用 property
在繫結屬性時,如果我們直接把屬性暴露出去,雖然寫起來很簡單,但是,沒辦法檢查引數,導致可以把成績隨便改 s student s.score 9999 這顯然不合邏輯。為了限制score的範圍,可以通過乙個set score 方法來設定成績,再通過乙個get score 來獲取成績,這樣,在set s...