可以實作C++ to C++、Python to Python、C++ to Python的視訊或圖像傳輸。
一. 概述
Socket的英文原義是“孔”或“插座”。作為BSD UNIX的程序通信機制,取後一種意思。通常也稱作”套接字”,用于描述IP位址和端口,是一個通信鍊的句柄,可以用來實作不同虛拟機或不同計算機之間的通信。在Internet上的主機一般運作了多個服務軟體,同時提供幾種服務。每種服務都打開一個Socket,并綁定到一個端口上,不同的端口對應于不同的服務。Socket正如其英文原意那樣,像一個多孔插座。一台主機猶如布滿各種插座的房間,每個插座有一個編号,有的插座提供220伏交流電, 有的提供110伏交流電,有的則提供有線電視節目。 客戶軟體将插頭插到不同編号的插座,就可以得到不同的服務。網絡上的兩個程式通過一個雙向的通信連接配接實作資料的交換,這個連接配接的一端稱為一個socket。
而socket與socket之間的連接配接以及資料傳輸需要一種規則,也就是我們通常所說的網絡傳輸協定,最常用的有TCP和UDP,這兩種協定的差別如下:
1.基于連接配接與無連接配接;
2.對系統資源的要求(TCP較多,UDP少);
3.UDP程式結構較簡單;
4.流模式與資料報模式 ;
5.TCP保證資料正确性,UDP可能丢包,TCP保證資料順序,UDP不保證。
接下來将以圖檔傳輸為例,用Python和C++實作服務端和用戶端。這裡不用語言得到的端口之間也可以互相連接配接。
二. 運作要求
(1)OpenCV
(2)Python 2.7
(3)C++的源檔案要求windows環境
三. 代碼
(1)client.py
#!/usr/bin/python
#-*-coding:utf-8 -*-
import socket
import cv2
import numpy
# socket.AF_INET用于伺服器與伺服器之間的網絡通信
# socket.SOCK_STREAM代表基于TCP的流式socket通信
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 連接配接服務端
address_server = ('10.106.20.111', )
sock.connect(address_server)
# 從攝像頭采集圖像
capture = cv2.VideoCapture()
ret, frame = capture.read()
encode_param=[int(cv2.IMWRITE_JPEG_QUALITY),] #設定編碼參數
while ret:
# 首先對圖檔進行編碼,因為socket不支援直接發送圖檔
result, imgencode = cv2.imencode('.jpg', frame)
data = numpy.array(imgencode)
stringData = data.tostring()
# 首先發送圖檔編碼後的長度
sock.send(str(len(stringData)).ljust())
# 然後一個位元組一個位元組發送編碼的内容
# 如果是python對python那麼可以一次性發送,如果發給c++的server則必須分開發因為編碼裡面有字元串結束标志位,c++會截斷
for i in range (,len(stringData)):
sock.send(stringData[i])
ret, frame = capture.read()
#cv2.imshow('CLIENT',frame)
# if cv2.waitKey(10) == 27:
# break
# 接收server發送的傳回資訊
data_r = sock.recv()
print (data_r)
sock.close()
cv2.destroyAllWindows()
(2)server.py
#!/usr/bin/python
#-*-coding:utf-8 -*-
import socket
import cv2
import numpy
# 接受圖檔大小的資訊
def recv_size(sock, count):
buf = b''
while count:
newbuf = sock.recv(count)
if not newbuf: return None
buf += newbuf
count -= len(newbuf)
return buf
# 接收圖檔
def recv_all(sock, count):
buf = ''
while count:
# 這裡每次隻接收一個位元組的原因是增強python與C++的相容性
# python可以發送任意的字元串,包括亂碼,但C++發送的字元中不能包含'\0',也就是字元串結束标志位
newbuf = sock.recv()
if not newbuf: return None
buf += newbuf
count -= len(newbuf)
return buf
# socket.AF_INET用于伺服器與伺服器之間的網絡通信
# socket.SOCK_STREAM代表基于TCP的流式socket通信
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 設定位址與端口,如果是接收任意ip對本伺服器的連接配接,位址欄可空,但端口必須設定
address = ('', )
s.bind(address) # 将Socket(套接字)綁定到位址
s.listen(True) # 開始監聽TCP傳入連接配接
print ('Waiting for images...')
# 接受TCP連結并傳回(conn, addr),其中conn是新的套接字對象,可以用來接收和發送資料,addr是連結用戶端的位址。
conn, addr = s.accept()
while :
length = recv_size(conn,) #首先接收來自用戶端發送的大小資訊
if isinstance (length,str): #若成功接收到大小資訊,進一步再接收整張圖檔
stringData = recv_all(conn, int(length))
data = numpy.fromstring(stringData, dtype='uint8')
decimg=cv2.imdecode(data,) #解碼處理,傳回mat圖檔
cv2.imshow('SERVER',decimg)
if cv2.waitKey() == :
break
print('Image recieved successfully!')
conn.send("Server has recieved messages!")
if cv2.waitKey() == :
break
s.close()
cv2.destroyAllWindows()
(3)client.cpp
#include <stdio.h>
#include <string>
#include <iostream>
#include <Winsock2.h>
#include <opencv2/opencv.hpp>
#include <vector>
#pragma comment(lib,"ws2_32.lib")
using namespace cv;
using namespace std;
void main()
{
WSADATA wsaData;
SOCKET sockClient;//用戶端Socket
SOCKADDR_IN addrServer;//服務端位址
WSAStartup(MAKEWORD(, ), &wsaData);
//建立用戶端socket
sockClient = socket(AF_INET, SOCK_STREAM, );
//定義要連接配接的服務端位址
addrServer.sin_addr.S_un.S_addr = inet_addr("10.106.20.111");//目标IP(10.106.20.74是回送位址)
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons();//連接配接端口
//連接配接到服務端
connect(sockClient, (SOCKADDR*)&addrServer, sizeof(SOCKADDR));
Mat image;
VideoCapture capture();
vector<uchar> data_encode;
while ()
{
if (!capture.read(image))
break;
imencode(".jpg", image, data_encode);
int len_encode = data_encode.size();
string len = to_string(len_encode);
int length = len.length();
for (int i = ; i < - length; i++)
{
len = len + " ";
}
//發送資料
send(sockClient, len.c_str(), strlen(len.c_str()), );
char send_char[];
for (int i = ; i < len_encode; i++)
{
send_char[] = data_encode[i];
send(sockClient, send_char, , );
}
//接收傳回資訊
char recvBuf[];
if(recv(sockClient, recvBuf, , ))
printf("%s\n", recvBuf);
}
closesocket(sockClient);
WSACleanup();
}
(4)server.cpp
#include <stdio.h>
#include <Winsock2.h>
#include <opencv2/opencv.hpp>
#include <vector>
#pragma comment(lib,"ws2_32.lib")
using namespace cv;
using namespace std;
void main()
{
WSADATA wsaData;
SOCKET sockServer;
SOCKADDR_IN addrServer;
SOCKET conn;
SOCKADDR_IN addr;
WSAStartup(MAKEWORD(, ), &wsaData);
//建立Socket
sockServer = socket(AF_INET, SOCK_STREAM, );
//準備通信位址
addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons();
//綁定
bind(sockServer, (SOCKADDR*)&addrServer, sizeof(SOCKADDR));
//監聽
listen(sockServer, );
printf("Waiting for images...\n");
int len = sizeof(SOCKADDR);
//監聽連接配接
conn = accept(sockServer, (SOCKADDR*)&addr, &len);
char recvBuf[];
char recvBuf_1[];
Mat img_decode;
vector<uchar> data;
while ()
{
if (recv(conn, recvBuf, , ))
{
for (int i = ; i < ; i++)
{
if (recvBuf[i]<'0' || recvBuf[i]>'9') recvBuf[i] = ' ';
}
data.resize(atoi(recvBuf));
for (int i = ; i < atoi(recvBuf); i++)
{
recv(conn, recvBuf_1, , );
data[i] = recvBuf_1[];
}
printf("Image recieved successfully!\n");
send(conn, "Server has recieved messages!", , );
img_decode = imdecode(data, CV_LOAD_IMAGE_COLOR);
imshow("server", img_decode);
if (waitKey() == ) break;
}
}
closesocket(conn);
WSACleanup();
}
四. 參考資料
C++ Socket程式設計步驟
Python Socket 程式設計詳細介紹
樹莓派用Python寫幾個簡單程式5:用socket傳圖像