勿驕勿燥,仍是要定下心學習,還有有些沒定下心python
1.基礎知識算法
2.tcp與udp協議編程
3.網絡套接字json
4.基於c/s結構的服務器客戶端的實驗小程序
開始今日份總結設計模式
1.基礎知識瀏覽器
現有的軟件,絕大多數是基於C/S結構,那麼就須要介紹網絡編程,畢竟如今的絕大多數數據仍是在網絡中傳輸。下面先說明一些網絡的基礎知識,不過對於從事網絡工程的來講只是很簡單的基礎知識,緩存
1.1 C/S架構安全
C/S架構中C指的是client(客戶端軟件),s指的是server(服務器端軟件),而本章的主要學習目的是寫一個基於C/S架構的軟件,客戶端軟件與服務器端基於網絡通訊。如今基本的C/S架構基本是下圖這樣:客戶端與服務器基於網絡傳輸互相傳輸數據。服務器
1.2 B/S架構
B/S架構中的B指的是Brower(瀏覽器),S指的是Server(服務器端),平常中的網頁瀏覽也是B/S架構。(不是很瞭解)
1.3 OSI的七層協議
瞭解了C/S結構的大概構成,就說一下OSI七層協議,在美國軍方發展ARPA網絡以後,將他公佈用於學術網絡以後,就大爆發同樣的發展了,因爲網絡協議的公開性,各家發展各自的標準,以致於各類網絡之間並不能互通,國際ISO標準組織就頒發一套標準七層網絡協議,七層網絡協議有應用層,表示層,會話層,傳輸層,網絡層,數據鏈路層,物理層
應用層(7) -------------------------------------------------- 提供應用程序間的通訊
表示層 (6)-------------------------------------------------- 處理數據格式以及數據加密等
會話層 (5)-------------------------------------------------- 創建,維護管理會話
傳輸層 (4)-------------------------------------------------- 創建主機端到端的鏈接
網絡層 (3)-------------------------------------------------- 尋址以及路由選擇
數據鏈路層(2)--------------------------------------------- 提供介質訪問,鏈路管理等
物理層(1) -------------------------------------------------- 比特流傳輸
通常567整合爲應用程序,1234爲數據流層
1.4 經常使用的TCP/IP的五層協議
因爲IOS標準組織制定標準時間長,在廠商中TCP/IP更容易理解,雖然有一些結構性的缺陷,可是TCP/IP已經成爲名副其實的標準了
主要有應用層,傳輸層,網絡層,數據鏈路層,物理層
應用層 -------------------------------------------------- 用戶數據
傳輸層 -------------------------------------------------- TCP報頭+上層數據
網絡層 -------------------------------------------------- IP報頭+上層數據
數據鏈路層 --------------------------------------------- LLC報頭+上層數據+FCS MAC報頭+上層數據+FCS
物理層 -------------------------------------------------- 0101的Bit
以上都是由上向下傳輸或者是由下向上傳輸
2. TCP與UDP
基於TCP/IP協議,主要有倆種傳輸形式,一種是TCP,一種UDP
TCP(傳輸控制協議):面向鏈接 重傳機制 確認機制 流量控制 (保證可靠)
UDP:面向無鏈接 低開銷 傳輸效率高,速度快
2.1 TCP的三次握手與四次揮手
TCP因爲傳輸數據,要和對端要先創建通道才能夠傳輸數據,因此被稱之爲可靠的傳輸協議,傳輸數據以前須要創建通道,等通道創建成功後,發送數據片斷,每發送一個數據片斷,發送一個確認碼ack,發送端只有在收到ack確認碼纔會發送下一個數據片斷,不然會從新發送未被確認數據片斷。因爲要確認的東西不少,因此TCP的報頭有20字節。這樣TCP傳輸就很佔用傳輸帶寬。
如下圖片就是三次握手以及四次揮手的過程,這個會後面網絡編程中較大聯繫
2.2 UDP協議
UDP協議因爲不須要和對端確認通道以及對方是否存在,只須要知道對端是誰就能夠,因此UDP也被稱之爲不可靠傳輸協議。UDP協議只負責傳送,不負責數據是否到達,因此低開銷,傳輸速率高,UDP頭部只有8字節。
2.3端口基礎
端口範圍在0----65535(2*16)
知名端口號(0----1023,其餘軟件禁止使用),
註冊端口號(1024----49151,通常用於軟件註冊,不過一些知名的端口仍是建議不使用)
隨機端口號(49152----65535,通常用於客戶端軟件,隨機端口)
3.基於c/s結構的服務器客戶端的實驗
3.1基礎知識點-socket
對於上面網絡基礎瞭解後,咱們能夠這麼想之後咱們本身敲代碼了,那我是否是就須要記住這些幾層協議,傳輸層,網絡層具體作什麼,這個時候就須要一個新的模塊了,socket,python中處理網絡編程相關的問題,Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來講,一組簡單的接口就是所有。這樣的話,用戶就不須要再次瞭解下面具體怎麼實施,只須要知道怎麼操做socket就行了,結構如圖。
3.2.1socket的套接字
family(socket家族)
socket type類型
(Only SOCK_STREAM and SOCK_DGRAM appear to be generally useful.)
服務器端套接字類型
用戶端套接字類型
公用套接字類型
3.3 tcp創建鏈接的過程
4.實驗
4.1實驗一
目的:基於TCP創建一個簡單的服務端與客戶端,服務端可以接收客戶端傳輸過來的值
服務器端 import socket sk = socket.socket()#實例化對象 sk.bind(('127.0.0.1',8500))#對象綁定 sk.listen(3)#服務端監聽 print('loading.....') conn,addr = sk.accept() msg = conn.recv(1024) print(msg) conn.close() sk.close()
客戶端 import socket sk = socket.socket() sk.connect(('127.0.0.1',8500))#客戶端鏈接 sk.send(b'123') sk.close()
4.2實驗二
目的:基於TCP實現客戶端與服務器之間的能夠退出的聊天程序
#服務器 import socket sk = socket.socket()#實例化對象 sk.bind(('127.0.0.1',8500))#對象綁定 sk.listen()#服務端監聽 print('loading.....') conn,addr = sk.accept() print(addr) while True: msg = conn.recv(1024).decode() if msg =='q':break else: print(msg) msg1 = input('>>>').strip().encode() conn.send(msg1) if msg1 =='q':break conn.close() sk.close()
#客戶端 import socket sk = socket.socket() sk.connect(('127.0.0.1',8500))#客戶端鏈接 while True: msg = input('>>>').strip().encode() sk.send(msg) if msg =='q':break ret = sk.recv(1024).decode() if ret =='q':break else:print(ret) sk.close()
咱們來講一下程序中系統的實現方法,對於咱們寫的小程序,並非咱們直接創建鏈接,服務器端與客戶端之間直接收發數據,真正的作法就是咱們把發送的數據交給操做系統,操做系統在調用物理硬件將數據發出,接收端也是發送信息交給操做系統讓他從網卡這裏獲取收到的數據,傳遞給應用程序。
補充一個:服務器端接收數據中的conn是一個套接字對象,是一個基於TCP協議創建的一個連接
4.3實驗三
目的:基於TCP實現簡單的時間服務器
#服務器端 import time import socket sk = socket.socket() sk.bind(('127.0.0.1',8500)) sk.listen() conn,addr = sk.accept() data = conn.recv(1024).decode() time_date = time.strftime(data).encode() conn.send(time_date) conn.close() sk.close()
#客戶端 import socket sk = socket.socket() sk.connect(('127.0.0.1',8500)) msg ='%Y.%m.%d %H:%M:%S' sk.send(msg.encode()) date=sk.recv(1024).decode() print(date) sk.close()
4.4實驗
基於TCP測試一次性發送多個字符串
#服務端 import socket sk = socket.socket() sk.bind(('127.0.0.1',8500)) sk.listen() conn,addr = sk.accept() msg = conn.recv(1024).decode() print(msg) conn.close() sk.close()
#客戶端 import socket sk = socket.socket() sk.connect(('127.0.0.1',8500)) sk.send(b'hello') sk.send(b'world') print('......') sk.close()
咱們會發現服務端收到了一個b’helloworld’這麼一個bytes類型的字符串,這個現象就叫作黏包現象
先解釋一下黏包的產生:
它的發生主要是由於socket緩衝區致使的,你的程序實際上無權直接操做網卡的,你操做網卡都是經過操做系統給用戶程序暴露出來的接口,那每次你的程序要給遠程發數據時,實際上是先把數據從用戶態copy到內核態,這樣的操做是耗資源和時間的,頻繁的在內核態和用戶態以前交換數據勢必會致使發送效率下降, 所以socket 爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一次數據給對方。若連續幾回須要send的數據都不多,一般TCP socket 會根據優化算法把這些數據合成一個TCP段後一次發送出去,這樣接收方就收到了粘包數據。
仍是看上圖,發送端能夠是一K一K地發送數據,而接收端的應用程序能夠兩K兩K地提走數據,固然也有可能一次提走3K或6K數據,或者一次只提走幾個字節的數據,也就是說,應用程序所看到的數據是一個總體,或說是一個流(stream),一條消息有多少字節對應用程序是不可見的,所以TCP協議是面向流的協議,這也是容易出現粘包問題的緣由。而UDP是面向消息的協議,每一個UDP段都是一條消息,應用程序必須以消息爲單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不一樣的。怎樣定義消息呢?能夠認爲對方一次性write/send的數據爲一個消息,須要明白的是當對方send一條信息的時候,不管底層怎樣分段分片,TCP協議層會把構成整條消息的數據段排序完成後才呈如今內核緩衝區。
這裏咱們說一下send與recv的區別
例如基於tcp的套接字客戶端往服務端上傳文件,發送時文件內容是按照一段一段的字節流發送的,在接收方看了,根本不知道該文件的字節流從何處開始,在何處結束
所謂粘包問題主要仍是由於接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的。
既然有問題,那就解決問題,既然爲了解決這個問題,就會引入管道這個類
#服務端 import socket import struct sk = socket.socket() sk.bind(('127.0.0.1',8500)) sk.listen() conn,addr = sk.accept() while True: send_msg = input('>>>').strip().encode('utf-8') msg_len = struct.pack('i',len(send_msg)) conn.send(msg_len) conn.send(send_msg) conn.close() sk.close()
import socket import struct sk = socket.socket() sk.connect(('127.0.0.1',8500)) data =sk.recv(4) msg_len = int(struct.unpack('i',data)[0]) print(msg_len) recive_len = 0 msg =b'' while recive_len<msg_len: date2 = sk.recv(5) msg +=date2 recive_len+=5 print(msg.decode()) sk.close()
4.5實驗
目的:基於TCP服務器端給客戶端傳文件,驗證客戶端接收的文件完整性,動態的顯示接收進度
#服務器端 import socket import struct import json sk = socket.socket() sk.bind(('127.0.0.1',8500)) sk.listen() conn,addr = sk.accept() header ={'filename':r'D:\test.zip','file_size':922933359,'MD5':'6237eb2c55b34f87e856422896c1f440'} header_json = json.dumps(header)#將頭文件字典轉換爲json模式 header_bytes = header_json.encode('utf-8')#將json文件轉換爲bytes類型 header_len = struct.pack('i',len(header_bytes))#將頭文件從管道發送過去 conn.send(header_len) conn.send(header_bytes) with open(header['filename'],'rb')as f1: while True: contact = f1.read(1024) if contact: conn.send(contact) else: break print('文件傳輸完畢!') conn.close() sk.close()
#客戶端 import socket import struct import json import hashlib import sys sk = socket.socket() sk.connect(('127.0.0.1',8500)) data =sk.recv(4)#接收頭文件那四個字節 head_len = int(struct.unpack('i',data)[0]) head_bytes = sk.recv(head_len)#接收頭文件 head_json = head_bytes.decode('utf-8')#將bytes類型的頭文件解析成json格式 header = json.loads(head_json)#將json格式的文件反解成字典 def check_md5(file):#驗證文件MD5 ret = hashlib.md5() with open(file,mode='rb')as f2: while True: contect = f2.read(1024)#讀取1024字節 if contect: ret.update(contect) else: break return ret.hexdigest() receive_num =0 with open('test.zip','wb')as f1: while receive_num < header['file_size']: contact = sk.recv(1024) if contact: f1.write(contact) receive_num += 1024 float_rate =receive_num/header['file_size'] rate = round(float_rate * 100, 2) sys.stdout.write('\r已下載:\033[1;32m{0}%\033[0m'.format(rate))#動態顯示接收進度! else: break print('文件下載成功!') num =check_md5('test.zip') if num ==header['MD5']: print('文件校驗成功,文件完整') else: print('文件校驗失敗,文件不完整') sk.close()