溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

初識python網(wǎng)絡(luò)編程-01

發(fā)布時間:2020-06-21 15:11:52 來源:網(wǎng)絡(luò) 閱讀:190 作者:山水共安榮 欄目:編程語言

近期學(xué)習(xí)python基礎(chǔ)知識,到網(wǎng)絡(luò)編程部分覺得很有意思,寫個博客記錄一下,首先可先了解一下計算機網(wǎng)絡(luò)的基礎(chǔ)知識,參考相關(guān)書籍即可,本文只做簡單知識介紹!


1.OSI七層協(xié)議,TCP、UDP協(xié)議

首先, 我們今天使用的計算機都是要聯(lián)網(wǎng)使用的. 很少有那種單機走天下的情況了. 那么我們的計算機是如何通過網(wǎng)絡(luò)實現(xiàn)通信的. 我們先了解一些關(guān)于網(wǎng)絡(luò)的基礎(chǔ)知識. 然后再開始學(xué)習(xí)一些關(guān)于網(wǎng)絡(luò)編程的內(nèi)容, 第一個要解釋的名詞叫協(xié)議. 我們只有明白協(xié)議是什么, 后面再看各種各樣的通信規(guī)則就容易的多了.
官話: 網(wǎng)絡(luò)協(xié)議是通信計算機雙方必須共同遵從的一組約定。如怎么樣建立連接、怎么樣互相識別等。只有遵守這個約定,計算機之間才能相互通信交流
普通話: 兩臺計算機之間約定好. 我發(fā)送的數(shù)據(jù)格式是什么. 你接收到數(shù)據(jù)之后. 使用相同的格式來拿到數(shù)據(jù)
例子: 你和一個韓國人交流. 你說中文, 他說韓文. 你倆是不能明白對方說的是什么的. 怎么辦. 你倆約定好, 都說英語.實現(xiàn)交流. 這個就叫協(xié)議.
網(wǎng)絡(luò)協(xié)議: 互聯(lián)網(wǎng)之間互相傳遞消息的時候使用統(tǒng)一的一系列約定
在今天的互聯(lián)網(wǎng)數(shù)據(jù)傳輸中一般使用的是OSI七層協(xié)議. 也有簡稱為五層, 四層協(xié)議. 只是對不同網(wǎng)絡(luò)層的定義不同.
內(nèi)部原理和作用是一樣的.
初識python網(wǎng)絡(luò)編程-01

下面簡單介紹一下OSI七層協(xié)議的基本功能
首先是物理層: 該層是網(wǎng)絡(luò)通信的數(shù)據(jù)傳輸介質(zhì),由連接不同結(jié)點的電纜與設(shè)備共同構(gòu)成。主要跟功能是:利用傳輸介質(zhì)為數(shù)據(jù)鏈路層提供物理連接,負(fù)責(zé)處理數(shù)據(jù)傳輸并監(jiān)控數(shù)據(jù)出錯率,以便數(shù)據(jù)流的透明傳輸
數(shù)據(jù)鏈路層: 這一層負(fù)責(zé)裝配自己和對方主機的MAC地址. MAC地址: 每個網(wǎng)絡(luò)設(shè)備的唯一編碼. 全球唯一. 由不同廠商直接燒錄在網(wǎng)卡上.
作用: 在龐大的網(wǎng)絡(luò)系統(tǒng)中, 你要發(fā)送的數(shù)據(jù)到底是要給誰的. 由誰發(fā)送出來的. 這就相當(dāng)于你寫信的時候的信封. 上面得寫清楚收貨人地址.
網(wǎng)絡(luò)層: 在有了MAC地址其實我們的電腦就可以開始通信了. 但是. 此時的通信方式是廣播. 相當(dāng)于通信基本靠吼. 你發(fā)送一個數(shù)據(jù)出去. 會自動的發(fā)給當(dāng)前網(wǎng)絡(luò)下的所有計算機. 然后每個計算機的網(wǎng)卡會看一眼這個數(shù)據(jù)是不是發(fā)給自己的. 像這樣的通信方式, 如果計算機的數(shù)據(jù)量少. 是沒有問題的. 但是. 如果全球所有計算機都按照這樣的方式來傳輸消息. 那不僅是效率的問題了. 絕對是災(zāi)難性的. 那怎么辦. 大家就想到了一個新的方案, 這個方案叫IP協(xié)議. 使用IP協(xié)議就把不同區(qū)域的計算機劃分成一個一個的子網(wǎng). 子網(wǎng)內(nèi)的通信使用廣播來傳遞消息. 廣播外通過路由進行傳遞消息. 你可以理解為不同快遞公司的分撥中心. 我給你郵寄一個快遞. 先看一下是不是自己區(qū)域的. 是自?己區(qū)域直接挨家挨戶找就OK了. 但是如果不是我這個區(qū)域的. 就通過分撥中心(路由器網(wǎng)關(guān))找到你所在的區(qū)域的分撥中心(路由?網(wǎng)關(guān)), 再通過你的分撥中心下發(fā)給你. 這里IP協(xié)議的作用就體現(xiàn)出來了. 專門用來劃分子網(wǎng)的.那么在傳輸數(shù)據(jù)的時候就必須要把對方的ip地址帶著. 有了這個ip再加上子網(wǎng)掩碼就可以判斷出該數(shù)據(jù)到底是屬于哪個子網(wǎng)下的數(shù)據(jù).
IP地址: 由4位點分?十進制表示. 每位最?大255. 故IP地址的范圍: 0.0.0.0~255.255.255.255. 為什什么是255, 答:
28 每一位用8位2進制表示, 合起來32位就可以表示一個計算機的ip地址
子網(wǎng)掩碼: 用來劃分子網(wǎng)的一個4位點分十進制.
網(wǎng)關(guān): 路由?在子網(wǎng)內(nèi)的ip. 不同局域網(wǎng)進行數(shù)據(jù)傳輸?shù)慕涌?分撥中心)
計算子網(wǎng)的過程:

1 ip1: 192.168.123.16
2 ip2: 192.168.123.45

3 子網(wǎng)掩碼: 255.255.255.0

4 全部轉(zhuǎn)化成二進制
4 ip1: 11000000 10101000 01111011 00010000
5 ip2: 11000000 10101000 01111011 00101101
6 子網(wǎng): 11111111 11111111 11111111 00000000

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

傳輸層: 我們現(xiàn)在解決了外界的數(shù)據(jù)傳輸問題. 使用MAC地址和IP地址可以唯一的定位到一臺計算機了. 那么還有一個問題沒有解決. 我們知道一臺計算機內(nèi)是很有可能運行著多個網(wǎng)絡(luò)應(yīng)用程序的. 比如, 你開著LOL, 掛著DNF, 聊著QQ, 還看著快播. 那么此時你的計算機網(wǎng)卡接收到了來自遠(yuǎn)方的一條數(shù)據(jù). 那么這一條數(shù)據(jù)到底給那個應(yīng)用呢? 說白了, 快遞送到你公司了. 地址沒毛病了. 可是你公司那么多人. 這個快遞到底給誰? 不能亂給啊. 怎么辦呢? 互聯(lián)網(wǎng)大佬們想到了一個新詞叫端口.
傳輸層規(guī)定: 給每?一個應(yīng)?用程序分配一個唯一的端口號. 當(dāng)有數(shù)據(jù)發(fā)送過來之后. 通過端口號來決定該數(shù)據(jù)發(fā)送的具體應(yīng)用程序.但是根據(jù)不同應(yīng)用程序?qū)W(wǎng)絡(luò)的需求的不同(有的要求快, 有的要求可靠) 又把傳輸層劃分成兩個協(xié)議. 一個叫TCP, 一個叫UDP. 所以, 我們常說的TCP/IP協(xié)議中最重要, 也是我們最關(guān)注的其實就是IP和端口了了. 因為有了這兩個, 我們其實就可以定位到某一臺計算機上的某個網(wǎng)絡(luò)應(yīng)用程序了了. 也就可以給他發(fā)送消息了
32位計算機上的端口數(shù):
TCP : 65536個
UDP: 65536個
TCP和UDP的區(qū)別:

  1. TCP, 它是基于連接的. 是連續(xù)的, 可靠的. 效率比較低. 更像是打電話. 聊天的過程中不能中斷.
  2. UDP, 它不是基于連接的. 是不連續(xù)的, 不可靠的. 效率比較高. 更像是寄信, 今兒一封, 明兒一封. 想啥時候發(fā)就啥時候發(fā).
    應(yīng)用層: TCP+IP可以定位到計算機上的某個應(yīng)用了了. 但是不同用傳輸?shù)臄?shù)據(jù)格式可能是不一樣的. 就好比快遞. 有的是大包裹. 有的是小文件. 一個要用大快遞袋裝, 一個要用小快遞袋裝. 到了了應(yīng)用層. 我們一般是根據(jù)不同類型的應(yīng)用程序進行的再一次封裝. 比如, HTTP協(xié)議, SMTP協(xié)議, FTP協(xié)議. 等等.

