1. 程式人生 > 程式設計 >python實現udp傳輸圖片功能

python實現udp傳輸圖片功能

本文例項為大家分享了python實現udp傳輸圖片的具體程式碼,供大家參考,具體內容如下

首先要了解UDP的工作模式

python實現udp傳輸圖片功能

對於伺服器,首先繫結IP和埠,本機測試的時候可以使用127.0.0.1是本機的專有IP,埠號 大於1024的是自定義的,所以用大於1024的埠號,然後接收客戶端資料,處理,返回
對於客戶端,UDP不用建立連線,只管傳送不管接收到沒有,所以可以直接對伺服器的IP地址和埠號傳送資訊,然後等待應答。

注意傳輸的資料是二進位制流資料,所以要找方法把需要傳輸的資料編碼成二進位制碼流,傳過去之後再解碼即可,這裡我用到了opencv讀取圖片成numpy的array格式,然後編碼,傳輸,最後接到之後再解碼。

先說一次性傳輸整個圖片,這個思路就是接受的引數設定很大,而且圖片比較小的情況,實現比較簡單

首先是伺服器指令碼,實現了接收、顯示、應答

udp_sever.py

# -*- coding: utf-8 -*-

import socket
import cv2
import numpy as np

s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

# 繫結埠:
s.bind(('127.0.0.1',9999))

print('Bind UDP on 9999...')

while True:
 # 接收資料:
 data,addr = s.recvfrom(400000)
 print('Received from %s:%s.' % addr)
 #解碼
 nparr = np.fromstring(data,np.uint8)
 #解碼成圖片numpy
 img_decode = cv2.imdecode(nparr,cv2.IMREAD_COLOR)
 cv2.imshow('result',img_decode)
 cv2.waitKey()
 reply = "get message!!!"
 s.sendto(reply.encode('utf-8'),addr)
 cv2.destroyAllWindows()

客戶端指令碼,實現了傳送圖片,接收應答

udp_client.py

# -*- coding: utf-8 -*-
import socket
import cv2
import numpy as np

s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

img = cv2.imread('/home/xbw/jupyter_notebook/0.jpg')
img_encode = cv2.imencode('.jpg',img)[1]
data_encode = np.array(img_encode)
data = data_encode.tostring()

# 傳送資料:
s.sendto(data,('127.0.0.1',9999))
# 接收資料:
print(s.recv(1024).decode('utf-8'))

s.close()

為了方便理解放一下圖片轉到二進位制再轉回圖片的程式碼

import numpy as np
import cv2
img = cv2.imread('0.jpg')
img_encode = cv2.imencode('.jpg',img)[1]
data_encode = np.array(img_encode)
str_encode = data_encode.tostring()
#print(str_encode)
nparr = np.fromstring(str_encode,np.uint8)
img_decode = cv2.imdecode(nparr,cv2.IMREAD_COLOR)
cv2.imshow('result',img_decode)
cv2.waitKey()
cv2.destroyAllWindows()

分批傳輸圖片

搞了好久終於知道怎麼分批傳輸圖片了,首先要知道需要傳的圖片需要多長的記憶體,不然不知道什麼時候停止接收,這樣就要考慮加一個檔案頭,告訴伺服器要接受多長的碼流。

實現思路是,首先客戶端要先發送一個檔案頭,包含了碼流的長度,用一個long int型的數,先用struct.pack打包,發過去,然後迴圈傳送圖片的碼流即可

接著伺服器先接到檔案頭,確認圖片碼流的長度,然後迴圈接收確定長度的碼流,最後再解碼成圖片即可

實現程式碼如下:

首先是客戶端指令碼

udp_client.py

# -*- coding: utf-8 -*-
import socket
import cv2
import numpy as np
import struct

s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#讀取圖片,編碼成二進位制 bytes格式
img = cv2.imread('/home/xbw/jupyter_notebook/0.jpg')
img_encode = cv2.imencode('.jpg',img)[1]
data_encode = np.array(img_encode)
data = data_encode.tostring()
#定義檔案頭,打包成結構體
fhead = struct.pack('l',len(data))
# 傳送檔案頭:
s.sendto(fhead,9999))
#迴圈傳送圖片碼流
for i in range(len(data)//1024+1):
 if 1024*(i+1)>len(data):
 s.sendto(data[1024*i:],9999))
 else:
 s.sendto(data[1024*i:1024*(i+1)],9999))
