日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > python >内容正文

python

python中基于tcp协议的通信(数据传输)

發(fā)布時(shí)間:2024/9/30 python 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python中基于tcp协议的通信(数据传输) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

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

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

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

簡(jiǎn)單的tcp協(xié)議通信模板:(需要一個(gè)服務(wù)端和一個(gè)客戶端)

-------------------------------------------------------------------- 注:如果你對(duì)python感興趣,我這有個(gè)學(xué)習(xí)Python基地,里面有很多學(xué)習(xí)資料,感興趣的+Q群:895817687 --------------------------------------------------------------------服務(wù)端:from socket import * # 確定服務(wù)端傳輸協(xié)議↓↓↓↓↓↓↓ server = socket(AF_INET, SOCK_STREAM) # 這里的SOCK_STREAM代表的就是流式協(xié)議TCP,如果是SOCK_DGRAM就代表UDP協(xié)議 # 固定服務(wù)端IP和PORT,讓客戶端能夠通過(guò)IP和端口訪問(wèn)服務(wù)端↓↓↓↓↓↓↓ server.bind(('127.0.0.1', 8080)) # ('127.0.0.1', 8080)這里必須用元組形式傳入IP和PORT,本地訪問(wèn)本地IP默認(rèn)為'127.0.0.1' # 設(shè)置半連接池?cái)?shù)量(一般為5) server.listen(5) # 半連接池:客戶端連接請(qǐng)求個(gè)數(shù)的容器,當(dāng)前已連接的客戶端信息收發(fā)未完成前,會(huì)有最大5個(gè)客戶端連接請(qǐng)求進(jìn)入排隊(duì)狀態(tài),# 等待上一個(gè)通信完畢后,就可以連接進(jìn)入開(kāi)始通信。 # 雙向通道建立成功,可以進(jìn)行下一步數(shù)據(jù)的通信了↓↓↓↓↓↓↓ conn, client_addr = server.accept() # 進(jìn)行一次信息的收與發(fā) data = conn.recv(1024) # 每次最大接收1024字節(jié),收到的數(shù)據(jù)為二進(jìn)制Bytes類型conn.send(data.upper()) # 將收到的數(shù)據(jù)進(jìn)行處理,返回新的數(shù)據(jù),反饋給客戶端(給客戶端發(fā)數(shù)據(jù)),發(fā)的數(shù)據(jù)類型也必須是Bytes類型# 一輪信息收發(fā)完畢,關(guān)閉已經(jīng)建立的雙向通道 conn.close()客戶端: from socket import * # 確定客戶端傳輸協(xié)議↓↓↓↓↓↓↓(服務(wù)端和客戶端服務(wù)協(xié)議一樣才能進(jìn)行有效的通信) client = socket(AF_INET, SOCK_STREAM) # 這里的SOCK_STREAM代表的就是流式協(xié)議TCP,如果是SOCK_DGRAM就代表UDP協(xié)議 # 開(kāi)始連接服務(wù)端IP和PORT,建立雙向鏈接 client.connect(('127.0.0.1', 8080)) # 通過(guò)服務(wù)端IP和PORT進(jìn)行連接# 走到這一步就已經(jīng)建立連接完畢,接下來(lái)開(kāi)始數(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)閉客戶端連接啟動(dòng)服務(wù)端(服務(wù)端開(kāi)始監(jiān)聽(tīng)客戶端的連接請(qǐng)求) 啟動(dòng)客戶端(客戶端給服務(wù)端發(fā)送連接請(qǐng)求) 建立雙向鏈接完成 客戶端給服務(wù)端發(fā)送信息 hello,server 服務(wù)端收到hello,server,將其轉(zhuǎn)換成大寫,返回給客戶端(此時(shí)服務(wù)端一輪通信完畢) 客戶端收到服務(wù)端的反饋信息,打印出HELLO,SERVER(此時(shí)客戶端一輪通信完畢)

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

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

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

這個(gè)問(wèn)題的更形象過(guò)程可以見(jiàn)下圖:

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

粘包問(wèn)題的解決分析:

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

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

解決方法:先發(fā)個(gè)包含待發(fā)送文件大小長(zhǎng)度的報(bào)頭文件>>>>再發(fā)送原始文件

引入模塊struct

具體看代碼:

服務(wù)端: import socket import structserver = 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('已連接到服務(wù)端')while True:try:msg = 'abcdefghijklmnopqrstuvwxyz1234567890'.encode('utf-8')head = struct.pack('i', len(msg))client.send(head)client.send(msg)except ConnectionResetError:print('服務(wù)端已中斷連接')client.close()breakexcept ConnectionRefusedError:print('無(wú)法連接到服務(wù)器')

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

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

