【Python入門基礎】網路程式設計

ZoomToday發表於2020-12-16

TCP/IP模型

  實現網路通訊的基礎是網路通訊協議,這些協議通常是由網際網路工程任務組(IETF)制定的。所謂“協議”就是通訊計算機雙方必須共同遵從的一組約定,例如怎樣建立連線、怎樣互相識別等,網路協議的三要素是:語法、語義和時序。構成我們今天使用的Internet的基礎的是TCP/IP協議族,所謂協議族就是一系列的協議及其構成的通訊模型,我們通常也把這套東西稱為TCP/IP模型。與國際標準化組織釋出的OSI/RM這個七層模型不同,TCP/IP是一個四層模型,也就是說,該模型將我們使用的網路從邏輯上分解為四個層次,自底向上依次是:網路介面層、網路層、傳輸層和應用層。
在這裡插入圖片描述
  IP通常被翻譯為網際協議,它服務於網路層,主要實現了定址和路由的功能。接入網路的每一臺主機都需要有自己的IP地址,IP地址就是主機在計算機網路上的身份標識。當然由於IPv4地址的匱乏,我們平常在家裡、辦公室以及其他可以接入網路的公共區域上網時獲得的IP地址並不是全球唯一的IP地址,而是一個區域網(LAN)中的內部IP地址,通過網路地址轉換(NAT)服務我們也可以實現對網路的訪問。計算機網路上有大量的被我們稱為“路由器”的網路中繼裝置,它們會儲存轉發我們傳送到網路上的資料分組,讓從源頭髮出的資料最終能夠找到傳送到目的地通路,這項功能就是所謂的路由。

  TCP全稱傳輸控制協議,它是基於IP提供的定址和路由服務而建立起來的負責實現端到端可靠傳輸的協議,之所以將TCP稱為可靠的傳輸協議是因為TCP向呼叫者承諾了三件事情:

  1. 資料不傳丟不傳錯(利用握手、校驗和重傳機制可以實現)。
  2. 流量控制(通過滑動視窗匹配資料傳送者和接收者之間的傳輸速度)。
  3. 擁塞控制(通過RTT時間以及對滑動視窗的控制緩解網路擁堵)。

網路應用模式

  1. C/S模式和B/S模式。這裡的C指的是Client(客戶端),通常是一個需要安裝到某個宿主作業系統上的應用程式;而B指的是Browser(瀏覽器),它幾乎是所有圖形化作業系統都預設安裝了的一個應用軟體;通過C或B都可以實現對S(伺服器)的訪問。
  2. 去中心化的網路應用模式。不管是B/S還是C/S都需要伺服器的存在,伺服器就是整個應用模式的中心,而去中心化的網路應用通常沒有固定的伺服器或者固定的客戶端,所有應用的使用者既可以作為資源的提供者也可以作為資源的訪問者。

基於HTTP協議的網路資源訪問

HTTP(超文字傳輸協議)

  HTTP是超文字傳輸協議的簡稱,超文字傳輸協議是一種用於分散式、協作式和超媒體資訊系統的應用層協議,它是全球資訊網資料通訊的基礎,設計HTTP最初的目的是為了提供一種釋出和接收HTML頁面的方法,通過HTTP或者HTTPS(超文字傳輸安全協議)請求的資源由URI(統一資源識別符號)來標識。

JSON

  JSONJavaScript Object Notation)是一種輕量級的資料交換語言,該語言以易於讓人閱讀的文字(純文字)為基礎,用來傳輸由屬性值或者序列性的值組成的資料物件。儘管JSON是最初只是Javascript中一種建立物件的字面量語法,但它在當下更是一種獨立於語言的資料格式,很多程式語言都支援JSON格式資料的生成和解析,Python內建的json模組也提供了這方面的功能。由於JSON是純文字,它和XML一樣都適用於異構系統之間的資料交換,而相較於XML,JSON顯得更加的輕便和優雅。
XML

<?xml version="1.0" encoding="UTF-8"?>
<message>
	<from>Alice</from>
	<to>Bob</to>
	<content>Hello</content>
</message>

JSON

{
    "from": "Alice",
    "to": "Bob",
    "content": "Hello"
}

requests庫

  requests是一個基於HTTP協議來使用網路的第三方庫。
