这次经历,真的一言难尽,想想上一届遗留问题C++下的QT,满满都是泪。需求:客户端(python)将视频流及初步识别结果发送给服务端(C++)
socket通信
通信流程如下:
C++的socket基本函数用法参见:1. 2.
int recv(int socket,char *buf,uint buflen,int flag); //返回值:>0表示收到的字节数,=0表示连接被关闭,SOCKET_ERROR表示出错
int send(int socket,char * buf,char buflen,int flag);// 返回值:>0表示收到的字节数,=0表示连接结束,SOCKET_ERROR表示出错
PS:这里的buf为用户应用层buf变量,而不是缓冲区
python的socket基本函数用法参见:1.
socket缓冲区
每一个socket在被创建之后,系统都会给它分配两个缓冲区,即输入缓冲区和输出缓冲区。
send函数并不是直接将数据传输到网络中,而是负责将数据写入输出缓冲区,数据从输出缓冲区发送到目标主机是由TCP协议完成的。数据写入到输出缓冲区之后,send函数就可以返回了,数据是否发送出去,是否发送成功,何时到达目标主机,都不由它负责了,而是由协议负责。
recv函数也是一样的,它并不是直接从网络中获取数据,而是从输入缓冲区中读取数据。
输入输出缓冲区,系统会为每个socket都单独分配,并且是在socket创建的时候自动生成的。一般来说,默认的输入输出缓冲区大小为8K。套接字关闭的时候,输出缓冲区的数据不会丢失,会由协议发送到另一方;而输入缓冲区的数据则会丢失。
socket收发阻塞
recv函数:阻塞情况:①输入缓冲区没有数据; ②协议正在往输入缓冲区中接收数据
函数先检查输入缓冲区,如果输入缓冲区中有数据,读取出缓冲区中的数据,否则的话,recv函数会被阻塞,等待网络上传来数据。如果读取的数据长度小于输出缓冲区中的数据长度,没法一次性将所有数据读出来,需要多次执行recv函数,才能将数据读取完毕。
返回SOCKET_ERROR(即-1)的情况:
1.copy输入缓冲区数据至应用层buf变量出错。PS:需要注意字节数的控制
send函数:阻塞情况:①输出缓冲区剩余空间不够;②协议正在将输出缓冲区数据发送出去
在数据进行发送的时候,需要先检查输出缓冲区的可用空间大小,如果可用空间大小小于要发送的数据长度,则send会被阻塞,直到缓冲区中的数据被发送到目标主机,有了足够的空间之后,send函数才会将数据写入输出缓冲区。
若TCP协议正在将数据发送到网络上的时候,输出缓冲区会被锁定(生产者消费者问题),不允许写入,send函数会被阻塞,直到数据发送完,输出缓冲区解锁,此时send才能将数据写入到输出缓冲区。
要写入的数据大于输出缓冲区的最大长度的时候,要分多次写入,直到所有数据都被写到缓冲区之后,send函数才会返回。
会返回SOCKET_ERROR的情况:
1.send函数copy数据进输出缓冲区出错
2.每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR
pack打包
利用python下struct.pack必要信息进行数据打包,在C++端定义相同的结构体进行接收
python下struct中支持的格式以及与C++ python中类型的对应关系:
根据需要,数据包中包含的信息有:视频名,帧字节流长度,帧的识别结果信息
fhead = struct.pack(b'20sl64s',
bytes("MyVideo",encoding="utf-8"),
len(stringData),
bytes(boxInfoStr, encoding='utf-8')
)
对应的C++中定义的结构体为:
struct Data { //20sl64s
char filename[20];
long imgStrSize;
char boxInfo[64];
}data;
unpack解包
python下解包
_size = struct.calcsize('20sl64s') #获取总的包尺寸
buf = conn.recv(_size) //从缓冲区中全取出到buf
if buf:
filename, imgStrSize, boxInfo = struct.unpack('20sl64s', buf)
boxinfo = boxInfo.decode().strip('\x00')
fn = filename.decode().strip('\x00')
C++下解包(这相对容易些,用对应结构体接收就好)
memset(&data, 0, sizeof(struct Data)); //清空
recv_len = recv(s_accept, (char*)&data, sizeof(struct Data), 0);
//接收成功直接利用结构体data调用就好 如data.boxinfo
Code
服务端(C++)
#include<iostream>
#include<opencv2\opencv.hpp>
#include<vector>
#include<winsock.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
using namespace cv;
void initialization();
int main() {
//定义长度变量
int send_len = 0;
int recv_len = 0;
int len = 0;
//定义发送缓冲区和接受缓冲区
char send_buf[100];
char recv_buf[100];
//定义图片数据
Mat img_decode;
vector<uchar>img_data;
char recvBuf_1[1];
//定义服务端套接字,接受请求套接字
SOCKET s_server;
SOCKET s_accept;
//服务端地址客户端地址
SOCKADDR_IN server_addr;
SOCKADDR_IN accept_addr;
initialization();
//填充服务端信息
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(8004);
//创建套接字
s_server = socket(AF_INET, SOCK_STREAM, 0);
if (::bind(s_server, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
cout << "套接字绑定失败!" << endl;
WSACleanup();
}
else {
cout << "套接字绑定成功!" << endl;
}
//设置套接字为监听状态
if (listen(s_server, SOMAXCONN) < 0) {
cout << "设置监听状态失败!" << endl;
WSACleanup();
}
else {
cout << "设置监听状态成功!" << endl;
}
cout << "服务端正在监听连接,请稍候...." << endl;
//接受连接请求
len = sizeof(SOCKADDR);
s_accept = accept(s_server, (SOCKADDR *)&accept_addr, &len);
if (s_accept == SOCKET_ERROR) {
cout << "连接失败!" << endl;
WSACleanup();
return 0;
}
cout << "连接建立,准备接受数据" << endl;
//数据包
struct Data { //20sl64s
char filename[20];
long imgStrSize;
char boxInfo[64];
//char recvImg_buf[72518];
}data;
//接收数据
while (1) {
memset(&data, 0, sizeof(struct Data)); //清空
recv_len = recv(s_accept, (char*)&data, sizeof(struct Data), 0);
if (recv_len < 0) {
cout << "接受失败!" << endl;
break;
}
else {
//cout << "客户端信息:" << data.filename << endl;
img_data.resize(data.imgStrSize); //
}
for (int i = 0; i < data.imgStrSize; i++)
{
recv(s_accept, recvBuf_1, 1, 0);
img_data[i] = recvBuf_1[0];
}
cout << data.filename<<"的图片帧接收成功! boxinfo:" <<data.boxInfo<<endl;
img_decode = imdecode(img_data, CV_LOAD_IMAGE_COLOR);
imshow("server", img_decode);
send_len = send(s_accept, "Server has recieved!", 29, 0);
if (send_len < 0) {
cout << "发送失败!" << endl;
break;
}
if (waitKey(30) == 27) break;
}
//关闭套接字
closesocket(s_server);
closesocket(s_accept);
//释放DLL资源
WSACleanup();
return 0;
}
void initialization() {
//初始化套接字库
WORD w_req = MAKEWORD(2, 2);//版本号
WSADATA wsadata;
int err;
err = WSAStartup(w_req, &wsadata);
if (err != 0) {
cout << "初始化套接字库失败!" << endl;
}
else {
cout << "初始化套接字库成功!" << endl;
}
//检测版本号
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) {
cout << "套接字库版本号不符!" << endl;
WSACleanup();
}
else {
cout << "套接字库版本正确!" << endl;
}
//填充服务端地址信息
}
客户端(Python)
import socket
import struct
import cv2
import numpy as np
import time
import sys
def recvall(sock, count):
buf = b'' # buf是一个byte类型
while count:
newbuf = sock.recv(count)
if not newbuf: return None
buf += newbuf
count -= len(newbuf)
return buf
def SendVideo():
# 建立sock连接
# address要连接的服务器IP地址和端口号
address = ('192.168.1.119', 8004)
try:
# 建立socket对象,参数意义见https://blog.csdn.net/rebelqsp/article/details/22109925
# socket.AF_INET:服务器之间网络通信
# socket.SOCK_STREAM:流式socket , for TCP
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 开启连接
sock.connect(address)
except socket.error as msg:
print(msg)
sys.exit(1)
# 建立图像读取对象pr
# capture = cv2.VideoCapture('./1.mp4')
capture = cv2.VideoCapture(0)
capture.set(3, 640)
capture.set(4, 480)
# 读取一帧图像,读取成功:ret=1 frame=读取到的一帧图像;读取失败:ret=0
ret, frame = capture.read()
# 压缩参数,后面cv2.imencode将会用到,对于jpeg来说,15代表图像质量,越高代表图像质量越好为 0-100,默认95
encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 95]
i = 0 #模拟识别结果
while ret:
boxInfoStr = str(i)
i += 1
t1 = time.time()
# 停止0.1S 防止发送过快服务的处理不过来,如果服务端的处理很多,那么应该加大这个值
# time.sleep(0.05)
# cv2.imencode将图片格式转换(编码)成流数据,赋值到内存缓存中;主要用于图像数据格式的压缩,方便网络传输
# '.jpg'表示将图片按照jpg格式编码。
result, imgencode = cv2.imencode('.jpg', frame, encode_param)
# 建立矩阵
data = np.array(imgencode)
# 将numpy矩阵转换成字符形式,以便在网络中传输
stringData = data.tostring()
fhead = struct.pack(b'20sl64s',
bytes("MyVideo",encoding="utf-8"),
len(stringData),
bytes(boxInfoStr, encoding='utf-8')
)
sock.send(fhead)
# 发送数据
sock.send(stringData)
#读取服务器返回值
receive = sock.recv(29)
print(str(receive,encoding="utf-8"))
# 读取下一帧图片
ret, frame = capture.read()
# cv2.imshow('show', frame)
if cv2.waitKey(10) == 27:
break
print('process time = ',time.time() - t1)
sock.close()
补充:服务端(Python)
import os
import socket
import struct
import time
import cv2
import numpy
import copy
def ReceiveVideo():
# IP地址'0.0.0.0'为等待客户端连接
address = ('', 8004)
# 建立socket对象,参数意义见https://blog.csdn.net/rebelqsp/article/details/22109925
# socket.AF_INET:服务器之间网络通信
# socket.SOCK_STREAM:流式socket , for TCP
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 将套接字绑定到地址, 在AF_INET下,以元组(host,port)的形式表示地址.
s.bind(address)
# 开始监听TCP传入连接。参数指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
s.listen(5)
def recvall(sock, count):
buf = b'' # buf是一个byte类型
count = int(count)
while count:
# 接受TCP套接字的数据。数据以字符串形式返回,count指定要接收的最大数据量.
newbuf = sock.recv(count)
if not newbuf: return None
buf += newbuf
count -= len(newbuf)
return buf
# 接受TCP连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。addr是连接客户端的地址。
# 没有连接则等待有连接
conn, addr = s.accept()
print('connect from:' + str(addr))
while 1:
start = time.time() # 用于计算帧率信息
# length = recvall(conn, 16) # 获得图片文件的长度,16代表获取长度
fileinfo_size = struct.calcsize('20sl64s')
buf = conn.recv(fileinfo_size)
if buf:
filename, filesize, boxInfo = struct.unpack('20sl64s', buf)
print(boxInfo.decode().strip('\x00'))
fn = filename.decode().strip('\x00')
new_filename = os.path.join('./', 'new_' + fn)
print(new_filename)
stringData = recvall(conn, filesize) # 根据获得的文件长度,获取图片文件
print(type(stringData))
data = numpy.frombuffer(stringData, numpy.uint8) # 将获取到的字符流数据转换成1维数组
decimg = cv2.imdecode(data, cv2.IMREAD_COLOR) # 将数组解码成图像
cv2.imshow('SERVER', decimg) # 显示图像
conn.send(bytes("Server has recieved!",encoding="utf-8"))
if cv2.waitKey(10) & 0xff == 27:
break
s.close()
cv2.destroyAllWindows()
if __name__ == '__main__':
ReceiveVideo()