九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
Python網(wǎng)絡編程基礎及socket之TCP收發(fā)消息及文件

網(wǎng)絡編程必須了解的基本概念

MAC地址:是全球唯一標示的網(wǎng)絡接口,每一個網(wǎng)卡接口、交換機接口、路由器接口的mac地址均不相同。mac地址是通信子網(wǎng)內部相互通信的標識,交換機根據(jù)mac地址區(qū)分用戶。mac地址是物理層的概念。

IP地址:ip是網(wǎng)絡層的網(wǎng)絡協(xié)議,通過路徑檢測和邏輯尋址等方法使得兩個端系統(tǒng)(pc與服務器、手機與服務器、手機與PC等)可經(jīng)由通信子網(wǎng)中多個節(jié)點傳輸數(shù)據(jù)。ip是跨越2個端系統(tǒng)跨越多個通信子網(wǎng)相互通信的前提。常見協(xié)議有IPv4和IPv6。

端口:端口是應用層的概念,ip解決了不同的端系統(tǒng)之間相互通信的問題,但進行網(wǎng)絡通信的實際上是端系統(tǒng)內部的不同進程之間進行的,可能存在多個進程。為了區(qū)分端系統(tǒng)內部不同進程所以引入了端口的概念。有個比較形象的比方,ip地址好比大樓地址、端口好比房間號碼。一條進程可使用多端口,但一個端口只能被一條進程獨占使用,其他進程訪問被已被占用的端口會報端口沖突。

TCP協(xié)議:是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議。建立連接需要三次握手,斷開連接需要四次揮手。優(yōu)點:有確認、窗口、重傳、擁塞控制機制,傳遞數(shù)據(jù)可以保證穩(wěn)定和可靠。缺點:效率低、占用系統(tǒng)資源高、易被攻擊 。常見的TCP應用及其端口有ssh:22、ftp:21、smtp:25、http:80、telnet:23、https:443、MYSQL:3306等。

UDP協(xié)議:是用戶數(shù)據(jù)報協(xié)議,提供面向事務的簡單不可靠信息傳送服務。使用UDP協(xié)議不用建立連接,它強調性能且不保證數(shù)據(jù)安全完整到達。優(yōu)點傳輸速度快,比TCP協(xié)議安全性略高。缺點:不可靠,不穩(wěn)定。常見的UDP應用及其端口有name server域名服務:53、bootps下載引導程序消息服務端:67、bootpc下載引導程序消息客戶端:68、TFTP簡單文件傳輸協(xié)議:69、RPC遠程過程調用:111、NTP網(wǎng)絡時間協(xié)議:123、SNMP簡單網(wǎng)絡管理協(xié)議:161、QQ:4000等。

TCP協(xié)議socket

  • 服務端方法
  1. 創(chuàng)建socket實例。
sk = socket.socket()
  1. 綁定ip和端口。
sk.bind(('127.0.0.1',9001))

說明:該方法有一個參數(shù),類型是元組(元組的第一個元素是IP,第二個元素是端口)。無返回值。

  1. 開啟監(jiān)聽。
sk.listen()

說明:該方法有一個參數(shù),類型是int??梢灾付ㄖ付ㄏ到y(tǒng)允許暫未 accept 的連接數(shù),超過后將拒絕新連接。未指定則自動設為合理的默認值,一般使用缺省值即可。無返回值。

  1. 接入客戶端,被動接受TCP客戶端連接,(阻塞式)等待連接的到來。
conn, ip_port = sk.accept()

說明:該方法無參數(shù)。返回2個值,第一個是socket實例,第二個是元組(元組的第一個元素是IP,第二個元素是端口)。如果server端需要服務多個客戶,那么accept()應該放入循環(huán),每次成功接入后開啟新的線程為新的客戶端服務。

  • 客戶端方法
  1. 創(chuàng)建socket實例。
sk = socket.socket()
  1. 連接服務端。
sk.connect(('127.0.0.1',9001))

說明:該方法有一個參數(shù),類型是元組(元組的第一個元素是IP,第二個元素是端口)。無返回值。

  • 公共方法
  1. 發(fā)送數(shù)據(jù)。
sk.sendall()
  1. 接收數(shù)據(jù)。
sk.recv()
  • 案例代碼:以下寫一個回聲的簡單案例,服務端多線程提供服務,接收客戶端發(fā)來的信息,并返回時間戳。
    • 服務端
import socketfrom threading import Threadfrom threading import enumerate as enimport timeHOST = '127.0.0.1' # IP地址PORT = 50007 # 端口max_connect = 5 # 最大連接數(shù)def talk(conn): while True: data = conn.recv(1024).decode() if data == 'q' or data == 'Q': return conn.sendall( f'服務端于{time.strftime('%Y年%m月%d日%H時%M分%S秒')}收到了你發(fā)來的“{data}”信息!'.encode())def main(): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind((HOST, PORT)) s.listen() while True: conn, _ = s.accept() if len(en()) < max_connect: # 如果線程數(shù)未超過最大連接數(shù),那么開啟線程接入客戶端提供服務 Thread(target=talk, args=(conn,)).start()if __name__ == '__main__': main()
    • 客戶端
import socketimport sysHOST = '127.0.0.1'        # IP地址PORT = 50007              # 端口with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:    s.connect((HOST, PORT))    while True:        message = input('輸入你想發(fā)給服務端的信息:')        if not message:            continue        s.sendall(message.encode())        if message == 'q' or message == 'Q':            break        data = s.recv(1024).decode()        print(f'收到服務端發(fā)來的信息:{data}')
  • 常見問題
    • 粘包問題:因為TCP協(xié)議是流式協(xié)議所以數(shù)據(jù)包之間沒有邊界,那么有時會因為操作系統(tǒng)緩存機制、網(wǎng)絡延遲等原因造成2次間隔時間較短、數(shù)據(jù)量較少的數(shù)據(jù)合并成一次發(fā)送。因而影響了數(shù)據(jù)的完整性。
    • 解決策略:常見的解決方式是通過自定義協(xié)議厘清數(shù)據(jù)包之間的邊界。常見的方法是發(fā)送數(shù)據(jù)前先發(fā)4字節(jié)的數(shù)據(jù)包長度然后再發(fā)信息,接收時先接收4字節(jié)的數(shù)據(jù)包長度再按長度接收信息。
      • 發(fā)送方: 1.發(fā)送數(shù)據(jù)包前先計算長度,再將int型長度數(shù)據(jù)轉換成4字節(jié)的bytes型; 2.先發(fā)送4字節(jié)bytes型長度數(shù)據(jù),再發(fā)送數(shù)據(jù)包。
      • 接收方: 1.先接收4字節(jié)bytes型長度數(shù)據(jù),將其轉換成int型長度數(shù)據(jù)。 2.只接收指定長度的數(shù)據(jù)。 以上協(xié)議是服務端和終端雙方均要遵守的自定義協(xié)議。這樣就可以解決粘包問題。
      • stuct模塊pack和unpack缺陷:處理粘包問題我查閱了很多資料,看到絕大多數(shù)人都是import struct,使用struct.pack和unpack來完成int數(shù)據(jù)與bytes相互轉換的工作。但是我覺得struct模塊的pack和unpack有2個缺陷:一是表示數(shù)值范圍是-2147483648至2147483647,負值在計算數(shù)據(jù)長度完全用不上,會造成上傳、下載文件大小不能超過2個G,unpack返回的是一個元組,還要對元組解包才能使用。所以我嘗試自己寫了一個pack和unpack在下面分享給大家。
      • 自定義pack和unpack
def pack(n): if n >= 4294967296 or n < 0: raise ValueError('The value is out of range.') values = ((0b11111111000000000000000000000000, 24), (0b111111110000000000000000, 16), (0b1111111100000000, 8), (0b11111111, 0)) ret = b'' for i in values: x = (n & i[0]) >> i[1] x = x.to_bytes(length=1, byteorder='big') ret += x return retdef unpack(numlist): if len(numlist) != 4: raise ValueError('The bytes length must be 4.') values = (24, 16, 8, 0) ret = 0 i = 0 while i < 4: n = numlist[i] ret += n << values[i] i += 1 return ret
      • 代碼說明: 自定義的pack函數(shù)表示范圍是0-4294967295,上傳、下載文件長度在4個g以內都不會報錯。 在該函數(shù)中通過按位與配合位移算法以及python3內置函數(shù)to_bytes()來完成功能,不需要另外import。 自定義的unpack函數(shù)直接返回int型數(shù)值,不需要解包。在這個函數(shù)里全部是自定義的代碼,沒有引用任何函數(shù)也沒有導包,通過位移運算完成bytes轉換成int。
    • 自定義類處理收發(fā)消息和文件
      • 編寫TCP協(xié)議socket應用時經(jīng)常會遇到發(fā)送消息和發(fā)送文件2種需求,如果將發(fā)送文件和發(fā)送消息封裝到類中,會很方便。
      • 代碼案例
        • common.py文件,存放了Transeiver類:
from os.path import getsize  # 導入os模塊中getsize函數(shù),方便檢查文件大小class Transceiver:    def __init__(self, conn, path='.', buffer=65536) -> None:        self.conn = conn  # 綁定接口        self.path = path  # 綁定工作目錄        self.buffer = buffer  # 綁定文件緩沖區(qū)大小    @staticmethod  # 靜態(tài)方法    def pack(n: int) -> bytes:        '''將int型長度值轉換成4字節(jié)bytes型數(shù)據(jù)'''        if n >= 4294967296 or n < 0:            raise ValueError('The value is out of range.')        values = ((0b11111111000000000000000000000000, 24),                  (0b111111110000000000000000, 16), (0b1111111100000000, 8), (0b11111111, 0))        ret = b''        for i in values:            x = (n & i[0]) >> i[1]            x = x.to_bytes(length=1, byteorder='big')            ret += x        return ret    @staticmethod  # 靜態(tài)方法    def unpack(numlist: bytes) -> int:        '''將4字節(jié)bytes型轉換回int型長度值'''        if len(numlist) != 4:            raise ValueError('The bytes length must be 4.')        values = (24, 16, 8, 0)        ret = 0        i = 0        while i < 4:            n = numlist[i]            ret += n << values[i]            i += 1        return ret    def send(self, message: str) -> None:        '''發(fā)送字符串消息'''        if (type(message) is str):  # 判斷消息類型            message = message.encode()  # 將消息轉成bytes型,缺省參數(shù)是utf8編碼        self.conn.sendall(self.pack(len(message)))  # 發(fā)送消息前先發(fā)消息的長度        self.conn.sendall(message)  # 發(fā)送消息    @property    def recv(self) -> str:        length = self.unpack(self.conn.recv(4))        return self.conn.recv(length).decode()  # 返回消息字符串    def send_file(self, name: str) -> int:        '''發(fā)送文件'''        try:            length = getsize(self.path + '/' + name)            if length > 4294967295:  # 文件大小超過4gb,返回 -2 ,停止發(fā)送文件                return -2            self.send(name + ',' + str(length))            with open(self.path + '/' + name, mode='rb') as f:  # 二進制讀模式打開指定文件                while length >= 0:  # 如果未發(fā)送的文件數(shù)據(jù)長度大于等于0則循環(huán)                    file = f.read(self.buffer)  # 從文件中讀取指定大小的文件數(shù)據(jù)                    self.conn.sendall(file)  # 發(fā)送文件數(shù)據(jù)                    length -= self.buffer  # 計算未發(fā)送的文件數(shù)據(jù)長度        except FileNotFoundError:            return -1  # 文件找不到,返回 -1 ,停止發(fā)送文件        return 1  # 文件正常發(fā)送完畢,返回1    def recv_file(self) -> int:        '''接收文件'''        try:            name, length = self.recv.split(',')  # 獲取文件名和文件長度            with open(self.path + '/' + name, mode='wb') as f:  # 接收文件寫入指定目錄下                size = 0  # 計算接收的數(shù)據(jù)大小                length = int(length)  # 文件長度                while length > size:  # 文件長度大于已接收的數(shù)據(jù)長度則循環(huán)                    file = self.conn.recv(self.buffer)  # 接收文件數(shù)據(jù)                    f.write(file)  # 寫入文件數(shù)據(jù)                    size += len(file)  # 累加已接收到的文件數(shù)據(jù)大小        except FileNotFoundError:            return -1  # 文件找不到,返回 -1 ,停止發(fā)送文件        return 1  # 文件正常接收完畢,返回1
        • server.py文件,服務端代碼
import socketfrom threading import Threadfrom threading import enumerate as enimport timeimport commonHOST = '127.0.0.1' # IP地址PORT = 50007 # 端口max_connect = 5def talk(conn): while True: m = common.Transceiver(conn, path='d:/2') data = m.recv if data == 'q' or data == 'Q': return elif data == 'file' or data == 'FILE': m.recv_file() else: m.send( f'服務端于{time.strftime('%Y年%m月%d日%H時%M分%S秒')}收到了你發(fā)來的“{data}”信息!')def main(): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind((HOST, PORT)) s.listen() while True: conn, _ = s.accept() if len(en()) <= max_connect: Thread(target=talk, args=(conn,)).start()if __name__ == '__main__': main()
        • client.py文件,客戶端代碼
import socketimport commonHOST = '127.0.0.1'        # IP地址PORT = 50007              # 端口with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:    s.connect((HOST, PORT))    m = common.Transceiver(s, path='d:/1')    while True:        message = input('輸入你想發(fā)給服務端的信息:')        if not message:            continue        m.send(message)        if message == 'q' or message == 'Q':            break        elif message == 'file' or message == 'FILE':            name = input('請輸入文件名:')            check = m.send_file(name)            if check == 1:                print('文件發(fā)送成功!')            elif check == -1:                print('該文件找不到')            elif check == -2:                print('該文件超過4GB,太大了,不能發(fā)送!')            continue        data = m.recv        print(f'收到服務端發(fā)來的信息:{data}')
      • 認真閱讀并理解上述代碼,從中可以發(fā)現(xiàn)使用類的好處。在common.py文件中定義了Transceiver收發(fā)器類。接下來在服務端和客戶端中發(fā)送消息和發(fā)送文件代碼都很簡潔,提高了代碼復用率。假設需要增加需求,發(fā)送接收文件需要校驗md5,那么只需要在Transceiver收發(fā)器類中修改send_file方法和recv_file方法即可,而客戶端和服務端的代碼無須改動,這樣面向對象的編程方法可維護性極高。
本站僅提供存儲服務,所有內容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權內容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Python學習筆記33:用struct模塊解決tcp協(xié)議傳輸過程中的黏包問題
python之socket編程
python從步兵到騎兵(五)
go語言系列-TCP編程
基于Linux操作系統(tǒng)下的TCP/IP網(wǎng)絡通信研究與應用
一次入侵應急響應分析
更多類似文章 >>
生活服務
熱點新聞
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服