2.初識Socket-TCP編程
在幾乎所有的編程語言中, 我們在編寫網(wǎng)絡(luò)程序的時候都要使用到socket. socket翻譯過來叫套接字. 我們上面也了解到了一次網(wǎng)絡(luò)通信的數(shù)據(jù)需要包裹著mac, ip, port等信息. 但是如果每次我們開發(fā)都要程序員去?個一個的去準(zhǔn)備數(shù)據(jù), 那工作量絕對是絕望的. 所以, 計算機提出了了socket. socket幫助我們完成了網(wǎng)絡(luò)通信中的絕大多數(shù)操作. 我們只需要告訴socket. 我要向哪臺計算機(ip, port)發(fā)送數(shù)據(jù). 剩下的所有東西都由socket幫我們完成. 所以使用socket完成數(shù)據(jù)傳輸是非常方便的.
話不多說,練起來吧~~

server端:

import socket

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

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

client端:

import socket

sk = socket.socket()  # 創(chuàng)建通道
print("客戶端初始化完成")
sk.connect(("127.0.0.1", 8088))  # 建立連接
print("客戶端連接成功")
while 1:   #持續(xù)向服務(wù)端發(fā)送消息
    print(sk.recv(1024).decode("utf-8"))  # 最大接受1024字節(jié)的內(nèi)容
    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不用長時間建立連接,發(fā)送一個包,不用等回復(fù),也不需要理會對方收到還是沒收到,沒有嚴(yán)格意義上的服務(wù)端和客戶端


4.黏包現(xiàn)象
在使用TCP協(xié)議進行數(shù)據(jù)傳輸?shù)臅r候, 會有以下問題出現(xiàn).
client端:

import socket

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

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

server端:

import socket

# 創(chuàng)建socket通道
sk = socket.socket()
sk.bind(("127.0.0.1", 8088))  # 綁定ip和端口
sk.listen()  # 開始監(jiān)聽
print("服務(wù)器就緒,等待連接")
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端收到的內(nèi)容:
初識python網(wǎng)絡(luò)編程-01
可以看到,兩次發(fā)送的內(nèi)容,黏在一起,變成了一個包,就是典型的黏包現(xiàn)象。
那么如何解決黏包問題呢? 很簡單. 之所以出現(xiàn)黏包就是因為數(shù)據(jù)沒有邊界. 直接把兩個包混合成了一個包. 那么我可以在發(fā)送數(shù)據(jù)的時候. 指定邊界. 告訴對方. 我接下來這個數(shù)據(jù)包有多大. 對面接收數(shù)據(jù)的時候呢, 先讀取該數(shù)據(jù)包的大小.然后再讀取數(shù)據(jù). 就不會產(chǎn)生黏包了.
普通話: 發(fā)送數(shù)據(jù)的時候制定數(shù)據(jù)的格式: 長度+數(shù)據(jù) 接收的時候就知道有多少是當(dāng)前這個數(shù)據(jù)包的大小了. 也就相當(dāng)于定義了分隔邊界了.
展示一波操作:
client端:

import socket

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

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

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

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

server端:

import socket

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

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

但是,你應(yīng)該發(fā)現(xiàn)了,這種傳輸?shù)姆绞诫m然能解決黏包問題,但是每次接收都要先定義接收的長度,喵的豈不是太累了??這個問題,python也有自己的態(tài)度,代碼堅決要給你搞簡單,就引入了一個新的模塊struct,這是重點,拿小本本記下來,要考??!
展示一下struct的用法:

import struct

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

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

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

好的,回歸到黏包的問題上來,論如何優(yōu)雅的解決黏包的問題:
client端:

import socket
import struct

sk = socket.socket()  # 創(chuàng)建通道
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("發(fā)送完畢")
# 發(fā)送第二次
sk.send(msg_struct_len)
sk.send(msg_bs)
print("發(fā)送完畢")

sk.close()

server端:

import socket
import struct

# 創(chuàng)建socket通道
sk = socket.socket()
sk.bind(("127.0.0.1", 8088))  # 綁定ip和端口
sk.listen()  # 開始監(jiān)聽
print("服務(wù)器就緒,等待連接")
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模塊,封裝起來,需要用到的時候,就導(dǎo)入這個自定義的模塊:
封裝的模塊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()  # 創(chuàng)建通道
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

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

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

這樣,是不是就簡便了很多呢~~

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI