天天看點

網絡程式設計:基于TCP的socket網絡傳輸視訊(C++, python)

可以實作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傳圖像