# 接收應答資料:
print(s.recv(1024).decode('utf-8'))
#關閉
s.close()

然後是伺服器接收

udp_sever.py

# -*- coding: utf-8 -*-

import socket
import cv2
import numpy as np
import struct

s = socket.socket(socket.AF_INET,9999))

print('Bind UDP on 9999...')

while True:
 # 接收檔案頭,檔案頭的長度由calcsize函式確定,注意這裡recvfrom是接收UDP訊息,recv是接收TCP訊息
 fhead_size = struct.calcsize('l')
 buf,addr = s.recvfrom(fhead_size)
 if buf:
 #這裡結果是一個元組,所以把值取出來
 data_size = struct.unpack('l',buf)[0]
 #接收圖片碼流長度的碼流
 recvd_size = 0
 data_total = b''
 while not recvd_size == data_size:
 if data_size -recvd_size >1024:
  data,addr = s.recvfrom(1024)
  recvd_size += len(data)
 else:
  data,addr = s.recvfrom(1024)
  recvd_size = data_size 
 data_total += data
# data,addr = s.recvfrom(400000)
 print('Received')
# reply = 'Hello,%s!' % data.decode('utf-8')
# s.sendto(reply.encode('utf-8'),addr)
 #把接到的碼流解碼成numpy陣列,顯示影象
 nparr = np.fromstring(data_total,np.uint8)
 img_decode = cv2.imdecode(nparr,img_decode)
 cv2.waitKey()
 #應答
 reply = "get message!!!"
 s.sendto(reply.encode('utf-8'),addr)
 cv2.destroyAllWindows()

-------------------分割線----------------

上面是基本的實現,經過一番學習我終於掌握了UDP傳輸的精髓

首先是確定客戶端和伺服器的執行機制

客戶端:先定義一個socket物件,不用繫結,然後指定IP地址和埠傳送訊息,然後如果用了recvfrom就會一直阻塞等待應答(這個很有用,作用就是保證對方確實收到,再發新的訊息,不用在考慮傳送頻率的問題了),前面加一個while True就可以迴圈傳送了,如果涉及到很大的訊息,可以拆分發送,技巧是先發送一個檔案頭高速伺服器要發的內容有多大(檔案頭這裡建議使用stuct庫,看前面例程),然後隨後傳送檔案內容,保證要迴圈傳送,因為每次傳送,對面就當發了一次,假如發了2048位元組的內容,對面設定的每次收1024,那麼剩下的1024就被丟掉了,而不是等待下次繼續接收。還有就是傳送的是二進位制的碼流,目前我用到的轉換成碼流的方法有:圖片用opencv,先imencode 轉成二進位制,然後再轉成numpy,然後再tostring。檔案頭這種,需要確切知道佔多大記憶體,使得伺服器好接收的,用了stuct庫,裡面的pack,unpack,calcsize三個函式非常好用,傳送的時候把資料pack一下就能傳送了。列表、字典等等,作為檔案內容,用到了json,有點萬能,先json.dumps轉換成json型別,然後再encode編碼成二進位制即可拿去傳送了。

伺服器:先定義一個socket物件,繫結IP地址和埠,讓客戶端可以找到,然後等待接收訊息,收到訊息之後處理訊息,應答,配合客戶端的recvfrom,保證接收頻率一致,伺服器為了保證始終接收訊息,一定會有一個while True,接收到的訊息是二進位制碼流,因此要進行解碼。針對上面講的編碼方式解碼,其實就是編碼方式的反向操作:圖片,用opencv解碼,先是np.fromstring,然後再cv2.imdecode(data,cv2.IMREAD_COLOR)。對於接收檔案頭,這裡有點技巧,用struct.calcsize確定檔案頭長度,然後只接收這個長度的碼流,再unpack出來即可,這裡unpack是個元組。對於json,解碼就是先decode,再json.loads即可,是上面編碼的反向操作。

