初識python網絡編程-01

近期學習python基礎知識,到網絡編程部分以爲頗有意思,寫個博客記錄一下,首先可先了解一下計算機網絡的基礎知識,參考相關書籍便可,本文只作簡單知識介紹!python


1.OSI七層協議,TCP、UDP協議程序員

首先, 咱們今天使用的計算機都是要聯網使用的. 不多有那種單機走天下的狀況了. 那麼咱們的計算機是如何經過網絡實現通訊的. 咱們先了解一些關於網絡的基礎知識. 而後再開始學習一些關於網絡編程的內容, 第一個要解釋的名詞叫協議. 咱們只有明白協議是什麼, 後面再看各類各樣的通訊規則就容易的多了.
官話: 網絡協議是通訊計算機雙方必須共同聽從的一組約定。如怎麼樣創建鏈接、怎麼樣互相識別等。只有遵照這個約定,計算機之間才能相互通訊交流
普通話: 兩臺計算機之間約定好. 我發送的數據格式是什麼. 你接收到數據以後. 使用相同的格式來拿到數據
例子: 你和一個韓國人交流. 你說中文, 他說韓文. 你倆是不能明白對方說的是什麼的. 怎麼辦. 你倆約定好, 都說英語.實現交流. 這個就叫協議.
網絡協議: 互聯網之間互相傳遞消息的時候使用統一的一系列約定
在今天的互聯網數據傳輸中通常使用的是OSI七層協議. 也有簡稱爲五層, 四層協議. 只是對不同網絡層的定義不同.
內部原理和做用是同樣的.
初識python網絡編程-01編程

下面簡單介紹一下OSI七層協議的基本功能
首先是物理層: 該層是網絡通訊的數據傳輸介質,由鏈接不一樣結點的電纜與設備共同構成。主要跟功能是:利用傳輸介質爲數據鏈路層提供物理鏈接,負責處理數據傳輸並監控數據出錯率,以便數據流的透明傳輸
數據鏈路層: 這一層負責裝配本身和對方主機的MAC地址. MAC地址: 每一個網絡設備的惟一編碼. 全球惟一. 由不同廠商直接燒錄在網卡上.
做用: 在龐大的網絡系統中, 你要發送的數據究竟是要給誰的. 由誰發送出來的. 這就至關於你寫信的時候的信封. 上面得寫清楚收貨人地址.
網絡層: 在有了MAC地址其實咱們的電腦就能夠開始通訊了. 可是. 此時的通訊方式是廣播. 至關於通訊基本靠吼. 你發送一個數據出去. 會自動的發給當前網絡下的全部計算機. 而後每一個計算機的網卡會看一眼這個數據是不是發給本身的. 像這樣的通訊方式, 若是計算機的數據量少. 是沒有問題的. 可是. 若是全球全部計算機都按照這樣的方式來傳輸消息. 那不只是效率的問題了. 絕對是災難性的. 那怎麼辦. 你們就想到了一個新的方案, 這個方案叫IP協議. 使用IP協議就把不同區域的計算機劃分紅一個一個的子網. 子網內的通訊使用廣播來傳遞消息. 廣播外經過路由進行傳遞消息. 你能夠理解爲不同快遞公司的分撥中心. 我給你郵寄一個快遞. 先看一下是不是本身區域的. 是自⼰己區域直接挨家挨戶找就OK了. 可是若是不是我這個區域的. 就經過分撥中心(路由器網關)找到你所在的區域的分撥中心(路由器網關), 再經過你的分撥中心下發給你. 這里IP協議的做用就體現出來了. 專門用來劃分子網的.那麼在傳輸數據的時候就必需要把對方的ip地址帶着. 有了這個ip再加上子網掩碼就能夠判斷出該數據究竟是屬於哪一個子網下的數據.
IP地址: 由4位點分⼗十進制表示. 每位最⼤大255. 故IP地址的範圍: 0.0.0.0~255.255.255.255. 爲什什麼是255, 答:
28 每一位用8位2進製表示, 合起來32位就能夠表示一個計算機的ip地址
子網掩碼: 用來劃分子網的一個4位點分十進制.
網關: 路由器在子網內的ip. 不同局域網進行數據傳輸的接口(分撥中心)
計算子網的過程:服務器