下載服務(wù)端:import socket import time import struct import json# 計(jì)算當(dāng)前文件夾下文件的md5值、大小 import os, hashlibdef 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計(jì)算時(shí)文件數(shù)據(jù)是放在內(nèi)存中的,當(dāng)我們計(jì)算一個(gè)大文件時(shí),可以用update方法進(jìn)行分步計(jì)算,# 每次添加部分文件數(shù)據(jù)進(jìn)行計(jì)算,減少內(nèi)存占用。with open(file_dir, 'rb') as f:le = 0d5 = hashlib.md5()for line in f:le += len(line)d5.update(line)file_info['lenth'] = le # 將文件長(zhǎng)度加入報(bào)頭字典file_md5 = d5.hexdigest()file_info['md5'] = file_md5 # 將文件md5加入報(bào)頭字典file_size = os.path.getsize(file_dir) / float(1024 * 1024)file_info['size(MB)'] = round(file_size, 2) # 將文件大小加入報(bào)頭字典return file_infoelse:return file_infoserver = 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:continuewith 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)已斷開(kāi)' % (time.strftime('%Y-%m-%d %H:%M:%S'), client_addr))conn.close()break 下載客戶端:import socket import time import struct import json# 進(jìn)度條顯示 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 >:已連接到服務(wù)端' % time.strftime('%Y-%m-%d %H:%M:%S'))while True:try:file_name = input('請(qǐng)輸入下載文件名稱:')client.send(file_name.encode('utf-8'))head = client.recv(4) # 收?qǐng)?bào)頭j_dic_lenth = struct.unpack('i', head)[0] # 解壓報(bào)頭,獲取json格式的文件信息字典的長(zhǎng)度j_head = client.recv(j_dic_lenth) # 收json格式的信息字典file_info_dic = json.loads(j_head) # 反序列化json字典,得到文件信息字典if not file_info_dic:print('文件不存在')continuefile_lenth = file_info_dic.get('lenth')file_size = file_info_dic.get('size(MB)')file_md5 = file_info_dic.get('md5')rec_len = 0with 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_lenthprogress(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 >:服務(wù)端已終止' % time.strftime('%Y-%m-%d %H:%M:%S'))client.close()breakexcept ConnectionRefusedError:print('%s >:無(wú)法連接到服務(wù)器' % time.strftime('%Y-%m-%d %H:%M:%S'))

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

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

服務(wù)端: import socketserver import timeclass MyTcpHandler(socketserver.BaseRequestHandler):# 到這里表示服務(wù)端已監(jiān)聽(tīng)到一個(gè)客戶端的連接請(qǐng)求,將通信交給一個(gè)handle方法實(shí)現(xiàn),自己再去監(jiān)聽(tīng)客戶連接請(qǐng)求def handle(self):# 建立雙向通道,進(jìn)行通信print('%s|客戶端%s已連接' % (time.strftime('%Y-%m-%d %H:%M:%S'), self.client_address))while True:try:data = self.request.recv(1024)msg = '我已收到您的請(qǐng)求[%s],感謝您的關(guān)注!' % data.decode('utf-8')self.request.send(msg.encode('utf-8'))except ConnectionResetError:print('%s|客戶端%s已斷開(kāi)連接' % (time.strftime('%Y-%m-%d %H:%M:%S'), self.client_address))breakif __name__ == '__main__':server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyTcpHandler) # 綁定服務(wù)端IP和PORT,并產(chǎn)生并發(fā)方法對(duì)象print('等待連接請(qǐng)求中...')server.serve_forever() # 服務(wù)端一直開(kāi)啟 客戶端: 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超時(shí)' % (time.strftime('%Y-%m-%d %H:%M:%S'), server_addr))breaktry:client = socket(AF_INET, SOCK_STREAM)client.connect(('127.0.0.1', 8080))count = 1print('%s|服務(wù)端%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|服務(wù)端%s已中斷' % (time.strftime('%Y-%m-%d %H:%M:%S'), server_addr))client.close()breakexcept ConnectionRefusedError:print('無(wú)法連接到服務(wù)端')count += 1

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

通過(guò)subprocess模塊,實(shí)現(xiàn)遠(yuǎn)程shell命令行命令

服務(wù)端 import socketserver import struct import subprocessclass 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()breakif __name__ == '__main__':server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyTcpHandler)server.serve_forever() 客戶端 from socket import * import structwhile 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 = 0result = b''while cur_size < size:data = client.recv(1024)cur_size += len(data)result += dataprint(result.decode('gbk')) # windows系統(tǒng)默認(rèn)編碼是gbk,解碼肯定也要用gbkexcept ConnectionResetError:print('服務(wù)端已中斷')client.close()breakexcept ConnectionRefusedError:print('無(wú)法連接服務(wù)端')

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

總結(jié)

以上是生活随笔為你收集整理的python中基于tcp协议的通信(数据传输)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。