Python學習之旅(三十三)

finsom發表於2018-12-14

Python基礎知識(32):網路程式設計(Ⅰ)

網路通訊是兩臺計算機上的兩個程式之間的通訊,而網路程式設計就是如何在程式中實現兩臺計算機的通訊

P協議負責把資料從一臺計算機通過網路傳送到另一臺計算機

TCP協議則是建立在IP協議之上的。TCP協議負責在兩臺計算機之間建立可靠連線,保證資料包按順序到達

許多常用的更高階的協議都是建立在TCP協議基礎上的,比如用於瀏覽器的HTTP協議、傳送郵件的SMTP協議等

TCP程式設計

Socket是網路程式設計的一個抽象概念。通常我們用一個Socket表示“開啟了一個網路連結”,而開啟一個Socket需要知道目標計算機的IP地址和埠號,再指定協議型別即可。

一、客戶端

大多數連線都是可靠的TCP連線。建立TCP連線時,主動發起連線的叫客戶端,被動響應連線的叫伺服器

1、建立一個基於TCP連線的Socket

#匯入socket
import socket

#建立一個socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#建立連線,注意引數是一個tuple,包含地址和埠號 s.connect((`www.sina.com.cn`, 80))

TCP連線建立的是雙向通道,雙方都可以同時給對方發資料。誰先發誰後發,怎麼協調,要根據具體的協議來決定

HTTP協議規定客戶端必須先發請求給伺服器,伺服器收到後才發資料給客戶端

傳送的文字格式必須符合HTTP標準

2、建立TCP連線後,就可以向傳送請求,要求返回首頁的內容

#傳送資料
s.send(b`GET / HTTP/1.1
Host: www.sina.com.cn
Connection: close

`)

3、接收伺服器返回的資料

#接收資料
buffer = []
while True:
    #每次最多接收1k位元組
    d = s.recv(1024)
    if d:
        buffer.append(d)
    else:
        break
data = b``.join(buffer)

接收資料時,呼叫recv(max)方法,一次最多接收指定的位元組數,因此,在一個while迴圈中反覆接收,直到recv()返回空資料,表示接收完畢,退出迴圈

4、呼叫close()方法關閉Socket,結束網路通訊

#關閉連線
s.close()

儲存網頁內容到檔案

#把網頁內容儲存到sina.html檔案
header, html = data.split(b`

`, 1)
print(header.decode(`utf-8`))
#把接收的資料寫入檔案
with open(`sina.html`, `wb`) as f:
    f.write(html)

接收到的資料包括HTTP頭和網頁本身,只需要把HTTP頭和網頁分離一下,把HTTP頭列印出來,網頁內容儲存到檔案

只需要在瀏覽器中開啟這個sina.html檔案,就可以看到新浪的首頁了

HTTP/1.1 302 Moved Temporarily
Server: nginx
Date: Fri, 14 Dec 2018 08:07:21 GMT
Content-Type: text/html
Content-Length: 154
Connection: close
Location: https://www.sina.com.cn/
X-Via-CDN: f=edge,s=ctc.xiamen.ha2ts4.35.nb.sinaedge.com,c=125.91.244.232;
X-Via-Edge: 1544774841629e8f45b7d3cd64cde1173d1de

 

二、伺服器

伺服器程式首先要繫結一個埠並監聽來自其他客戶端的連線。如果某個客戶端連線過來了,伺服器就與該客戶端建立Socket連線,隨後的通訊就靠這個Socket連線了

一個Socket依賴4項:伺服器地址、伺服器埠、客戶端地址、客戶端埠來唯一確定一個Socket

伺服器需要同時響應多個客戶端的請求,每個連線都需要一個新的程式或者新的執行緒來處理,否則,伺服器一次就只能服務一個客戶端

1、建立一個基於IPv4和TCP協議的Socket

#建立一個基於IPv4和TCP協議的Socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

2、繫結監聽的地址和埠

#監聽埠
s.bind((`127.0.0.1`, 9999))

伺服器可能有多塊網路卡,可以繫結到某一塊網路卡的IP地址上,也可以用0.0.0.0繫結到所有的網路地址,還可以用127.0.0.1繫結到本機地址

127.0.0.1是一個特殊的IP地址,表示本機地址,如果繫結到這個地址,客戶端必須同時在本機執行才能連線,也就是說,外部的計算機無法連線進來

埠號需要預先指定。因為我們寫的這個服務不是標準服務,所以用9999這個埠號

注意:小於1024的埠號必須要有管理員許可權才能繫結

3、呼叫listen()方法監聽埠

傳入的引數指定等待連線的最大數量

s.listen(5)
print(`Waiting for connection...`)

4、通過一個永久迴圈接受來自客戶端的連線

while True:
    #接受一個新連線
    sock, addr = s.accept()
    #建立新執行緒來處理TCP連線
    t = threading.Thread(target=tcplink, args=(sock, addr))
    t.start()

accept()會等待並返回一個客戶端的連線

5、建立新程式處理連線

每個連線都必須建立新執行緒(或程式)來處理,否則,單執行緒在處理連線的過程中,無法接受其他客戶端的連線

#建立新程式處理連線
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 close.` % addr)

連線建立後,伺服器首先發一條歡迎訊息,然後等待客戶端資料,並加上Hello再傳送給客戶端

如果客戶端傳送了exit字串,就直接關閉連線

6、編寫一個客戶端程式測試這個伺服器程式

#tcp_client.py
import socket

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`Alice`, b`Bob`, b`Jack`]:
    s.send(data)
    print(s.recv(1024).decode(`utf-8`))
s.send(b`exit`)
s.close()

 

分別執行tcp_client.py和tcp_server.py

tcp_server.py的結果:
Waiting for connection...
Accept new connection from 127.0.0.1:60647...
Connection from 127.0.0.1:60647 close.

tcp_client.py的結果:
Welcome
Hello, Alice!
Hello, Bob!
Hello, Jack!

 

參考資料:

1、廖雪峰學習官網

2、寧致樂水的博文:https://blog.csdn.net/qq_31603575/article/details/80089707

相關文章