1 ip1: 192.168.123.16
2 ip2: 192.168.123.45

3 子網掩碼: 255.255.255.0

4 所有轉化成二進制
4 ip1: 11000000 10101000 01111011 00010000
5 ip2: 11000000 10101000 01111011 00101101
6 子網: 11111111 11111111 11111111 00000000

7 讓ip1和ip2分別和子網進行"與"運算
8 ip1 & 子網: 11000000 10101000 01111011 00000000
9 ip2 & 子網: 11000000 10101000 01111011 00000000
10 相等. OK 這兩個IP就是同一個子網

傳輸層: 咱們如今解決了外界的數據傳輸問題. 使用MAC地址和IP地址能夠惟一的定位到一臺計算機了. 那麼還有一個問題沒有解決. 咱們知道一臺計算機內是頗有可能運行着多個網絡應用程序的. 好比, 你開着LOL, 掛着DNF, 聊着QQ, 還看着快播. 那麼此時你的計算機網卡接收到了來自遠方的一條數據. 那麼這一條數據到底給那個應用呢? 說白了, 快遞送到你公司了. 地址沒毛病了. 但是你公司那麼多人. 這個快遞到底給誰? 不能亂給啊. 怎麼辦呢? 互聯網大佬們想到了一個新詞叫端口.
傳輸層規定: 給每⼀一個應⽤用程序分配一個惟一的端口號. 當有數據發送過來以後. 經過端口號來決定該數據發送的具體應用程序.可是根據不同應用程序對網絡的需求的不同(有的要求快, 有的要求可靠) 又把傳輸層劃分紅兩個協議. 一個叫TCP, 一個叫UDP. 因此, 咱們常說的TCP/IP協議中最重要, 也是咱們最關注的其實就是IP和端口了了. 由於有了這兩個, 咱們其實就能夠定位到某一臺計算機上的某個網絡應用程序了了. 也就能夠給他發送消息了
32位計算機上的端口數:
TCP : 65536個
UDP: 65536個
TCP和UDP的區別:網絡

  1. TCP, 它是基於鏈接的. 是連續的, 可靠的. 效率比較低. 更像是打電話. 聊天的過程當中不能中斷.
  2. UDP, 它不是基於鏈接的. 是不連續的, 不可靠的. 效率比較高. 更像是寄信, 今兒一封, 明兒一封. 想啥時候發就啥時候發.
    應用層: TCP+IP能夠定位到計算機上的某個應用了了. 可是不同用傳輸的數據格式多是不同樣的. 就比如快遞. 有的是大包裹. 有的是小文件. 一個要用大快遞袋裝, 一個要用小快遞袋裝. 到了了應用層. 咱們通常是根據不同類型的應用程序進行的再一次封裝. 好比, HTTP協議, SMTP協議, FTP協議. 等等.

2.初識Socket-TCP編程
在幾乎全部的編程語言中, 咱們在編寫網絡程序的時候都要使用到socket. socket翻譯過來叫套接字. 咱們上面也了解到了一次網絡通訊的數據須要包裹着mac, ip, port等信息. 可是若是每次咱們開發都要程序員去⼀個一個的去準備數據, 那工做量絕對是絕望的. 因此, 計算機提出了了socket. socket幫助咱們完成了網絡通訊中的絕大多數操做. 咱們只須要告訴socket. 我要向哪臺計算機(ip, port)發送數據. 剩下的全部東西都由socket幫咱們完成. 因此使用socket完成數據傳輸是很是方便的.
話很少說,練起來吧~~socket

server端:編程語言

import socket

#建立socket通道
sk = socket.socket()
sk.bind(("127.0.0.1", 8088))  # 綁定ip和端口
sk.listen()  # 開始監聽
print("服務器就緒,等待鏈接")
conn, address = sk.accept()  # 程序阻塞,等待鏈接
print("有客戶端鏈接,ip是:", address)  # address是客戶端的ip和端口

while 1:   #能持續向客戶端傳遞消息
    conn.send(input(">>>").encode("utf-8"))  # 發送的內容只能是bytes
    print(conn.recv(1024).decode("utf-8"))  #展現接收到的消息

client端:ide

import socket

sk = socket.socket()  # 建立通道
print("客戶端初始化完成")
sk.connect(("127.0.0.1", 8088))  # 創建鏈接
print("客戶端鏈接成功")
while 1:   #持續向服務端發送消息
    print(sk.recv(1024).decode("utf-8"))  # 最大接受1024字節的內容
    sk.send(input(">>>").encode("utf-8")) #展現接收到的消息

3.初識Socket-UDP編程
server端:學習

import socket

sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
sk.bind(("127.0.0.1", 8089))
msg, address = sk.recvfrom(1024)
print(msg)
sk.sendto(b'hi', address)

client端:編碼

import socket

sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)

sk.sendto(b'hello', ("127.0.0.1", 8089))
msg, address = sk.recvfrom(1024)
print(msg)

跟TCP編程原理差很少,只是UDP不用長時間創建鏈接,發送一個包,不用等回覆,也不須要理會對方收到仍是沒收到,沒有嚴格意義上的服務端和客戶端


4.黏包現象
在使用TCP協議進行數據傳輸的時候, 會有如下問題出現.
client端:

import socket

sk = socket.socket()  # 建立通道
print("客戶端初始化完成")
sk.connect(("127.0.0.1", 8088))  # 創建鏈接
print("客戶端鏈接成功")

sk.send("哈哈".encode("utf-8"))
sk.send("哈哈".encode("utf-8")) #發送兩次
print("發送完畢")
sk.close()

server端:

import socket

# 建立socket通道
sk = socket.socket()
sk.bind(("127.0.0.1", 8088))  # 綁定ip和端口
sk.listen()  # 開始監聽
print("服務器就緒,等待鏈接")
conn, address = sk.accept()  # 程序阻塞,等待鏈接
print("有客戶端鏈接,ip是:", address)  # address是客戶端的ip和端口

msg1 = conn.recv(1024)
print(msg1.decode("utf-8"))
msg2 = conn.recv(1024)
print(msg2.decode("utf-8"))
sk.close()

server端收到的內容:
初識python網絡編程-01
能夠看到,兩次發送的內容,黏在一塊兒,變成了一個包,就是典型的黏包現象。
那麼如何解決黏包問題呢? 很簡單. 之因此出現黏包就是由於數據沒有邊界. 直接把兩個包混合成了一個包. 那麼我能夠在發送數據的時候. 指定邊界. 告訴對方. 我接下來這個數據包有多大. 對面接收數據的時候呢, 先讀取該數據包的大小.而後再讀取數據. 就不會產生黏包了.
普通話: 發送數據的時候制定數據的格式: 長度+數據 接收的時候就知道有多少是當前這個數據包的大小了. 也就至關於定義了分隔邊界了.
展現一波操做:
client端:

import socket

sk = socket.socket()  # 建立通道
print("客戶端初始化完成")
sk.connect(("127.0.0.1", 8088))  # 創建鏈接
print("客戶端鏈接成功")

s = "哈哈"
bs = sk.send(s.encode("utf-8"))
# 計算數據長度.格式化成四位數字
bs_len = format(len(bs), "04d").encode("utf-8")
# 發送數據以前,先發送數據的長度
sk.send(bs_len)
sk.send(bs)

# 發送第二次
sk.send(bs_len)
sk.send(bs)

print("發送完畢")
sk.close()

server端:

import socket

# 建立socket通道
sk = socket.socket()
sk.bind(("127.0.0.1", 8088))  # 綁定ip和端口
sk.listen()  # 開始監聽
print("服務器就緒,等待鏈接")
conn, address = sk.accept()  # 程序阻塞,等待鏈接
print("有客戶端鏈接,ip是:", address)  # address是客戶端的ip和端口

