中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

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

初識python網絡編程-01

發布時間:2020-06-21 15:11:52 來源:網絡 閱讀:190 作者:山水共安榮 欄目:編程語言

近期學習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完成數據傳輸是非常方便的.
話不多說,練起來吧~~

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端:

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()

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

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

湟源县| 济南市| 武威市| 光泽县| 宣汉县| 防城港市| 江津市| 郯城县| 工布江达县| 大荔县| 阿坝县| 新泰市| 策勒县| 承德县| 桐柏县| 锦屏县| 突泉县| 奉贤区| 新乡县| 陕西省| 甘泉县| 彩票| 南丹县| 武邑县| 兴化市| 莫力| 赤水市| 莱州市| 海安县| 西藏| 宣威市| 牡丹江市| 斗六市| 商河县| 普格县| 长宁区| 剑川县| 孟连| 故城县| 旌德县| 黎川县|