然後再高階一點的操作,同一個指令碼多程序工作,這就要用到了threading.Thread建立多個程序,思路就是新建多個伺服器,然後分配給不同的程序,他們的IP地址可以一樣,埠號不一樣就行,然後就可以在同一個腳本里並行工作了,這裡不同於TCP,因為UDP不需要建立連線

然後附上我實現的原始碼,伺服器腳本里有兩個程序,一個接收客戶端1的圖片,另一個接收客戶端2的列表

伺服器

udp_server.py

# -*- coding: utf-8 -*-
import socket
import cv2
import numpy as np
import struct
import threading
import json
#設定IP地址、兩個伺服器埠號
dest_ip = '127.0.0.1'
img_port = 9999
msg_port = 6666

#伺服器1的處理、應答函式,接收圖片、顯示、應答
def receive_img(rec_img):
 while True:
 # 接收資料:
 fhead_size = struct.calcsize('l')
 buf,addr = rec_img.recvfrom(fhead_size)
 if buf:
  data_size = struct.unpack('l',buf)[0]
  print(data_size)
 recvd_size = 0
 data_total = b''
 while not recvd_size == data_size:
  if data_size -recvd_size >1024:
  data,addr = rec_img.recvfrom(1024)
  recvd_size += len(data)
  else:
  data,addr = rec_img.recvfrom(1024)
  recvd_size = data_size 
  data_total += data
# data,addr = rec_img.recvfrom(400000)
 print('Received')
# reply = 'Hello,%s!' % data.decode('utf-8')
# rec_img.sendto(reply.encode('utf-8'),addr)

 nparr = np.fromstring(data_total,img_decode)
 cv2.waitKey(100)
 reply = "get message!!!"
 rec_img.sendto(reply.encode('utf-8'),addr)
# cv2.destroyAllWindows()

#伺服器2函式,接收訊息、輸出、應答
def receive_msg(rec_msg):
 while True:
 msg_data,msg_addr = rec_msg.recvfrom(1024)
 msg_str = msg_data.decode('utf-8')
 msg = json.loads(msg_str)
 print(msg)
 reply = 'get the msg'
 rec_msg.sendto(reply.encode('utf-8'),msg_addr)
 rec_msg.close()

#主函式 建立伺服器、繫結埠、建立執行兩個程序、呼叫上面兩個函式
def main():
 #建立套接字
 rec_img = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
 rec_msg = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
 #繫結本地地址埠
 rec_img.bind((dest_ip,img_port))
 rec_msg.bind((dest_ip,msg_port))
 #建立程序
 t_recimg = threading.Thread(target=receive_img,args=(rec_img,))
 t_recmsg = threading.Thread(target=receive_msg,args=(rec_msg,))
 #開始程序
 t_recimg.start()
 t_recmsg.start()
 print('程式正常執行!!!')
 


if __name__ == '__main__':
 main()

客戶端1

udp_client_1.py

# -*- coding: utf-8 -*-
import socket
import cv2
import numpy as np
import struct

s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
cap = cv2.VideoCapture(0)
#cap.set(3,320)
#cap.set(4,240)
while True:
 
 if cap.isOpened():
 flag,img = cap.read()
# img = cv2.imread('/home/xbw/jupyter_notebook/0.jpg')
 img_encode = cv2.imencode('.jpg',img)[1]
 data_encode = np.array(img_encode)
 data = data_encode.tostring()
 #定義檔案頭
 fhead = struct.pack('l',len(data))
 # 傳送檔案頭、資料:
 s.sendto(fhead,9999))
 for i in range(len(data)//1024+1):
  if 1024*(i+1)>len(data):
  s.sendto(data[1024*i:],9999))
  else:
  s.sendto(data[1024*i:1024*(i+1)],9999))
 # 接收應答:
 cv2.waitKey(1)
 print(s.recv(1024).decode('utf-8'))

s.close()

客戶端2

udp_client_2.py

import socket
import cv2
import numpy as np
import struct
import json
import time
#定義套接字
send_msg = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#設定目標IP地址、埠號
target_ip = '127.0.0.1'
target_port = 6666
#傳送資料,等待應答
while True:
 data = [0,1]
 data_str = json.dumps(data)
 send_msg.sendto(data_str.encode(),(target_ip,target_port))
 time.sleep(0.01)
 print(send_msg.recv(1024).decode('utf-8'))

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。