基於TCP協議的Socket網路程式設計( )
TCP程式設計
Socket是網路程式設計的一個抽象概念。通常我們用一個Socket表示“開啟了一個網路連結”,而開啟一個Socket需要知道目標計算機的IP地址和埠號,再指定協議型別即可。
今天我們要在Python中,基於TCP協議進行Socket網路程式設計
客戶端
大多數連線都是可靠的TCP連線。建立TCP連線時,主動發起連線的叫客戶端,被動響應連線的叫伺服器。
舉個例子,當我們在瀏覽器中訪問百度時,我們自己的計算機就是客戶端,瀏覽器會主動向百度的伺服器發起連線。如果一切順利,百度的伺服器接受了我們的連線,一個TCP連線就建立起來的,後面的通訊就是傳送網頁內容了。
言歸正傳,如果我們需要進行網路通訊,就必須要建立一個基於TCP連線的Socket:
#######################
#########客戶端#########
#######################
import socket#匯入socket庫
import time, threading#匯入threading模組
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #建立一個socket
s.connect(('www.baidu.com', 80))#建立連線
建立Socket時,AF_INET指定使用IPv4協議,如果要用更先進的IPv6,就指定為AF_INET6。SOCK_STREAM指定使用面向流的TCP協議,這樣,一個Socket物件就建立成功,但是還沒有建立連線。
客戶端要主動發起TCP連線,必須知道伺服器的IP地址和埠號。百度網站的IP地址可以用域名www.baidu.com自動轉換到IP地址,但是怎麼知道百度伺服器,它作為伺服器,提供什麼樣的服務,埠號就必須固定下來。由於我們想要訪問網頁,因此百度提供網頁服務的伺服器必須把埠號固定在80埠,因為80埠是Web服務的標準埠。其他服務都有對應的標準埠號,例如SMTP服務是25埠,FTP服務是21埠,等等。埠號小於1024的是Internet標準服務的埠,埠號大於1024的,可以任意使用。
因此,我們連線百度伺服器的程式碼如下
注意引數是一個tuple(元祖),包含地址和埠號。
建立TCP連線後,我們就可以向百度伺服器傳送請求,要求返回首頁的內容:
s.send(b'GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n')
#傳送資料
TCP連線建立的是雙向通道,雙方都可以同時給對方發資料。但是誰先發誰後發,怎麼協調,要根據具體的協議來決定。例如,HTTP協議規定客戶端必須先發請求給伺服器,伺服器收到後才發資料給客戶端。
傳送的文字格式必須符合HTTP標準,如果格式沒問題,接下來就可以接收百度伺服器返回的資料了:
#接收資料
buffer = []
while True:
#每次最多接受1kb
d = s.recv(1024)#一次最多接受指定的位元組數
if d:
buffer.append(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) #將HTTP首部和網頁分離
print(header.decode('utf-8'))
#把接收的資料寫入檔案
with open('baidu.html','wb') as f:
f.write(html);
整體的客戶端程式碼如下
#######################
#########客戶端#########
#######################
import socket#匯入socket庫
import time, threading#匯入threading模組
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #建立一個socket
s.connect(('www.baidu.com', 80))#建立連線
s.send(b'GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n')#傳送資料
#s.connect(('www.sina.com.cn', 80))#新浪
#s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')#傳送資料
#接收資料
buffer = []
while True:
#每次最多接受指定的位元組數,此處最多接受1kb
d = s.recv(1024)
if d:
buffer.append(d)
else:
break
data = b''.join(buffer)
#關閉連線
s.close()
header,html = data.split(b'\r\n\r\n',1) #將HTTP首部和網頁分離
print(header.decode('utf-8'))
#把接收的資料寫入檔案
with open('baidu.html', 'wb') as f:
f.write(html);
#with open('sina.html','wb') as f:
#f.write(html)
正確執行結果如下:
接受的網頁資料成功寫入了一個html檔案中
html檔案內容
- 開始的時候因為程式碼縮排和傳參字串少個空格的問題,結果一直請求失敗,無法接受正確的網頁資訊
其中with在檔案操作上的用法非常巧妙,以前不太熟悉,看的時候很困惑,所以特別瞭解了一下
With語句是什麼?
關於with我已經單獨整理到這篇部落格中:
Python中With的用法
有一些任務,可能事先需要設定,事後做清理工作。對於這種場景,Python的with語句提供了一種非常方便的處理方式。其中一個很好的例子是檔案處理,你需要獲取一個檔案控制程式碼,從檔案中讀取資料,然後關閉檔案控制程式碼。
如果不用with語句,程式碼如下:
file = open("/tmp/foo.txt")
data = file.read()
file.close()
這裡有兩個問題。一是可能忘記關閉檔案控制程式碼;二是檔案讀取資料發生異常,沒有進行任何處理。下面是處理異常的加強版本:
file = open("/tmp/foo.txt")
try:
data = file.read()
finally:
file.close()
這段程式碼執行良好,但是太冗長。這時候with便體現出了優勢。 除了有更優雅的語法,with還可以很好的處理上下文環境產生的異常。下面是with版本的程式碼:
with open("/tmp/foo.txt") as file:
data = file.read()
是不是很簡單?
但是如果對with工作原理不熟悉的通許可能會和剛才的我一樣,不懂其中原理
那麼下面我們簡單看一下with的工作原理
with是如何工作的?
基本思想是:with所求值的物件必須有一個enter()方法,一個exit()方法。
緊跟with**後面的語句被求值後,返回物件的**__enter__()方法被呼叫,這個方法的返回值將被賦值給as後面的變數。當with後面的程式碼塊全部被執行完之後,將呼叫前面返回物件的exit()方法。
下面是一個例子
######################
########with()##########
######################
class Sample:
def __enter__(self):
print("in __enter__")
return "Foo"
def __exit__(self, exc_type, exc_val, exc_tb):
#exc_type: 錯誤的型別
#exc_val: 錯誤型別對應的值
#exc_tb: 程式碼中錯誤發生的位置
print("in __exit__")
def get_sample():
return Sample()
with get_sample() as sample:
print("Sample: " ,sample)
執行程式碼,輸出如下
分析執行過程:
- 進入這段程式,首先建立Sample類,完成它的兩個成員函式enter ()、exit()的定義,然後順序向下定義get_sample()函式.
進入with語句,呼叫get_sample()函式,返回一個Sample()類的物件,此時就需要進入Sample()類中,可以看到
1. __enter__()方法先被執行 2. __enter__()方法返回的值 - 這個例子中是"Foo",賦值給變數'sample' 3. 執行with中的程式碼塊,列印變數"sample",其值當前為 "Foo" 4. 最後__exit__()方法被呼叫
完整執行細節的除錯過程請看gif:
這裡只做了有限的簡單說明,關於python中with用法詳細參考:
淺談 Python 的 with 語句
python的with用法
說完with我們繼續回到,socket程式設計
伺服器
接下來我們實現伺服器端的過程
和客戶端程式設計相比,伺服器程式設計就要複雜一些。
伺服器程式首先要繫結一個埠並監聽來自其他客戶端的連線。如果某個客戶端連線過來了,伺服器就與該客戶端建立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字串,就直接關閉連線。
要測試這個伺服器程式,我們還需要單獨編寫一個客戶端程式:
##########
#測試 客戶端
##########
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'Michael', b'Tracy', b'Sarah']:
# 傳送資料:
s.send(data)
print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()
測試時,先啟動伺服器程式,再執行客戶端程式,這樣我們就能完整看到這次簡要的建立連線-傳送請求-接受響應報文-解析並輸出的網路通訊過程了
完整程式碼
- server.py
########################
##########伺服器#########
########################
import socket#匯入socket庫
import time, threading#匯入threading模組
#建立一個給予IPv4和TCP協議的Socket:
s = socket.socket(socket.AF_INET , socket.SOCK_STREAM)
#監聽埠
#繫結
s.bind(('127.0.0.1',9999))
#呼叫listen()函式監聽埠,傳入的引數指定等待連線的最大數量
s.listen(5)
print('Waiting for connection...')
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,xxx!再傳送給客戶端.入夥客戶端傳送了'exit'字串,就直接關閉連線
#伺服器通過一個死迴圈來接受來自客戶端的連結,accept()會等待並返回一個客戶端的連結:
while True:
# 接受一個新連線:
sock, addr = s.accept()
# 建立新執行緒來處理TCP連線:
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
#每個連結都必須建立新執行緒(或程式)來單獨處理,否則,單執行緒在處理連線的過程中,無法接受其他客戶的連結:
- 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'Michael', b'Tracy', b'Sarah']:
# 傳送資料:
s.send(data)
print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()
執行結果:
總結一下:
其實用TCP協議進行Socket程式設計在Python中十分簡單:
對於客戶端:
1.要主動連線伺服器的IP和指定埠,
對於伺服器
1.首先監聽指定埠
2.對每一個新的連線,建立一個執行緒或程式來處理。
3.通常,伺服器程式會無限執行下去。
注意:同一個埠,被一個Socket繫結了以後,就不能被別的Socket繫結了,即被佔用了,此時就會發生衝突
參考原始碼
do_tcp.py
相關文章
- 基於TCP/UDP的Socket程式設計,HTTP/HTTPS協議TCPUDP程式設計HTTP協議
- Java:基於TCP協議網路socket程式設計(實現C/S通訊)JavaTCP協議程式設計
- python網路-Socket之TCP程式設計(26)PythonTCP程式設計
- Python 網路資料傳輸協議 TCP 程式設計Python協議TCP程式設計
- 網路協議之:socket協議詳解之Socket和Stream Socket協議
- 網路協議之:socket協議詳解之Datagram Socket協議
- Python 基於 TCP 傳輸協議的網路通訊實現PythonTCP協議
- 網路協議之:socket協議詳解之Unix domain Socket協議AI
- Linux Socket C語言網路程式設計:TCP SocketLinuxC語言程式設計TCP
- Java 網路程式設計 – TCP協議基本步驟Java程式設計TCP協議
- UDP協議網路Socket程式設計(java實現C/S通訊案例)UDP協議程式設計Java
- TCP/IP協議 - 網路層TCP協議
- 網路通訊協議-TCP協議詳解!協議TCP
- Socket.D 基於訊息的響應式應用層網路協議協議
- 計算機網路之七:TCP協議(1)計算機網路TCP協議
- Python網路程式設計(socket模組、緩衝區、http協議)Python程式設計HTTP協議
- Python中兩種網路程式設計方式:Socket和HTTP協議Python程式設計HTTP協議
- TCP協議之網路延時TCP協議
- 網路程式設計協議(TCP和UDP協議,黏包問題)以及socketserver模組程式設計協議TCPUDPServer
- 計算機網路之TCP/IP協議簡介計算機網路TCP協議
- Socket程式設計,從TCP分析到建立web網站程式設計TCPWeb網站
- iOS中基於協議的路由設計iOS協議路由
- socket程式設計在TCP中的應用程式設計TCP
- 通過故事引申網路協議TCP協議TCP
- Android程式設計師必知必會的網路通訊傳輸層協議——UDP和TCPAndroid程式設計師協議UDPTCP
- 計算機網路之八:TCP協議(2) TCP可靠傳輸的實現計算機網路TCP協議
- 網路協議之:基於UDP的高速資料傳輸協議UDT協議UDP
- java多執行緒實現TCP網路Socket程式設計(C/S通訊)Java執行緒TCP程式設計
- 基於Java的Socket類Tcp網路程式設計實現實時聊天互動程式(一):QQ聊天介面的搭建JavaTCP程式設計
- 計算機網路學習筆記(10) TCP/IP協議棧 之TELNET協議計算機網路筆記TCP協議
- TCP/IP 協議及網路分層模型TCP協議模型
- Socket網路程式設計基礎與實踐:建立高效的網路通訊程式設計
- Socket程式設計基礎程式設計
- 網路程式設計UDP協議方式程式設計UDP協議
- (3)Tcp Socket程式設計的封裝類 TcpListener/TcpClientTCP程式設計封裝client
- Socket程式設計入門(基於Java實現)程式設計Java
- 傳輸控制協議/網際網路協議(TCP / IP)是什麼意思?-VeCloud協議TCPCloud
- socket網路程式設計程式設計