python實現udp傳輸圖片功能
本文實例為大家分享了python實現udp傳輸圖片的具體代碼,供大家參考,具體內容如下
首先要了解UDP的工作模式
對于服務器,首先綁定IP和端口,本機測試的時候可以使用127.0.0.1是本機的專有IP,端口號 大于1024的是自定義的,所以用大于1024的端口號,然后接收客戶端數據,處理,返回對于客戶端,UDP不用建立連接,只管發送不管接收到沒有,所以可以直接對服務器的IP地址和端口號發送信息,然后等待應答。
注意傳輸的數據是二進制流數據,所以要找方法把需要傳輸的數據編碼成二進制碼流,傳過去之后再解碼即可,這里我用到了opencv讀取圖片成numpy的array格式,然后編碼,傳輸,最后接到之后再解碼。
先說一次性傳輸整個圖片,這個思路就是接受的參數設置很大,而且圖片比較小的情況,實現比較簡單
首先是服務器腳本,實現了接收、顯示、應答
udp_sever.py
# -*- coding: utf-8 -*-import socketimport cv2import numpy as nps = 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 socketimport cv2import numpy as nps = 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 npimport cv2img = 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 socketimport cv2import numpy as npimport structs = 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,(’127.0.0.1’, 9999))#循環發送圖片碼流for i in range(len(data)//1024+1): if 1024*(i+1)>len(data): s.sendto(data[1024*i:], (’127.0.0.1’, 9999)) else: s.sendto(data[1024*i:1024*(i+1)], (’127.0.0.1’, 9999))# 接收應答數據:print(s.recv(1024).decode(’utf-8’))#關閉s.close()
然后是服務器接收
udp_sever.py
# -*- coding: utf-8 -*-import socketimport cv2import numpy as npimport structs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 綁定端口:s.bind((’127.0.0.1’, 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, cv2.IMREAD_COLOR) cv2.imshow(’result’,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 socketimport cv2import numpy as npimport structimport threadingimport json#設置IP地址、兩個服務器端口號dest_ip = ’127.0.0.1’img_port = 9999msg_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, np.uint8) img_decode = cv2.imdecode(nparr, cv2.IMREAD_COLOR) cv2.imshow(’result’,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 socketimport cv2import numpy as npimport structs = 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,(’127.0.0.1’, 9999)) for i in range(len(data)//1024+1): if 1024*(i+1)>len(data): s.sendto(data[1024*i:], (’127.0.0.1’, 9999)) else: s.sendto(data[1024*i:1024*(i+1)], (’127.0.0.1’, 9999)) # 接收應答: cv2.waitKey(1) print(s.recv(1024).decode(’utf-8’))s.close()
客戶端2
udp_client_2.py
import socketimport cv2import numpy as npimport structimport jsonimport 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,0,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’))
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持好吧啦網。
相關文章:
