1. 程式人生 > 實用技巧 >第十章 python socker程式設計

第十章 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")