python網路編程、套接字、HTTP協議

  • 2019 年 10 月 3 日
  • 筆記

網路編程 

網路目的 : 數據的傳輸

網路數據傳輸是一個複雜的過程

OSI 七層模型 –》 網路通訊標準化流程

  • 應用層 : 提供用戶服務,具體內容由特定程式規定
  • 表示層 : 數據的壓縮優化 
  • 會話層 : 建立應用連接,選擇傳輸層服務
  • 傳輸層 : 提供不同的傳輸服務,流量控制
  • 網路層 : 路由選擇,網路互連
  • 鏈路層 : 提供鏈路交換,具體消息以幀發送
  • 物理層 : 物理硬體,介面,網卡,線路

osi七層模型優點 : 將功能分開,降低了網路傳輸中的耦合性,每一部分完成自己的功能。可以在開發和實施的過程中各司其職。實現高內聚和低耦合的功能。

高內聚 : 單個模組功能盡量單一
低耦合 : 模組之間盡量減少關聯和影響

四層 

  • 應用層 : 應用層 表示層 會話層
  • 傳輸層 : 傳輸層
  • 網路層 : 網路層
  • 物理鏈路層: 鏈路層和物理層

五層(tcp/ip模型)

  • 應用層 : 應用層 表示層 會話層
  • 傳輸層 : 傳輸層
  • 網路層 : 網路層
  • 鏈路層 : 鏈路層
  • 物理層 : 物理層

 

協議(網路協議):在網路通訊中,各方必須遵守的規定。包括建立什麼樣的連接,消息結構等

應用層 : TFTP HTTP DNS SMTP
傳輸層 : TCP UDP
網路層 : IP 
物理層 : IEEE

 

網路基本概念

1、主機: “localhost” 表示本台電腦

  網路上 : 只在本地測試使用
    ’localhost’ ‘127.0.0.1’

  如果想在網路上進行測試(自動使用本地可用網卡IP)
    ’0.0.0.0′ ” ‘172.60.50.93’

  查看本地 IP 網路資訊
    linux上:    ifconfig 
    win上查看本地IP: ipconfig

    ping www.baidu.com  —>14.215.177.38(百度的IP地址)

  獲取電腦名稱
    socket.gethostname()
    ’tedu’

  獲取主機IP
    socket.gethostbyname(‘localhost’)
    ’127.0.0.1′

2、IP地址
  在網路上用於區分一台計算 

  IPv4 : 點分十進位 e.g. 192.168.1.72 0-255
    32位二進位表示

  IPv6 : 128

  網路連接測試命令: ping 172.60.50.92

  特殊IP
    127.0.0.1 本地測試IP
    0.0.0.0 本地網卡通用IP
    192.168.1.0 表示當前網段
    192.168.1.1 表示網關
    192.168.1.255 廣播地址

  獲取伺服器主機資訊
    socket.gethostbyaddr(“www.baidu.com”)
    (‘127.0.0.1’, [], [‘119.75.213.61’])
      主機    別名    IP地址

  將ip十進位轉化為二進位
    socket.inet_aton(“192.168.1.2”)
    b’xc0xa8x01x02′

  將ip二進位轉化為十進位
    socket.inet_ntoa(b”xc0xa8x01x02″)
    ’192.168.1.2′

  域名 : 網路伺服器IP地址的名稱
  url : 在網路上定位某個資源位置

3、埠號 :埠號是網路地址的一部分,在一個系統中每個網路應用監聽不同的埠,以獲取對應埠傳輸的資訊

  數字範圍 : 1–65535

    1–255 : 一些眾所周知的埠
    256–1023 : 系統應用
    1024—65535 : 自用 
    推薦用  >10000 8888 9999 7777 6666

  測試一個軟體埠號
    socket.getservbyname(‘mysql’)
    3306
    socket.getservbyname(‘http’)
    80
    socket.getservbyname(‘ssh’)
    22

傳輸層服務 

面向連接的傳輸服務 —》 tcp協議 

傳輸特徵:提供可靠的傳輸服務
可靠性表現: 數據在傳輸過程中,無失序,無差錯,無重複,無丟失

* 傳輸過程中有建立和斷開連接的過程
三次握手:建立數據傳輸兩端的持續連接
  1. 客戶端向伺服器發起連接請求(我可以牽你手嗎)
  2. 伺服器收到連接請求進行確認,返回報文(可以)
  3. 客戶端收到伺服器確認進行連接創建(牽手成功)

四次揮手:斷開連接的兩端,保證數據的傳輸完整
  1.主動方發送報文,告知被動方要斷開連接(我們分手吧,你準備好)
  2.被動方返回報文,告知收到請求,準備斷開(知道了)
  3.被動方再次發送報文,告知準備完畢可以斷開(你分手吧)
  4.主動方發送報文完成斷開(分手了)

適用情況:文件的上傳下載,網路情況良好,需要必須保證可靠性的情況
比如 : 資訊聊天,文件上傳下載,郵件,網頁獲取

