欧美成人午夜免费全部完,亚洲午夜福利精品久久,а√最新版在线天堂,另类亚洲综合区图片小说区,亚洲欧美日韩精品色xxx

千鋒扣丁學堂Python培訓之基于tcp協(xié)議的通信(數(shù)據(jù)傳輸)實例講解

2019-07-22 13:57:04 3348瀏覽

今天千鋒扣丁學堂Python培訓老師為大家分享一篇python中基于tcp協(xié)議的通信(數(shù)據(jù)傳輸)實例講解,下面我們一起來看一下吧。



tcp協(xié)議:流式協(xié)議(以數(shù)據(jù)流的形式通信傳輸)、安全協(xié)議(收發(fā)信息都需收到確認信息才能完成收發(fā),是一種雙向通道的通信)

tcp協(xié)議在OSI七層協(xié)議中屬于傳輸層,它上承用戶層的數(shù)據(jù)收發(fā),下啟網(wǎng)絡層、數(shù)據(jù)鏈路層、物理層。可以說很多安全數(shù)據(jù)的傳輸通信都是基于tcp協(xié)議進行的。

為了讓tcp通信更加方便需要引入一個socket模塊(將網(wǎng)絡層、數(shù)據(jù)鏈路層、物理層封裝的模塊),我們只要調(diào)用模塊中的相關(guān)接口就能實現(xiàn)傳輸層下面的繁瑣操作。

簡單的tcp協(xié)議通信模板:(需要一個服務端和一個客戶端)

服務端:
 
from socket import *
# 確定服務端傳輸協(xié)議↓↓↓↓↓↓↓
server = socket(AF_INET, SOCK_STREAM) # 這里的SOCK_STREAM代表的就是流式協(xié)議TCP,如果是SOCK_DGRAM就代表UDP協(xié)議
# 固定服務端IP和PORT,讓客戶端能夠通過IP和端口訪問服務端↓↓↓↓↓↓↓
server.bind(('127.0.0.1', 8080))  # ('127.0.0.1', 8080)這里必須用元組形式傳入IP和PORT,本地訪問本地IP默認為'127.0.0.1'
# 設置半連接池數(shù)量(一般為5)
server.listen(5) # 半連接池:客戶端連接請求個數(shù)的容器,當前已連接的客戶端信息收發(fā)未完成前,會有最大5個客戶端連接請求進入排隊狀態(tài),
         # 等待上一個通信完畢后,就可以連接進入開始通信。                                           
 
# 雙向通道建立成功,可以進行下一步數(shù)據(jù)的通信了↓↓↓↓↓↓↓
conn, client_addr = server.accept()
# 進行一次信息的收與發(fā)
data = conn.recv(1024)  # 每次最大接收1024字節(jié),收到的數(shù)據(jù)為二進制Bytes類型
 
conn.send(data.upper())  # 將收到的數(shù)據(jù)進行處理,返回新的數(shù)據(jù),反饋給客戶端(給客戶端發(fā)數(shù)據(jù)),發(fā)的數(shù)據(jù)類型也必須是Bytes類型
 
# 一輪信息收發(fā)完畢,關(guān)閉已經(jīng)建立的雙向通道
conn.close()
 
 
客戶端:
from socket import *
# 確定客戶端傳輸協(xié)議↓↓↓↓↓↓↓(服務端和客戶端服務協(xié)議一樣才能進行有效的通信)
client = socket(AF_INET, SOCK_STREAM) # 這里的SOCK_STREAM代表的就是流式協(xié)議TCP,如果是SOCK_DGRAM就代表UDP協(xié)議
# 開始連接服務端IP和PORT,建立雙向鏈接
client.connect(('127.0.0.1', 8080)) # 通過服務端IP和PORT進行連接
 
# 走到這一步就已經(jīng)建立連接完畢,接下來開始數(shù)據(jù)通信:
client.send('hello,server'.encode('utf-8'))  # 將發(fā)送的信息轉(zhuǎn)碼成Bytes類型數(shù)據(jù)
 
data = client.recv(1024) # 每次最大收數(shù)據(jù)大小為1024字節(jié)(1kb)
 
print(data.decode('utf-8')) # 將b類型數(shù)據(jù)轉(zhuǎn)換成字符串格式
 
# 一次傳輸完畢
client.close()  # 關(guān)閉客戶端連接
 
 
啟動服務端(服務端開始監(jiān)聽客戶端的連接請求)
啟動客戶端(客戶端給服務端發(fā)送連接請求)
建立雙向鏈接完成
客戶端給服務端發(fā)送信息 hello,server
服務端收到hello,server,將其轉(zhuǎn)換成大寫,返回給客戶端(此時服務端一輪通信完畢)
客戶端收到服務端的反饋信息,打印出HELLO,SERVER(此時客戶端一輪通信完畢)

以上是最基本的一次基于tcp協(xié)議通信的過程客戶端發(fā),服務端收,服務端處理數(shù)據(jù)然后發(fā),客戶端收到服務端發(fā)了的反饋數(shù)據(jù)。

TCP協(xié)議的通信粘包問題:

但是由于tcp協(xié)議是一種流式協(xié)議,流式協(xié)議就會有一個特點:數(shù)據(jù)的傳輸像一涓涓水流的形式傳輸,我們在收數(shù)據(jù)的時候默認最大收數(shù)據(jù)大小為1024字節(jié),當發(fā)送的數(shù)據(jù)小于1024字節(jié)時候當然不會有問題,一次性全部收完,但是但是但是當發(fā)送的數(shù)據(jù)大于1024字節(jié)的時候,我們這邊又不知道發(fā)送的數(shù)據(jù)大小是多少,只能默認的1024字節(jié)的時候,數(shù)據(jù)一次性就不可能收完,只能在這次收1024字節(jié),那1024字節(jié)以外的數(shù)據(jù)呢?由于數(shù)據(jù)的傳輸是流式協(xié)議,所以沒有收完的數(shù)據(jù)會依次排隊在門外等著,等待你下次收數(shù)據(jù)時候再次收取,這樣如果每次傳的數(shù)據(jù)大小不確認,收的時候數(shù)據(jù)也不知道該收多少的時候,就會導致每次收數(shù)據(jù)的時候收不完,收不完的數(shù)據(jù)就會在緩存中排隊,等待下次收,收不完的數(shù)據(jù)就好像粘粘在一起(zhannian)。這就叫tcp的流式協(xié)議的通信粘包問題。

這個問題的更形象過程可以見下圖:



知道這粘包的大致過程,就能夠找到方法對癥下藥了:

粘包問題的解決分析:

粘包問題歸根到底是數(shù)據(jù)接收不徹底導致,那么要解決這個問題最直接的方法就是每次都徹底地收完數(shù)據(jù)。

要想達到這個目的就需要每次在收數(shù)據(jù)之前事先知道我要收數(shù)據(jù)的文件大小,知道了文件大小我們就能有的放矢,準確的把數(shù)據(jù)收完不遺留。

解決方法:先發(fā)個包含待發(fā)送文件大小長度的報頭文件>>>>再發(fā)送原始文件

引入模塊struct

具體看代碼:

服務端:
import socket
import struct
 
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
  conn, client_addr = server.accept()
  print('客戶端已連接')
  while True:
    try:
      head = conn.recv(4)
      size = struct.unpack('i', head)[0]
      data = conn.recv(size)
      print('已收到客戶端信息:', data.decode('utf-8'))
    except ConnectionResetError:
      print('客戶端已中斷連接')
      conn.close()
      break
 
客戶端:
import socket
import struct
while True:
  try:
    client = socket.socket()
    client.connect(('127.0.0.1', 8080))
    print('已連接到服務端')
    while True:
      try:
        msg = 'abcdefghijklmnopqrstuvwxyz1234567890'.encode('utf-8')
        head = struct.pack('i', len(msg))
        client.send(head)
        client.send(msg)
 
      except ConnectionResetError:
        print('服務端已中斷連接')
        client.close()
        break
 
  except ConnectionRefusedError:
    print('無法連接到服務器')

以上方法只是為了試驗解決粘包問題,真正應用場景可以是上傳或者下載一個大文件的時候,這時就必須要提前知道接收的文件實際大小,做到100%精確的接收每一個數(shù)據(jù),這時就需要收數(shù)據(jù)前獲取即將收到的文件大小,然后對癥下藥,做到精確接收,但實現(xiàn)方法不一定非要用struct模塊,struct模塊只是解決粘包問題中的一個官方正式的方法,自己還可以有自己的想法,比如先直接把要發(fā)送文件的大小已字符串的格式發(fā)送過去,然后再發(fā)送這個文件,目的只有一個,知道我接收的文件的大小,精準接收文件。

下面寫一個客戶端從服務端下載文件的實例,供大家參考:(假設下載文件在服務端文件同一級)

下載服務端:
 
import socket
import time
import struct
import json
 
# 計算當前文件夾下文件的md5值、大小
import os, hashlib
 
def get_info(file_name):
  file_info = {}
  base_dir = os.path.dirname(__file__)
  file_dir = os.path.join(base_dir, file_name)
  if os.path.exists(file_dir):
    # md5計算時文件數(shù)據(jù)是放在內(nèi)存中的,當我們計算一個大文件時,可以用update方法進行分步計算,
    # 每次添加部分文件數(shù)據(jù)進行計算,減少內(nèi)存占用。
    with open(file_dir, 'rb') as f:
      le = 0
      d5 = hashlib.md5()
      for line in f:
        le += len(line)
        d5.update(line)
      file_info['lenth'] = le # 將文件長度加入報頭字典
      file_md5 = d5.hexdigest()
      file_info['md5'] = file_md5 # 將文件md5加入報頭字典
    file_size = os.path.getsize(file_dir) / float(1024 * 1024)
    file_info['size(MB)'] = round(file_size, 2) # 將文件大小加入報頭字典
    return file_info
  else:
    return file_info
 
 
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
  conn, client_addr = server.accept()
  print('%s >:客戶端(%s)已連接' % (time.strftime('%Y-%m-%d %H:%M:%S'), client_addr))
  while True:
    try:
      download_filename = conn.recv(1024).decode('utf-8')
      download_file_info_dic = get_info(download_filename)
      j_head = json.dumps(download_file_info_dic) # 將文件信息字典轉(zhuǎn)成json字符串格式
      head = struct.pack('i', len(j_head))
      conn.send(head)
      conn.send(j_head.encode('utf-8'))
      if not download_file_info_dic:
        continue
      with open(download_filename, 'rb') as f:
        while True:
          data=f.read(1024)
          conn.send(data)
        # for line in f:
        #   conn.send(line)
 
    except ConnectionResetError:
      print('%s >:客戶端(%s)已斷開' % (time.strftime('%Y-%m-%d %H:%M:%S'), client_addr))
      conn.close()
      break

下載客戶端:
 
import socket
import time
import struct
import json
 
# 進度條顯示
def progress(percent,width=30):
  text=('\r[%%-%ds]'%width)%('x'*int(percent*width))
  text=text+'%3s%%'
  text=text%(round(percent*100))
  print(text,end='')
 
while True:
  try:
    client = socket.socket()
    client.connect(('127.0.0.1', 8080))
    print('%s >:已連接到服務端' % time.strftime('%Y-%m-%d %H:%M:%S'))
    while True:
      try:
        file_name = input('請輸入下載文件名稱:')
        client.send(file_name.encode('utf-8'))
 
        head = client.recv(4) # 收報頭
        j_dic_lenth = struct.unpack('i', head)[0] # 解壓報頭,獲取json格式的文件信息字典的長度
        j_head = client.recv(j_dic_lenth) # 收json格式的信息字典
        file_info_dic = json.loads(j_head) # 反序列化json字典,得到文件信息字典
        if not file_info_dic:
          print('文件不存在')
          continue
        file_lenth = file_info_dic.get('lenth')
        file_size = file_info_dic.get('size(MB)')
        file_md5 = file_info_dic.get('md5')
        rec_len = 0
        with open('cpoy_'+file_name, 'wb') as f:
          while rec_len < file_lenth:
            data = client.recv(1024)
            f.write(data)
            rec_len += len(data)
            per=rec_len/file_lenth
            progress(per)
          print()
            # print('下載比例:%6s %%'%)
          if not rec_len:
            print('文件不存在')
          else:
 
            print('文件[%s]下載成功: 大?。?s MB|md5值:[%s]' % (file_name, file_size, file_md5))
 
      except ConnectionResetError:
        print('%s >:服務端已終止' % time.strftime('%Y-%m-%d %H:%M:%S'))
        client.close()
        break
 
  except ConnectionRefusedError:
    print('%s >:無法連接到服務器' % time.strftime('%Y-%m-%d %H:%M:%S'))

文件上傳同理,只是換成客戶端給服務端發(fā)送文件,服務端接收。

接下來我們來學習一下TCP協(xié)議下通信利用socketserver模塊實現(xiàn)多客戶端并發(fā)通信的效果:

服務端:
import socketserver
import time
 
class MyTcpHandler(socketserver.BaseRequestHandler):
  # 到這里表示服務端已監(jiān)聽到一個客戶端的連接請求,將通信交給一個handle方法實現(xiàn),自己再去監(jiān)聽客戶連接請求
  def handle(self):
    # 建立雙向通道,進行通信
    print('%s|客戶端%s已連接' % (time.strftime('%Y-%m-%d %H:%M:%S'), self.client_address))
    while True:
      try:
        data = self.request.recv(1024)
        msg = '我已收到您的請求[%s],感謝您的關(guān)注!' % data.decode('utf-8')
        self.request.send(msg.encode('utf-8'))
      except ConnectionResetError:
        print('%s|客戶端%s已斷開連接' % (time.strftime('%Y-%m-%d %H:%M:%S'), self.client_address))
        break
 
if __name__ == '__main__':
  server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyTcpHandler)  # 綁定服務端IP和PORT,并產(chǎn)生并發(fā)方法對象
  print('等待連接請求中...')
  server.serve_forever() # 服務端一直開啟

客戶端:
from socket import *
import time
server_addr = ('127.0.0.1', 8080)
count = 1
while True:
  if count > 10:
    time.sleep(1)
    print('%s|連接%s超時' % (time.strftime('%Y-%m-%d %H:%M:%S'), server_addr))
    break
  try:
    client = socket(AF_INET, SOCK_STREAM)
    client.connect(('127.0.0.1', 8080))
    count = 1
    print('%s|服務端%s連接成功' % (time.strftime('%Y-%m-%d %H:%M:%S'), server_addr))
    while True:
      try:
        client.send('北鼻'.encode('utf-8'))
        data = client.recv(1024)
        print(data.decode('utf-8'))
        time.sleep(0.5)
      except ConnectionResetError:
        print('%s|服務端%s已中斷' % (time.strftime('%Y-%m-%d %H:%M:%S'), server_addr))
        client.close()
        break
  except ConnectionRefusedError:
    print('無法連接到服務端')
    count += 1

同時再添加客戶端2、客戶端3,將發(fā)送數(shù)據(jù)稍微修改一下,實現(xiàn)多客戶端并發(fā)通信服務端。

通過subprocess模塊,實現(xiàn)遠程shell命令行命令

服務端
import socketserver
import struct
import subprocess
 
 
class MyTcpHandler(socketserver.BaseRequestHandler):
  def handle(self):
    while True:
      print('客戶端<%s,%s>已連接' % self.client_address)
      try:
        cmd = self.request.recv(1024).decode('utf-8')
        res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout = res.stdout.read()
        stderr = res.stderr.read()
        head = struct.pack('i', len(stdout + stderr))
        self.request.send(head)
        self.request.send(stdout)
        self.request.send(stderr)
      except ConnectionResetError:
        print('客戶端<%s,%s>已中斷連接' % self.client_address)
        self.request.close()
        break
 
 
if __name__ == '__main__':
  server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyTcpHandler)
  server.serve_forever()

客戶端
from socket import *
import struct
 
while True:
  try:
    client = socket(AF_INET,SOCK_STREAM)
    client.connect(('127.0.0.1', 8080))
    while True:
      try:
        cmd = input('>>>>>>>:').strip().encode('utf-8')
        client.send(cmd)
        head = client.recv(4)
        size = struct.unpack('i', head)[0]
        cur_size = 0
        result = b''
        while cur_size < size:
          data = client.recv(1024)
          cur_size += len(data)
          result += data
        print(result.decode('gbk'))  # windows系統(tǒng)默認編碼是gbk,解碼肯定也要用gbk
      except ConnectionResetError:
        print('服務端已中斷')
        client.close()
        break
 
  except ConnectionRefusedError:
    print('無法連接服務端')


通過客戶端輸入命令,在服務端執(zhí)行shell命令,通過服務端執(zhí)行subprocess模塊達到遠程shell命令操作,此過程主要需要考慮2個難點,①解決命令產(chǎn)生結(jié)果數(shù)據(jù)的發(fā)送粘包問題,②注意返回結(jié)果的shell命令結(jié)果是gbk編碼,接收后需要用gbk解碼一下。

以上關(guān)于千鋒扣丁學堂Python培訓之基于tcp協(xié)議的通信(數(shù)據(jù)傳輸)實例講解的全部內(nèi)容,希望能給大家一個參考,最后想要了解更多關(guān)于Python和人工智能方面內(nèi)容的小伙伴,請關(guān)注扣丁學堂Python培訓官網(wǎng)、微信等平臺,扣丁學堂IT職業(yè)在線學習教育平臺為您提供權(quán)威的Python開發(fā)環(huán)境搭建視頻,Python培訓后的前景無限,行業(yè)薪資和未來的發(fā)展會越來越好的,扣丁學堂老師精心推出的Python視頻教程定能讓你快速掌握Python從入門到精通開發(fā)實戰(zhàn)技能??鄱W堂Python技術(shù)交流群:279521237。


扣丁學堂微信公眾號                          Python全棧開發(fā)爬蟲人工智能機器學習數(shù)據(jù)分析免費公開課直播間


      【關(guān)注微信公眾號獲取更多學習資料】         【掃碼進入Python全棧開發(fā)免費公開課】



查看更多關(guān)于"Python開發(fā)資訊"的相關(guān)文章>


標簽: Python培訓 Python視頻教程 Python在線視頻 Python學習視頻 Python培訓班

熱門專區(qū)

暫無熱門資訊

課程推薦

微信
微博
15311698296

全國免費咨詢熱線

郵箱:codingke@1000phone.com

官方群:148715490

北京千鋒互聯(lián)科技有限公司版權(quán)所有   北京市海淀區(qū)寶盛北里西區(qū)28號中關(guān)村智誠科創(chuàng)大廈4層
京ICP備2021002079號-2   Copyright ? 2017 - 2022
返回頂部 返回頂部