通過requests實現一個訪問網路資料介面並從網頁下載圖片到本地,使用天行資料提供的網路API。

from threading import Thread

import requests


# 繼承Thread類建立自定義的執行緒類
class DownloadHanlder(Thread):

    def __init__(self, url):
        super().__init__()
        self.url = url

    def run(self):
        filename = self.url[self.url.rfind('/') + 1:]
        resp = requests.get(self.url)
        with open('./pic/' + filename, 'wb') as f:
            f.write(resp.content)


def main():
    # 通過requests模組的get函式獲取網路資源
    # 下面的程式碼中使用了天行資料介面提供的網路API
    # 要使用該資料介面需要在天行資料的網站上註冊
    # 申請使用‘抽取網頁圖片’介面
    # 然後用自己的Key替換掉下面程式碼的中APIKey即可
    resp = requests.get(
        'http://api.tianapi.com/txapi/htmlpic/index?key=APIKey&url=https://www.huceo.com/post/481.html')
    # 將伺服器返回的JSON格式的資料解析為字典
    data_model = resp.json()
    for mm_dict in data_model['newslist']:
        url = mm_dict['picUrl']
        # 通過多執行緒的方式實現圖片下載
        DownloadHanlder(url).start()


if __name__ == '__main__':
    main()

基於傳輸層協議的套接字程式設計

  套接字是用一套C語言寫成的應用程式開發庫,主要用於實現程式間通訊和網路程式設計,在網路應用開發中被廣泛使用。在Python中也可以基於套接字來使用傳輸層提供的傳輸服務,並基於此開發自己的網路應用。實際開發中使用的套接字可以分為三類:流套接字(TCP套接字)、資料包套接字和原始套接字。

TCP套接字

  所謂TCP套接字就是使用TCP協議提供的傳輸服務來實現網路通訊的程式設計介面。在Python中可以通過建立socket物件並指定type屬性為SOCK_STREAM來使用TCP套接字。由於一臺主機可能擁有多個IP地址,而且很有可能會配置多個不同的服務,所以作為伺服器端的程式,需要在建立套接字物件後將其繫結到指定的IP地址和埠上。這裡的埠並不是物理裝置而是對IP地址的擴充套件,用於區分不同的服務,例如我們通常將HTTP服務跟80埠繫結,而MySQL資料庫服務預設繫結在3306埠,這樣當伺服器收到使用者請求時就可以根據埠號來確定到底使用者請求的是HTTP伺服器還是資料庫伺服器提供的服務。埠的取值範圍是0~65535,而1024以下的埠我們通常稱之為“著名埠”(留給像FTP、HTTP、SMTP等“著名服務”使用的埠,有的地方也稱之為“周知埠”),自定義的服務通常不使用這些埠,除非自定義的是HTTP或FTP這樣的著名服務。
提供時間日期的伺服器

from socket import socket, SOCK_STREAM, AF_INET
from datetime import datetime


def main():
    # 1.建立套接字物件並指定使用哪種傳輸服務
    # family=AF_INET - IPv4地址
    # family=AF_INET6 - IPv6地址
    # type=SOCK_STREAM - TCP套接字
    # type=SOCK_DGRAM - UDP套接字
    # type=SOCK_RAW - 原始套接字
    server = socket(family=AF_INET, type=SOCK_STREAM)
    # 2.繫結IP地址和埠(埠用於區分不同的服務)
    # 同一時間在同一個埠上只能繫結一個服務否則報錯
    server.bind(('192.168.75.1', 6789))
    # 3.開啟監聽 - 監聽客戶端連線到伺服器
    # 引數512可以理解為連線佇列的大小
    server.listen(512)
    print('伺服器啟動開始監聽...')
    while True:
        # 4.通過迴圈接收客戶端的連線並作出相應的處理(提供服務)
        # accept方法是一個阻塞方法如果沒有客戶端連線到伺服器程式碼不會向下執行
        # accept方法返回一個元組其中的第一個元素是客戶端物件
        # 第二個元素是連線到伺服器的客戶端的地址(由IP和埠兩部分構成)
        client, addr = server.accept()
        print(str(addr) + '連線到了伺服器.')
        # 5.傳送資料
        client.send(str(datetime.now()).encode('utf-8'))
        # 6.斷開連線
        client.close()


if __name__ == '__main__':
    main()

