day18-網絡編程基礎(一)

勿驕勿燥,仍是要定下心學習,還有有些沒定下心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.AF_UNIX:用於本機進程間通信,爲了保證程序安全,兩個獨立的程序(進程)間是不能互相訪問彼此的內存的,但爲了實現進程間的通信,能夠經過建立一個本地的socket來完成
  • socket.AF_INET:(還有AF_INET6被用於ipv6,還有一些其餘的地址家族,不過,他們要麼是隻用於某個平臺,要麼就是已經被廢棄,或者是不多被使用,或者是根本沒有實現,全部地址家族中,AF_INET是使用最普遍的一個,python支持不少種地址家族,可是因爲咱們只關心網絡編程,因此大部分時候我麼只使用AF_INET)

socket type類型

  • socket.SOCK_STREAM #for tcp
  • socket.SOCK_DGRAM #for udp
  • socket.SOCK_RAW #原始套接字,普通的套接字沒法處理ICMP、IGMP等網絡報文,而SOCK_RAW能夠;其次,SOCK_RAW也能夠處理特殊的IPv4報文;此外,利用原始套接字,能夠經過IP_HDRINCL套接字選項由用戶構造IP頭。
  • socket.SOCK_RDM #是一種可靠的UDP形式,即保證交付數據報但不保證順序。SOCK_RAM用來提供對原始協議的低級訪問,在須要執行某些特殊操做時使用,如發送ICMP報文。SOCK_RAM一般僅限於高級用戶或管理員運行的程序使用。
  • socket.SOCK_SEQPACKET #廢棄了
(Only SOCK_STREAM and SOCK_DGRAM appear to be generally useful.)

服務器端套接字類型

  • s.bind() 綁定(主機,端口號)到套接字
  • s.listen() 開始TCP監聽
  • s.accept() 被動接受TCP客戶的鏈接,(阻塞式)等待鏈接的到來

用戶端套接字類型

  • s.connect() 主動初始化TCP服務器鏈接
  • s.connect_ex() connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常

公用套接字類型

  • s.recv() 接收數據
  • s.send() 發送數據(send在待發送數據量大於己端緩存區剩餘空間時,數據丟失,不會發完,可後面經過實例解釋)
  • s.sendall() 發送完整的TCP數據(本質就是循環調用send,sendall在待發送數據量大於己端緩存區剩餘空間時,數據不丟失,循環調用send直到發完)
  • s.recvfrom() Receive data from the socket. The return value is a pair (bytes, address)
  • s.getpeername() 鏈接到當前套接字的遠端的地址
  • s.close() 關閉套接字
  • socket.setblocking(flag) #True or False,設置socket爲非阻塞模式,之後講io異步時會用
  • socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0) 返回遠程主機的地址信息,例子 socket.getaddrinfo('luffycity.com',80)
  • socket.getfqdn() 拿到本機的主機名
  • socket.gethostbyname() 經過域名解析ip地址

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類型的字符串,這個現象就叫作黏包現象

先解釋一下黏包的產生:

6.5%20buffer%20

它的發生主要是由於socket緩衝區致使的,你的程序實際上無權直接操做網卡的,你操做網卡都是經過操做系統給用戶程序暴露出來的接口,那每次你的程序要給遠程發數據時,實際上是先把數據從用戶態copy到內核態,這樣的操做是耗資源和時間的,頻繁的在內核態和用戶態以前交換數據勢必會致使發送效率下降, 所以socket 爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一次數據給對方。若連續幾回須要send的數據都不多,一般TCP socket 會根據優化算法把這些數據合成一個TCP段後一次發送出去,這樣接收方就收到了粘包數據。

仍是看上圖,發送端能夠是一K一K地發送數據,而接收端的應用程序能夠兩K兩K地提走數據,固然也有可能一次提走3K或6K數據,或者一次只提走幾個字節的數據,也就是說,應用程序所看到的數據是一個總體,或說是一個流(stream),一條消息有多少字節對應用程序是不可見的,所以TCP協議是面向流的協議,這也是容易出現粘包問題的緣由。而UDP是面向消息的協議,每一個UDP段都是一條消息,應用程序必須以消息爲單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不一樣的。怎樣定義消息呢?能夠認爲對方一次性write/send的數據爲一個消息,須要明白的是當對方send一條信息的時候,不管底層怎樣分段分片,TCP協議層會把構成整條消息的數據段排序完成後才呈如今內核緩衝區。

這裏咱們說一下send與recv的區別

  • send:無論recv仍是send並非直接接受或者發送對方的數據,而是操做本身操做系統的內存,將數據copy一份給內存,不過並非一個send對應一個recv
  • recv:主要是有倆個過程wait data與copy data 倆個過程,wait data 時間是最長的,中間要通過網絡傳輸,內存獲取到數據將數據copy到應用層

例如基於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()
相關文章
相關標籤/搜索