面向無連接的傳輸服務 —》 udp協議

 傳輸特徵 :
  * 保證傳輸的可靠性
  * 無需建立三次握手四次揮手的連接斷開過程
  * 消息的收發比較自由,不受其他約束(請原諒我這一生放蕩不羈愛自由)

適用情況 : 網路情況較差,對可靠性要求不高,收發消息的兩端不適合建立固定連接
比如 :網路影片,群聊,發送廣播

套接字—-socket

socket模組的套接字屬性

(s表示一個套接字對象)

s.type  獲取套接字類型  # SocketKind.SOCK_STREAM 流式套接字

s.family  獲取地址族類型  # AddressFamily.AF_INET 獲取地址族類型

s.fileno()  獲取套接字的文件描述符(每一個IO作業系統都會為其分配一個不同的正整數,該正整數即為此IO作業系統的文件描述符)

s.getsockname()  獲取套接字綁定的地址  # (‘192.168.191.3’, 8888)

s.getpeername()  獲取連接套接字另一端的地址 # (‘192.168.191.3’, 7826)

s.setsockopt(level,optname,value)  設置套接字選項,豐富修改原有套接字功能

  參數: level 設置選項的類型  optname 每個選項類型中的子選項  value 為選項設置值

s.getsockopt(level,optname)  獲取套接字選項的值

 1 from socket import *   2 s = socket()   3 print(s.type)   # SocketKind.SOCK_STREAM 流式套接字   4 print(s.family) # AddressFamily.AF_INET 獲取地址族類型   5 print(s.fileno())   # 376   6 s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 設置埠可重用   7 print(s.getsockopt(SOL_SOCKET,SO_REUSEADDR))# 獲取選項值 1   8 s.bind(("192.168.191.3",8888))   9 print(s.getsockname())  # 獲取綁定的地址 ('192.168.191.3', 8888)  10 s.listen()  11 c,addr = s.accept() # addr也是鏈接客戶端的地址  12 print(c.getpeername())  # ('192.168.191.3', 7826)獲取鏈接套接字客戶端地址  13 data = c.recv(1024)  14 print(data) # b'i'  15 c.close()  16 s.close()

View Code

socket套接字編程

  套接字:通過程式語言提供的函數介面進行組合,更簡單的完成基於tcp和udp通訊的網路編程

套接字的分類

  流式套接字(SOCK_STREAM):傳輸層基於tcp的協議進行通訊

  數據報套接字(SOCK_DGRAM):傳輸層基於udp的協議進行通訊

  底層套接字(SOCK_RAM):訪問底層協議的套接字 

網路收發緩衝區

  1、協調讀寫速度、減少和磁碟交互

  2、recv和send實際上是從緩衝區獲取內容,和向緩衝區發送內容

recv()特性

  1、如果連接斷開,recv會立即結束阻塞返回空字元串

  2、當接收快取區為空時會阻塞

  3、如果recv一次接收不完緩衝區內容,下次會繼續接收,確保數據不丟失

send()特性

  1、如果另一端不存在還試圖使用send進行發送則會產生BrokenPipError異常

  2、當發送緩衝區滿時會阻塞

本地套接字 

作用:用於本地不同程式間的進行數據傳輸

本地套接字的創建流程

1、創建套接字對象

  sockfd = socket(AF_UNIX,SOCK_STREAM)

2、綁定本地套接字文件,如果文件不存在,則自動創建文件(綁定套接字文件)

  sockfd.bind(file)

  判斷一個文件夾下是否有某個文件  os.path.exists(‘./tcp_client.py’)

  刪除一個文件  os.remove(file)  os.remove(file)

3、監聽  listen 

4、接收發送消息  recv  send

from socket import *  import os    sock_file =  './sock'    # 使用哪個文件作為套接字文件    if os.path.exists(sock_file):# 判斷文件是否已經存在      os.unlink(sock_file)    sockfd = socket(AF_UNIX,SOCK_STREAM)    # 創建本地套接字    sockfd.bind(sock_file)    # 綁定  sockfd.listen(5)    # 監聽    while True:      c,addr = sockfd.accept()     # 建立連接      while True:          data = c.recv(1024)          if not data:              break          print(data.decode())      c.close()  sockfd.close()

服務端

from socket import *    sock_file = "./sock"    # 確保通訊兩端用相同的套接字文件    sockfd = socket(AF_UNIX,SOCK_STREAM)    # 創建套接字    sockfd.connect(sock_file)    # 鏈接    while True:      msg = input("Msg>>")      if msg:          sockfd.send(msg.encode())      else:          break    sockfd.close()

客戶端

 

TCP粘包

  產生原因:TCP傳輸採用位元組流的方式,消息之間沒有邊界,如果發送的速度比接收速度快,會造成多次發送的內容被一次接收,形成意義上的誤解即粘包

  產生條件:當使用send快速的連續發送極有可能產生粘包

  影響:如果每次發送的內容代表一個獨立的意思,需要單獨識別,就會產生粘包。但是如果多次發送的內容就是一個連續的整體,此時就不需要處理。

  如何處理

    1、每次發送後加一個結尾標誌,接收端通過標誌進行判斷

    2、發送一個數據結構

    3、每次發送中間有一個短暫的延遲(有一個間隔)