通過Windows系統的telnet來訪問該伺服器
在這裡插入圖片描述
客戶端訪問

from socket import socket


def main():
    # 1.建立套接字物件預設使用IPv4和TCP協議
    client = socket()
    # 2.連線到伺服器(需要指定IP地址和埠)
    client.connect(('192.168.1.2', 6789))
    # 3.從伺服器接收資料
    print(client.recv(1024).decode('utf-8'))
    client.close()


if __name__ == '__main__':
    main()

  上面的伺服器並沒有使用多執行緒或者非同步I/O的處理方式,這也就意味著當伺服器與一個客戶端處於通訊狀態時,其他的客戶端只能排隊等待。很顯然,這樣的伺服器並不能滿足我們的需求,我們需要的伺服器是能夠同時接納和處理多個使用者請求的。下面我們來設計一個使用多執行緒技術處理多個使用者請求的伺服器,該伺服器會向連線到伺服器的客戶端傳送一張圖片。
伺服器端

from socket import socket, SOCK_STREAM, AF_INET
from base64 import b64encode
from json import dumps
from threading import Thread


def main():
    
    # 自定義執行緒類
    class FileTransferHandler(Thread):

        def __init__(self, cclient):
            super().__init__()
            self.cclient = cclient

        def run(self):
            my_dict = {}
            my_dict['filename'] = 'guido.jpg'
            # JSON是純文字不能攜帶二進位制資料
            # 所以圖片的二進位制資料要處理成base64編碼
            my_dict['filedata'] = data
            # 通過dumps函式將字典處理成JSON字串
            json_str = dumps(my_dict)
            # 傳送JSON字串
            self.cclient.send(json_str.encode('utf-8'))
            self.cclient.close()

    # 1.建立套接字物件並指定使用哪種傳輸服務
    server = socket()
    # 2.繫結IP地址和埠(區分不同的服務)
    server.bind(('192.168.1.2', 5566))
    # 3.開啟監聽 - 監聽客戶端連線到伺服器
    server.listen(512)
    print('伺服器啟動開始監聽...')
    with open('guido.jpg', 'rb') as f:
        # 將二進位制資料處理成base64再解碼成字串
        data = b64encode(f.read()).decode('utf-8')
    while True:
        client, addr = server.accept()
        # 啟動一個執行緒來處理客戶端的請求
        FileTransferHandler(client).start()


if __name__ == '__main__':
    main()

客戶端

from socket import socket
from json import loads
from base64 import b64decode


def main():
    client = socket()
    client.connect(('192.168.1.2', 5566))
    # 定義一個儲存二進位制資料的物件
    in_data = bytes()
    # 由於不知道伺服器傳送的資料有多大每次接收1024位元組
    data = client.recv(1024)
    while data:
        # 將收到的資料拼接起來
        in_data += data
        data = client.recv(1024)
    # 將收到的二進位制資料解碼成JSON字串並轉換成字典
    # loads函式的作用就是將JSON字串轉成字典物件
    my_dict = loads(in_data.decode('utf-8'))
    filename = my_dict['filename']
    filedata = my_dict['filedata'].encode('utf-8')
    with open('/Users/Hao/' + filename, 'wb') as f:
        # 將base64格式的資料解碼成二進位制資料並寫入檔案
        f.write(b64decode(filedata))
    print('圖片已儲存.')


if __name__ == '__main__':
    main()

JSON作為資料傳輸的格式(通過JSON格式對傳輸的資料進行了序列化和反序列化的操作),但是JSON並不能攜帶二進位制資料,因此對圖片的二進位制資料進行了Base64編碼的處理。Base64是一種用64個字元表示所有二進位制資料的編碼方式,通過將二進位制資料每6位一組的方式重新組織,剛好可以使用0~9的數字、大小寫字母以及“+”和“/”總共64個字元表示從000000111111的64種狀態。

UDP套接字

  傳輸層除了有可靠的傳輸協議TCP之外,還有一種非常輕便的傳輸協議叫做使用者資料包協議,簡稱UDP。TCP和UDP都是提供端到端傳輸服務的協議,二者的差別就如同打電話和發簡訊的區別,後者不對傳輸的可靠性和可達性做出任何承諾從而避免了TCP中握手和重傳的開銷,所以在強調效能和而不是資料完整性的場景中(例如傳輸網路音視訊資料),UDP可能是更好的選擇。可能大家會注意到一個現象,就是在觀看網路視訊時,有時會出現卡頓,有時會出現花屏,這無非就是部分資料傳丟或傳錯造成的。

網路應用開發

傳送電子郵件

from smtplib import SMTP
from email.header import Header
from email.mime.text import MIMEText


def main():
    # 請自行修改下面的郵件傳送者和接收者
    sender = 'XXX@163.com'
    receivers = ['XXX@qq.com','XXX@163.com']
    # 郵件正文
    message = MIMEText('用Python傳送郵件.', 'plain', 'utf-8')
    message['From'] = '我自己<XXX@163.com>'
    message['To'] = '%s'%receivers
    # 郵件標題
    message['Subject'] = '郵件報告郵件報告'
    smtper = SMTP('smtp.163.com')
    # 請自行修改下面的授權碼,開啟郵箱獲取pop3/SMTP授權碼
    smtper.login(sender, '授權碼')
    smtper.sendmail(sender, receivers, message.as_string())
    print('郵件傳送完成!')


if __name__ == '__main__':
    main()

傳送帶有附件的郵件

from smtplib import SMTP
from email.header import Header
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart

import urllib


def main():
    # 建立一個帶附件的郵件訊息物件
    message = MIMEMultipart()
    
    # 建立文字內容
    text_content = MIMEText('附件中有本月資料請查收', 'plain', 'utf-8')
    message['Subject'] = Header('本月資料', 'utf-8')
    # 將文字內容新增到郵件訊息物件中
    message.attach(text_content)

    # 讀取檔案並將檔案作為附件新增到郵件訊息物件中
    with open('/Users/Hao/Desktop/hello.txt', 'rb') as f:
        txt = MIMEText(f.read(), 'base64', 'utf-8')
        txt['Content-Type'] = 'text/plain'
        txt['Content-Disposition'] = 'attachment; filename=hello.txt'
        message.attach(txt)
    # 讀取檔案並將檔案作為附件新增到郵件訊息物件中
    with open('/Users/Hao/Desktop/彙總資料.xlsx', 'rb') as f:
        xls = MIMEText(f.read(), 'base64', 'utf-8')
        xls['Content-Type'] = 'application/vnd.ms-excel'
        xls['Content-Disposition'] = 'attachment; filename=month-data.xlsx'
        message.attach(xls)
    
    # 建立SMTP物件
    smtper = SMTP('smtp.126.com')
    # 開啟安全連線
    # smtper.starttls()
    sender = 'abcdefg@126.com'
    receivers = ['uvwxyz@qq.com']
    # 登入到SMTP伺服器
    # 請注意此處不是使用密碼而是郵件客戶端授權碼進行登入
    smtper.login(sender, 'secretpass')
    # 傳送郵件
    smtper.sendmail(sender, receivers, message.as_string())
    # 與郵件伺服器斷開連線
    smtper.quit()
    print('傳送完成!')


if __name__ == '__main__':
    main()

傳送簡訊

  傳送簡訊也是專案中常見的功能,網站的註冊碼、驗證碼、營銷資訊基本上都是通過簡訊來傳送給使用者的。在下面的程式碼中我們使用了互億無線簡訊平臺(該平臺為註冊使用者提供了50條免費簡訊以及常用開發語言傳送簡訊的demo,可以登入該網站並在使用者自服務頁面中對簡訊進行配置)提供的API介面實現了傳送簡訊的服務

import urllib.parse
import http.client
import json


def main():
    host  = "106.ihuyi.com"
    sms_send_uri = "/webservice/sms.php?method=Submit"
    # 下面的引數需要填入自己申請介面APIID和APIKEY
    params = urllib.parse.urlencode({'account': 'APIID', 'password' : 'APIKEY', 'content': '您的驗證碼是:147258。請不要把驗證碼洩露給其他人。', 'mobile': '接收者的手機號', 'format':'json' })
    print(params)
    headers = {'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'text/plain'}
    conn = http.client.HTTPConnection(host, port=80, timeout=30)
    conn.request('POST', sms_send_uri, params, headers)
    response = conn.getresponse()
    response_str = response.read()
    jsonstr = response_str.decode('utf-8')
    print(json.loads(jsonstr))
    conn.close()


if __name__ == '__main__':
    main()

相關文章