# 接收數據長度4個字節,轉化成數字
bs_len = int(conn.recv(4).decode("utf-8"))
# 讀取數據
msg1 = conn.recv(1024)
print(msg1.decode("utf-8"))
# 再接收數據長度,讀取數據
bs_len = int(conn.recv(4).decode("utf-8"))
msg2 = conn.recv(1024)
print(msg2.decode("utf-8"))
sk.close()

可是,你應該發現了,這種傳輸的方式雖然能解決黏包問題,可是每次接收都要先定義接收的長度,喵的豈不是太累了??這個問題,python也有本身的態度,代碼堅定要給你搞簡單,就引入了一個新的模塊struct,這是重點,拿小本本記下來,要考!!
展現一下struct的用法:

import struct

ret = struct.pack("i", 123456789)  # 把數字打包成字節
print(ret)

print(len(ret))  # 4  不論數字大小,定死了4個字節

# 把字節還原回數字
ds = b'\x15\xcd[\x07'
num = struct.unpack("i", ds)[0]   # num返回的是一個元祖,取索引爲0的元素,也就是第一個元素,就是咱們傳輸的數字
print(num)   # 123456789

好的,迴歸到黏包的問題上來,論如何優雅的解決黏包的問題:
client端:

import socket
import struct

sk = socket.socket()  # 建立通道
print("客戶端初始化完成")
sk.connect(("127.0.0.1", 8088))  # 創建鏈接
print("客戶端鏈接成功")

msg_bs = "你好呀".encode("utf-8")
msg_struct_len = struct.pack("i", len(msg_bs))
sk.send(msg_struct_len)
sk.send(msg_bs)
print("發送完畢")
# 發送第二次
sk.send(msg_struct_len)
sk.send(msg_bs)
print("發送完畢")

sk.close()

server端:

import socket
import struct

# 建立socket通道
sk = socket.socket()
sk.bind(("127.0.0.1", 8088))  # 綁定ip和端口
sk.listen()  # 開始監聽
print("服務器就緒,等待鏈接")
conn, address = sk.accept()  # 程序阻塞,等待鏈接
print("有客戶端鏈接,ip是:", address)  # address是客戶端的ip和端口

msg_struct_len = conn.recv(4)
msg_len = struct.unpack("i", msg_struct_len)[0]
data = conn.recv(msg_len)
print(data.decode("utf-8"))
print("接收完畢")
# 接收第二個包
msg_struct_len = conn.recv(4)
msg_len = struct.unpack("i", msg_struct_len)[0]
data = conn.recv(msg_len)
print(data.decode("utf-8"))
print("接收完畢")

sk.close()

可是吧,這樣也是稍顯繁瑣了,若是屢次使用,也可將用到的struct模塊,封裝起來,須要用到的時候,就導入這個自定義的模塊:
封裝的模塊my_struct_util.py:

import struct

def my_send(sk, msg):
    msg_len = msg.encode("utf-8")
    msg_struct_len = struct.pack("i", len(msg_len))
    sk.send(msg_struct_len)
    sk.send(msg_len)

def my_recv(sk):
    msg_struct_len = sk.recv(4)
    msg_len = struct.unpack("i", msg_struct_len)[0]
    data = sk.recv(msg_len)
    print(data.decode("utf-8"))

client端:

import socket
import my_socket_util as msu

sk = socket.socket()  # 建立通道
print("客戶端初始化完成")
sk.connect(("127.0.0.1", 8088))  # 創建鏈接
print("客戶端鏈接成功")
msu.my_send(sk, "你好嗎")
msu.my_send(sk, "你好嗎")

sk.close()

server端:

import socket
import my_socket_util as msu

# 建立socket通道
sk = socket.socket()
sk.bind(("127.0.0.1", 8088))  # 綁定ip和端口
sk.listen()  # 開始監聽
print("服務器就緒,等待鏈接")
conn, address = sk.accept()  # 程序阻塞,等待鏈接
print("有客戶端鏈接,ip是:", address)  # address是客戶端的ip和端口

msu.my_recv(conn)
msu.my_recv(conn)
sk.close()

這樣,是否是就簡便了不少呢~~

相關文章
相關標籤/搜索