TCP接收多個客戶端連接,且可以持續發送消息

from socket import *    sockfd = socket(AF_INET,SOCK_STREAM)    #創建套接字    sockfd.bind(('127.0.0.1',9999))    #綁定地址    sockfd.listen(5)    #設置監聽    while True:    # 等待客戶端連接      print("Waiting for connect...")      connfd,addr = sockfd.accept()      print("Connect from",addr)      while True:    # 消息收發          data = connfd.recv(1024)          if not data:              break          print("Receive:",data.decode())          n = connfd.send(b"Receive your message")          print("send %d bytes"%n)        connfd.close()    # 關閉套接字    sockfd.close()

TCP-server

from socket import *    sockfd = socket()    #創建套接字    sockfd.connect(('127.0.0.1',9999))    #發起連接    while True:    #消息收發        msg = input("Msg>>")      if not msg:          break      sockfd.sendall(msg.encode())      data = sockfd.recv(1024)      print(data.decode())    sockfd.close()

TCP-client

HTTP

http協議–>超文本傳輸協議  應用層協議,HTTP是基於TCP協議編碼的。

用途:網頁的獲取,基於網站的數據傳輸,基於http協議的數據傳輸

特點

  • 應用層協議。傳輸層使用TCP傳輸
  • 無狀態協議,不記錄用戶的通訊內容
  • http1.1—->http2.0 成熟穩定

工作模式:

  使用http雙方均遵守http協議規定發送接收消息體

  請求方,根據協議組織請求內容給對象

  服務方,收到內容按照協議解析

  服務方,將回復內容按照協議組織發送給請求方

  請求方,收到回復根據協議解析

HTTP請求格式

格式: 請求行 請求頭 空行 請求體

1、請求行(熟悉格式及作用): 提供具體的請求類別, 請求內容

    GET  /  index.html / HTTP/1.1

    請求類別  請求內容    協議版本

    請求種類 :   GET 獲取網路資源
          POST 提交一定的附加數據,得到返回 結果
          HEAD 獲取響應頭
          PUT 更新伺服器資源
          DELETE 刪除伺服器資源
          CONNECT 預留
          TRACE 測試
          OPTIONS 獲取伺服器性能

2、請求頭 : 對請求內容的具體描述, 鍵值對的形式對請求資訊進行描述

  e.g.

    Accept: text/html
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9
    Cache-Control: max-age=0
    Connection: keep-alive

3、空行

4、請求體 : 具體的請求參數

  (GET參數,或者POST提交的內容)

HTTP響應格式

1、響應行 回饋具體的響應情況

  HTTP/1.1   200   OK
  版本資訊  響應碼  附加資訊

  響應碼 :

  • 1xx 提示資訊 表示請求已經接受
  • 2xx 響應成功
  • 3xx 響應需要重新請定向
  • 4xx 客戶端錯誤
  • 5xx 伺服器錯誤

  常見響應碼 :

  • 200 成功
  • 404 請求頁面不存在
  • 401 沒有訪問許可權
  • 500 伺服器發生未知錯誤
  • 503 伺服器暫時無法執行

2、響應頭 對響應資訊的具體描述

  e.g.
  Cache-Control: private
  Connection: Keep-Alive

3、空行
4、響應體:將客戶想要的內容進行返回

搭建HTTP本地伺服器 

做的是一個本地服務端,接收來自瀏覽器客戶端的請求

# 返回第一行   GET / HTTP/1.1
  1. 接收http請求; 解析http請求
  2. 響應一個網頁給客戶端
from socket import *    # 處理客戶端請求  def handle(connfd):    request = connfd.recv(4096)   # 接收請求      # 防止客戶端斷開request為空    if not request:      return    request_line = request.splitlines()[0]    # 返回第一行   GET / HTTP/1.1    info = request_line.decode().split(' ')[1]    if info == '/':      with open('index.html') as f:        response = "HTTP/1.1 200 OKrn"            # 響應行        response += "Content-Type:text/htmlrn"    # 響應頭        response += 'rn'                          # 空行        response += f.read()                        # 響應體    else:      response = "HTTP/1.1 404 Not Foundrn"      response += "Content-Type:text/htmlrn"      response += 'rn'      response += "<h1>Sorry...</h1>"      connfd.send(response.encode())    # 發送給瀏覽器    sockfd = socket()   # 搭建tcp網路  sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)  sockfd.bind(('0.0.0.0',8000))       # 綁定地址  sockfd.listen(3)                    # 設置監聽  while True:    connfd,addr = sockfd.accept()     # 獲取連接端和地址    handle(connfd)      # 處理客戶端請求

 在瀏覽器輸入地址:127.0.0.1:8888,即可得到網頁顯示!