第十章 python socker程式設計
10.1 弄懂HTTP、Socker、TCP這幾個概念
整個計算機網路都是由協議組成的。
A是client端,B是伺服器端,當A向B傳送請求時,資料傳輸的時候(平時使用requests、urllib請求資料),在我們作業系統和網路之中要經歷5層網路模型。
應用層:和具體的包相關,如果是requests,則走http協議,如果是一些郵件則走SMTP協議,如果用的FTP的包,則走FTP協議。
傳輸層:
網路層:從一個ip到另一個ip時,網路該如何發包
資料鏈路層:
物理層:
A傳送資料是從上到下的(拆包),B接受資料是從下到上的(組包)。
瀏覽器也是實現了http協議,http協議是構建與TCP、IP...協議之上。可以看到應用層有很多協議,HTTP、SMTP、DNS.......。
如果我們要實現TCP、UDP協議,自己寫協議比較複雜,一般都是作業系統提供的。作業系統提供了一個Socket,可以理解為一個API,不屬於任何一個協議,我們通過Socket可以和傳輸層打交道。
把Socket理解為一個插座,資料理解為電力,如果我們要充電,就需要插座,而且電在傳輸過程中要經過很多變壓器(傳送層下面的層),將裝置和插座連線起來,就可以使用電了。
在TCP協議上提供了一個介面(Socket),通過Socket和底層協議建立了一個連線。
HTTP協議是單向的,A傳送請求,B響應,但B不能主動傳送訊息給A。
但是TCP協議是雙向的,只要不主動斷開,就一直在存在聯絡。
Socket可以操控TCP,這樣就可以實現自己的協議,不需要去TCP協議上進行開發。這個在很多情況下是很實用的,比如聊天工具,會在應用層實現一套自己的協議,通過Socket和TCP打交道。
10.2 通過Socket實現client和Server通訊
Socket程式設計一般會涉及Socket_client和Socket_server程式設計。
左側是server端,右側是client端。
server端,比如寫一個web系統,部署到伺服器。
client端,比如常見的PC軟體、瀏覽器。
爬蟲是一個很典型的client端,請求server端的資料。
自己實現一個web server端,有個特點是必須隨時處於監聽和服務的狀態,因為不知道客戶端什麼時候傳送請求過來。
繫結:
協議(Socket本身是介面,用來連線應用層和傳輸層)、
地址(Socket可以監聽本地ip,也可以監聽對外Ip)
埠(每一個應用程式只能佔用一個埠,使用Socket指定埠,就可以把應用程式和埠繫結起來,這樣在多個應用程式下資料傳輸不會混亂,特定的應用程式使用某一個埠進行資料傳輸),相同的應用程式可以使用一個埠(多執行緒中運行了相同的程式)。
listen:
監聽Socker連線請求
繫結完之後就可以開始監聽,監聽之後呼叫accept函式會阻塞連線請求,直到client端發起連線請求(TCP,三次握手),成功後就建立一個Socket連線。然後執行recv函式獲取資料(需要client端send傳送資料過來),之後可以列印並做各種操作(服務端可以send資料給client端,理論上只要有Socket連線,Server端就可以一直向client端傳送資料,但是在HTTP請求中,一般只進行一次server和client之間的資料傳輸,之後連線就會關閉)。
可以看一個服務端的簡單例子
server:
import socket
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # AF_INET是ipv4,socket.SOCK_STREAM是一個協議
server.bind(('0.0.0.0', 8000))# IP地址,埠
server.listen()
sock, addr = server.accept() # accept 會返回一個sock和addr
#獲取從客戶端傳送的資料
data = sock.recv(1024) #1024=1k的資料
print(data.decode("utf8")) # data是byte型別
server.close()
sock.close()
bobby
client:
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8000)) # 要明確指明ip地址
client.send("bobby".encode("utf8"))
client.close()
這樣通訊就完成了,再修改一下,讓服務端傳送資料給client端
server:
import socket
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # AF_INET是ipv4,socket.SOCK_STREAM是一個協議
server.bind(('0.0.0.0', 8000))# IP地址,埠
server.listen()
sock, addr = server.accept() # accept 會返回一個sock和addr
#獲取從客戶端傳送的資料
data = sock.recv(1024) #1024=1k的資料
print(data.decode("utf8")) # data是byte型別
sock.send("hello {}".format(data.decode("utf8")).encode("utf8")) # 從client傳過來的資料是byte型別,先decode為str,再encode為byte,傳入client端
server.close()
sock.close()
bobby
client:
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8000)) # 要明確指明ip地址
client.send("bobby".encode("utf8"))
data = client.recv(1024)
print (data.decode("utf8"))
client.close()
hello bobby
10.3 Socker實現聊天和多使用者連線
server:
import socket
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # AF_INET是ipv4,socket.SOCK_STREAM是一個協議
server.bind(('0.0.0.0', 8000))# IP地址,埠
server.listen()
sock, addr = server.accept() # accept 會返回一個sock和addr
# server是用來監聽的,sock才是使用者連線的socket
#獲取從客戶端傳送的資料
#一次獲取1k的資料
while True:
data = sock.recv(1024) # 假設1024位元組就可以把資料取完
print(data.decode("utf8"))
re_data = input()
sock.send(re_data.encode("utf8"))
client:
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 對使用者來說,只需要一個client就可以了
client.connect(('127.0.0.1', 8000)) # 要明確指明ip地址
while True:
re_data = input()
client.send(re_data.encode("utf8"))
data = client.recv(1024)
print (data.decode("utf8"))
但是問題是server端只能接受一個請求,一個使用者進來後,就一直在while迴圈裡出不來。
如何實現多使用者連線呢,可以用執行緒,每一個socket是一個執行緒,主執行緒接受請求。
server:
import socket
import threading
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # AF_INET是ipv4,socket.SOCK_STREAM是一個協議
server.bind(('0.0.0.0', 8000))# IP地址,埠
server.listen()
# server是用來監聽的,sock才是使用者連線的socket
#獲取從客戶端傳送的資料
#一次獲取1k的資料
def handle_sock(sock, addr):
while True:
data = sock.recv(1024) # 假設1024位元組就可以把資料取完
print(data.decode("utf8"))
re_data = input()
sock.send(re_data.encode("utf8"))
while True:
sock, addr = server.accept()
# 用執行緒去處理新接收的連線(使用者)
client_thread = threading.Thread(target=handle_sock, args=(sock, addr)) # target必須為函式名
client_thread.start()
client:
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 對使用者來說,只需要一個client就可以了
client.connect(('127.0.0.1', 8000)) # 要明確指明ip地址
while True:
re_data = input()
client.send(re_data.encode("utf8"))
data = client.recv(1024)
print (data.decode("utf8"))
10.4 socket模擬http請求
requests ->urllib -> socket
requests 基於urllib ,urllib 基於socket,凡是網路相關的連線(比如資料庫連線,程序間的通訊等)最底層都是用socket來連線的。
#requests -> urlib -> socket
import socket
from urllib.parse import urlparse # urlparse是用來解析url的,不是用來發起請求的
def get_url(url):
#通過socket請求html
url = urlparse(url) # 需要先進行url解析
host = url.netloc # url主域名
path = url.path # url子路徑
if path == "": # 如果直接請求主域名
path = "/"
# 建立socket連線
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((host, 80)) # HTTP請求都是80埠
# http協議是基於TCP協議的
# GET {} 第一行必須這樣寫
client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(path, host).encode("utf8"))
data = b"" # 從server獲取的資料為byte
while True:
d = client.recv(1024) # 臨時獲取的資料
if d:
data += d
else: # 如果沒有資料,則退出
break
data = data.decode("utf8")
html_data = data.split("\r\n\r\n")[1] # 去掉header資訊
print(html_data)
client.close()
if __name__ == "__main__":
get_url("http://www.baidu.com")