Day14 - 網(wǎng)絡(luò)編程入門
計算機基礎(chǔ)
mark一本《計算機網(wǎng)絡(luò)》書籍
計算機網(wǎng)絡(luò)發(fā)展史
TCP/IP模型
協(xié)議畦攘,就是通信計算機雙方必須共同遵守的一組約定集绰,網(wǎng)絡(luò)協(xié)議三要素:語法错妖、語義、時序疚沐。協(xié)議族暂氯,就是一系列的協(xié)議及其構(gòu)成的通信模型,我們通常把這套東西成為TCP/IP模型亮蛔。與國際標準化組織發(fā)布的OSI/RM七層模型不同痴施,TCP/IP只是一個四層模型,自下而上依次是:網(wǎng)絡(luò)接口層究流、網(wǎng)絡(luò)層辣吃、傳輸層、應用層芬探。
IP神得,常被翻譯為網(wǎng)際協(xié)議,它服務(wù)于網(wǎng)絡(luò)層偷仿,主要實現(xiàn)尋址和路由的功能哩簿。接入網(wǎng)絡(luò)的每一臺及其都要有自己的IP地址,IP地址就是主機在計算機網(wǎng)絡(luò)上的身份標識酝静。由于IPv4地址匱乏卡骂,我們平常使用IP地址并不是全球唯一的IP地址,而是一個局域網(wǎng)中的內(nèi)部IP地址形入,通過網(wǎng)絡(luò)地址轉(zhuǎn)換服務(wù)我們也可以實現(xiàn)對網(wǎng)絡(luò)的訪問。計算機網(wǎng)絡(luò)有大量被我們稱為“路由器”的中繼設(shè)備缝左,它們會存儲轉(zhuǎn)發(fā)我們發(fā)送到網(wǎng)絡(luò)上的數(shù)據(jù)分組亿遂,讓從源頭發(fā)出的數(shù)據(jù)最終能夠找到傳送到的目的地通路,這項功能就是所謂的路由渺杉。
TCP全稱是傳輸控制協(xié)議蛇数,它是基于IP提供的尋址和路由服務(wù)而建立起來的負責實現(xiàn)端到端可靠傳輸?shù)膮f(xié)議,之所以將TCP成為可靠的傳輸協(xié)議是因為TCP向調(diào)用者承諾了三件事:
- 數(shù)據(jù)不傳丟不傳錯(利用握手是越,校驗耳舅,重傳機制)
- 流量控制(通過窗口活動匹配數(shù)據(jù)發(fā)送者和接受者之間的傳輸速度)
- 擁塞控制(通過RTT時間以及對滑動窗口的控制緩解網(wǎng)絡(luò)擁堵)
網(wǎng)絡(luò)應用模式
- C/S和B/S模式,即通過客戶端到服務(wù)器倚评,或者瀏覽器到服務(wù)器浦徊,進行網(wǎng)絡(luò)訪問。
- 去中心化的網(wǎng)絡(luò)應用模式天梧。意思就是去中心化的網(wǎng)絡(luò)應用通常沒有固定的服務(wù)器或者固定的客戶端盔性,所有應用的使用者既可以作為資源的提供者也可以作為資源的訪問者。
基于HTTP協(xié)議的網(wǎng)絡(luò)資源訪問
HTTP超文本傳輸協(xié)議
HTTP是一種用于分布式呢岗、協(xié)作式和超媒體信息系統(tǒng)的應用層協(xié)議冕香,它是萬維網(wǎng)數(shù)據(jù)通信的基礎(chǔ)蛹尝,設(shè)計HTTP最初目的是為了提供一種發(fā)布和接受HTML頁面的方法,通過HTTP或者HTTPS(超文本傳輸安全協(xié)議)請求的資源由URL(統(tǒng)一資源標識符)來標識悉尾。
ps.有空翻翻突那,Mark一篇博客《HTTP協(xié)議入門》
JSON格式
json是一種輕量級的數(shù)據(jù)交換語言,為了讓人容易閱讀的基礎(chǔ)上构眯,用來傳輸由屬性值或者序列性的值組成的數(shù)據(jù)對象愕难。
requests庫
requests 是一個基于HTTP協(xié)議來使用網(wǎng)絡(luò)的第三方庫,它可以很方便使用HTTP鸵赖,避免安全缺陷务漩、冗余代碼以及重復造輪子。
敲一個例子它褪,下載圖片
from time import time
from threading import Thread
import requests
class DownloadHanlder(Thread):
def __init__(self, url):
super().__init__()
self.url = url
def run(self):
# rfind 返回字符串最后一次出現(xiàn)的位置饵骨,從右向左查詢,沒有匹配到則返回-1
filename = self.url[self.url.rfind('/') + 1:]
print(filename)
resp = requests.get(self.url)
with open('data/' + filename, 'wb') as f:
f.write(resp.content)
def main():
resp = requests.get(
'http://api.tianapi.com/meinv/?key=19f7261742115ad6d5656318d2c4b778&num=10')
data_model = resp.json()
for mm_dict in data_model['newslist']:
url = mm_dict['picUrl']
DownloadHanlder(url).start()
if __name__ == '__main__':
main()
基于傳輸層協(xié)議的套接字編程
套接字就是一套用C語言攜程的應用程序開發(fā)庫茫打,主要用于實現(xiàn)進程間的通信和網(wǎng)絡(luò)編程居触,在網(wǎng)絡(luò)應用開發(fā)中被廣泛使用。在Python中也可以基于套接字來使用傳輸層提供的傳輸服務(wù)老赤,并基于此開發(fā)自己的網(wǎng)絡(luò)應用轮洋。實際開發(fā)中使用的套接字分三類:流套接字(TCP套接字)、數(shù)據(jù)報套接字和原始套接字抬旺。
TCP套接字
TCP套接字就是使用TCP協(xié)議提供的傳輸服務(wù)來實現(xiàn)網(wǎng)絡(luò)通信的編程接口弊予。在Python中可通過創(chuàng)建socket對象并指定type屬性為SOCK_STREAM來使用TCP套接字。由于一臺主機可有多個IP地址开财,而且很可能會配置多個不同服務(wù)汉柒,所以作為服務(wù)器端的程序,需要在創(chuàng)建套接字對象后將其綁定到指定的IP地址和端口上责鳍。
實現(xiàn)一個提供時間日期的服務(wù)器(注釋代碼很詳細就不刪掉了)
from socket import socket, SOCK_STREAM, AF_INET
from datetime import datetime
def main():
# 1.創(chuàng)建套接字對象并指定使用哪種傳輸服務(wù)
# 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地址和端口(端口用于區(qū)分不同的服務(wù))
# 同一時間在同一個端口上只能綁定一個服務(wù)否則報錯
server.bind(('192.168.1.2', 6789))
# 3.開啟監(jiān)聽 - 監(jiān)聽客戶端連接到服務(wù)器
# 參數(shù)512可以理解為連接隊列的大小
server.listen(512)
print('服務(wù)器啟動開始監(jiān)聽...')
while True:
# 4.通過循環(huán)接收客戶端的連接并作出相應的處理(提供服務(wù))
# accept方法是一個阻塞方法如果沒有客戶端連接到服務(wù)器代碼不會向下執(zhí)行
# accept方法返回一個元組其中的第一個元素是客戶端對象
# 第二個元素是連接到服務(wù)器的客戶端的地址(由IP和端口兩部分構(gòu)成)
client, addr = server.accept()
print(str(addr) + '連接到了服務(wù)器.')
# 5.發(fā)送數(shù)據(jù)
client.send(str(datetime.now()).encode('utf-8'))
# 6.斷開連接
client.close()
if __name__ == '__main__':
main()
實現(xiàn)客戶端
from socket import socket
def main():
# 1.創(chuàng)建套接字對象默認使用IPv4和TCP協(xié)議
client = socket()
# 2.連接到服務(wù)器(需要指定IP地址和端口)
client.connect(('192.168.1.2', 6789))
# 3.從服務(wù)器接收數(shù)據(jù)
print(client.recv(1024).decode('utf-8'))
client.close()
if __name__ == '__main__':
main()
上述例子沒有使用多線程或者異步I/O的處理方式碾褂,只能一個一個客戶執(zhí)行。
下面設(shè)計一個多線程處理多用戶請求的服務(wù)器历葛,服務(wù)器向用戶發(fā)送一張圖片正塌。
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'] = '131.jpg'
# JSON是純文本不能攜帶二進制數(shù)據(jù)
# 所以圖片的二進制數(shù)據(jù)要處理成base64編碼
my_dict['filedata'] = data
# 通過dumps函數(shù)將字典處理成JSON字符串
json_str = dumps(my_dict)
# 發(fā)送JSON字符串
self.cclient.send(json_str.encode('utf-8'))
self.cclient.close()
# 1.創(chuàng)建套接字對象并指定使用哪種傳輸服務(wù)
server = socket()
# 2.綁定IP地址和端口(區(qū)分不同的服務(wù))
server.bind(('127.0.0.1', 5566))
# 3.開啟監(jiān)聽 - 監(jiān)聽客戶端連接到服務(wù)器
server.listen(512)
print('服務(wù)器啟動開始監(jiān)聽...')
with open('131.jpg', 'rb') as f:
# 將二進制數(shù)據(jù)處理成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(('127.0.0.1', 5566))
# 定義一個保存二進制數(shù)據(jù)的對象
in_data = bytes()
# 由于不知道服務(wù)器發(fā)送的數(shù)據(jù)有多大每次接收1024字節(jié)
data = client.recv(1024)
while data:
# 將收到的數(shù)據(jù)拼接起來
in_data += data
data = client.recv(1024)
# 將收到的二進制數(shù)據(jù)解碼成JSON字符串并轉(zhuǎn)換成字典
# loads函數(shù)的作用就是將JSON字符串轉(zhuǎn)成字典對象
my_dict = loads(in_data.decode('utf-8'))
filename = my_dict['filename']
filedata = my_dict['filedata'].encode('utf-8')
with open('data/' + filename, 'wb') as f:
# 將base64格式的數(shù)據(jù)解碼成二進制數(shù)據(jù)并寫入文件
f.write(b64decode(filedata))
print('圖片已保存.')
if __name__ == '__main__':
main()
地址改成127.0.0.1,否則會報下面的錯誤恤溶。沒搞清楚為啥乓诽。
OSError: [WinError 10049] 在其上下文中,該請求的地址無效咒程。
ps.JSON并不能攜帶二進制數(shù)據(jù)问裕,因此對圖片的二進制數(shù)據(jù)進行了Base64編碼的處理。Base64是一種用64個字符表示所有二進制數(shù)據(jù)的編碼方式孵坚,通過將二進制數(shù)據(jù)每6位一組的方式重新組織粮宛,剛好可以使用0~9的數(shù)字窥淆、大小寫字母以及“+”和“/”總共64個字符表示從000000到111111的64種狀態(tài)。
UDP套接字
UDP巍杈,即用戶數(shù)據(jù)報協(xié)議忧饭。TCP和UDP都是提供端到端傳輸服務(wù)的協(xié)議。UDP不對傳輸?shù)目煽啃院涂蛇_性做出任何承諾從而避免了TCP中握手和重傳的開銷筷畦,所以在強調(diào)性能和不要求數(shù)據(jù)完整性的場景中(例如傳輸網(wǎng)絡(luò)音視頻數(shù)據(jù))词裤,UDP可能是更好的選擇。
網(wǎng)絡(luò)應用開發(fā)
發(fā)送電子郵件
發(fā)送郵件使用的是SMTP(簡單郵件傳輸協(xié)議)鳖宾,SMTP是建立在TCP提供的可靠數(shù)據(jù)傳輸服務(wù)的基礎(chǔ)上的應用級協(xié)議吼砂,它規(guī)定郵件的發(fā)送者如何跟發(fā)送郵件的服務(wù)器進行通信的細節(jié),而Python中的smtplib模塊將這些操作簡化成幾個函數(shù)鼎文。
利用Python發(fā)送郵件渔肩,可以參考菜鳥教程的文章很詳細。《Python SMTP發(fā)送郵件》
發(fā)送短信
發(fā)送短信拇惋,其實就是看使用的短信平臺提供的API周偎,根據(jù)API寫代碼就完事了。
mark一下示例撑帖,用到的時候會寫就完事了蓉坎。
import urllib.parse
import http.client
import json
def main():
host = "106.ihuyi.com"
sms_send_uri = "/webservice/sms.php?method=Submit"
# 下面的參數(shù)需要填入自己注冊的賬號和對應的密碼
params = urllib.parse.urlencode({'account': '你自己的賬號', 'password' : '你自己的密碼', '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()