☰
C++ Python跨语言TCP视频传输:粘包半包与协议设计实战
2026/10/8 7:38:38 网站建设 项目流程

简介:一套基于TCP的Socket网络传输视频与图像的跨语言实现方案,面向计算机相关专业在校生、毕业设计开发者及网络编程初学者。项目打通C++与Python两种语言,支持C++到C++、Python到Python、C++到Python的视频或图像传输,代码均已运行成功,可直接作为课程设计或毕设项目初期的演示基础。资源共包含5个文件:两个Python源码、两个C++源码和一份README文档说明,压缩包整体仅5KB,文件结构简洁,适合快速阅读和二次修改。已有336人学习下载。通过四个可执行文件,可系统掌握Socket套接字建立连接、TCP协议下的数据流收发、OpenCV读取与显示图像,以及跨语言通信时的端口与数据格式匹配等关键环节;README中还梳理了Socket概念、TCP与UDP的主要区别、运行环境要求等基础知识。四个源码分别对应服务端与客户端,可灵活组合,作为项目文档或答辩参考。

1. 先讲清楚这个问题:一段视频在 socket 上为什么不能“直接 send”

看到这个标题,先给你一句话结论:视频能不能在 TCP socket 上跑通,关键不在 C++ 还是 Python,也不在视频编码格式,而在你给数据帧定的“打包协议”。TCP socket 网络编程里最典型的一道坎,就是 TCP 是字节流不是消息流——你 send 出去一个视频帧,接收端 recv 到的东西可能多一截也可能少一截,直接按帧处理必然花屏。这个标题的价值在于:它把 C++ 和 Python 放在同一个协议框架里,你只需要写一次协议约定,两门语言各写一份收发逻辑就能互传视频。适合两类人:一是做内网摄像头预览、课程设计、毕设视频传输的同学,二是想把 TCP 粘包、半包、字节序、缓冲这些概念彻底搞明白的开发者。下面这套方案会从协议设计讲到两端代码,再讲到排错,按顺序做就能看到实时画面。

2. 视频帧在 TCP 上怎么才算“传输”:定帧协议与跨语言约定

2.1 TCP 是字节流不是消息流:粘包和半包的来源

TCP 三次握手把连接建起来之后,socket 就变成了一条双向字节管道。这个管道没有“消息”这个概念,它只认字节顺序。你发送端调两次 send,接收端可能一次 recv 就把两段数据全收走,这叫粘包;反过来,发送端调一次 send 发了一个大帧,接收端可能要分三次 recv 才能收完,这叫半包。视频帧的体积通常从几 KB 到几百 KB 不等,所以这两种情况几乎必然出现。

直接调 send 把整帧丢过去,接收端天真地调 recv 等一帧,大概率拿到的是前半帧或前后两帧的拼凑物,imdecode 解出来就是花屏、绿屏甚至直接返回空。这不是算法问题,是 TCP 传输模型决定的。所以要传视频,第一件事不是选语言,而是先定一个应用层协议,把“一帧从哪开始、到哪结束”说得清清楚楚。

我一般把协议分成三层来定:参数包、帧包、结束标记。参数包负责告诉接收端视频的宽、高、帧率;帧包负责承载每一帧 JPEG 数据;结束标记让接收端知道流已经结束。协议先立住,C++ 和 Python 的实现就只是体力活了。

2.2 先定协议再写代码:参数包 + JPEG 帧体 + EOF 标记

这套方案里,视频帧统一用 JPEG 编码。选 JPEG 而不是 PNG,是因为 JPEG 压缩体积小、编码解码速度快,而且 OpenCV 的 imencode/imdecode 在 C++ 和 Python 里都是直接支持的内存操作,不需要写文件再读文件。H.264 虽然压缩率更高,但要处理 IDR 关键帧、解码器状态、音视频同步,复杂度直接翻几倍,不适合作为第一版。

协议布局如下:

包类型字段布局长度
参数包magic(4B) + width(4B) + height(4B) + fps(4B) + quality(4B) + reserved(4B)24 字节
视频帧length(4B) + JPEG 数据(length 字节)4 + length
EOFlength = 04 字节

所有多字节整数统一用网络字节序(大端)。C++ 端用 htonl/ntohl 转换,Python 端用 struct.pack('!I') 的感叹号表示大端,这样两门语言互传才不会把长度读成天书。magic 我习惯用 0xA1B2C3D4,接收端先校验它,能挡住“发错文件”“顺序接错导致串包”这类低级问题。帧长度字段代表的是 payload 的长度,不包括自身那 4 字节;EOF 用 length=0 表达,因为合法帧长度永远大于 0。

为什么不用“魔数+长度”的帧头?因为 JPEG 数据本身可能包含任意字节,魔数有碰撞风险,而纯长度前缀已经足够切分,逻辑最简单。接收端只要先精确读 4 字节拿到长度,再精确读 length 字节拿数据,一帧就完整了。

2.3 C++ 和 Python 各干各的活:语言分工与方案边界

常见做法是把这套代码拆成四个程序:C++ 接收端、C++ 发送端、Python 接收端、Python 发送端。C++ 适合做采集和转发,比如接摄像头、接 RTSP 流,再把帧推给其他进程;Python 适合做快速验证和算法处理,比如给帧加识别框、改分辨率、验证显示效果。实际项目里 C++ 做服务端、Python 做客户端是最高频的组合,反过来也能跑。

这套方案的边界也要说清楚:它是内网视频传输、教学 demo、原型系统的可靠选择,适合 1080p 以下的局域网实时预览。如果要做公网大规模并发或超低延迟直播,应该转向 UDP + 丢包重传、SRT 或 RTSP/RTP 那套体系,TCP 的拥塞控制和重传机制在弱网下会带来延迟累积。明白这个边界,你就知道这套代码值不值得生产化,以及改到什么时候该换架构。

3. C++ 端实现:接收端与发送端两段可跑的 socket 代码

3.1 服务端接收流程:先收参数包,再按长度收帧

先给一份 C++ 接收端代码。它做的事分三步:创建 socket 监听端口、accept 一个客户端、然后循环收帧解码显示。

// server.cpp —— C++ 接收端:收参数包 -> 循环收帧 -> imshow 显示 #include <opencv2/opencv.hpp> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <cstring> #include <cstdint> #include <vector> #include <iostream> using namespace cv; // 从 socket 精确读 n 字节,彻底解决半包问题 bool recv_exact(int fd, void* buf, size_t n) { size_t got = 0; while (got < n) { ssize_t r = recv(fd, (char*)buf + got, n - got, 0); if (r <= 0) return false; got += r; } return true; } int main() { int server_fd = socket(AF_INET, SOCK_STREAM, 0); int opt = 1; // 端口复用,避免上次进程退出后 TIME_WAIT 导致 bind 失败 setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡 addr.sin_port = htons(9000); // 端口与发送端保持一致 if (bind(server_fd, (sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; } listen(server_fd, 5); // backlog=5,演示足够 int client_fd = accept(server_fd, nullptr, nullptr); std::cout << "client connected" << std::endl; // 第一步:先收 24 字节参数包 uint8_t hdr[24]; if (!recv_exact(client_fd, hdr, 24)) return 1; uint32_t magic, w, h, fps, q; memcpy(&magic, hdr + 0, 4); magic = ntohl(magic); memcpy(&w, hdr + 4, 4); w = ntohl(w); memcpy(&h, hdr + 8, 4); h = ntohl(h); memcpy(&fps, hdr + 12, 4); fps = ntohl(fps); memcpy(&q, hdr + 16, 4); q = ntohl(q); if (magic != 0xA1B2C3D4) { std::cerr << "bad magic, wrong peer or protocol mismatch" << std::endl; return 1; } std::cout << "video " << w << "x" << h << " @ " << fps << "fps" << std::endl; // 第二步:循环收帧,每帧都是 4 字节长度 + JPEG 数据 while (true) { uint32_t net_len; if (!recv_exact(client_fd, &net_len, 4)) break; uint32_t len = ntohl(net_len); if (len == 0) { std::cout << "EOF received" << std::endl; break; } if (len > 10 * 1024 * 1024) { std::cerr << "frame too large, protocol broken" << std::endl; break; } std::vector<uchar> jpg(len); if (!recv_exact(client_fd, jpg.data(), len)) break; Mat frame = imdecode(jpg, IMREAD_COLOR); // 解码成 Mat if (!frame.empty()) { imshow("video", frame); if (waitKey(1) == 27) break; // Esc 退出 } } close(client_fd); close(server_fd); return 0; }

这段代码里最有价值的是 recv_exact 函数。它不假设一次 recv 能收到要求的字节数,而是在循环里累积,直到收满 n 字节。长度字段和帧数据都走它,粘包半包问题从根源上消失。len 上限 10MB 是硬保护,防止对端发一个异常的大长度值导致内存暴涨。waitKey(1) 的 1 表示等 1 毫秒,用来刷新窗口,如果用 0 会阻塞到按键才继续,画面就卡住了。

3.2 发送端流程:VideoCapture 读帧 + imencode + send

// client.cpp —— C++ 发送端:读取本地视频 -> JPEG 编码 -> 逐帧发送 #include <opencv2/opencv.hpp> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <cstdint> #include <vector> #include <iostream> using namespace cv; // 与 recv_exact 对称,确保全部字节都发出 bool send_all(int fd, const void* buf, size_t n) { size_t sent = 0; while (sent < n) { ssize_t r = send(fd, (const char*)buf + sent, n - sent, 0); if (r <= 0) return false; sent += r; } return true; } int main() { VideoCapture cap("./demo.mp4"); // 换成你的视频路径 if (!cap.isOpened()) { std::cerr << "open video failed" << std::endl; return 1; } int sock = socket(AF_INET, SOCK_STREAM, 0); sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_port = htons(9000); inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); // 回环测试地址 if (connect(sock, (sockaddr*)&addr, sizeof(addr)) < 0) { perror("connect"); return 1; } // 第一步:组装 24 字节参数包 uint32_t w = cap.get(CAP_PROP_FRAME_WIDTH); uint32_t h = cap.get(CAP_PROP_FRAME_HEIGHT); uint32_t fps = cap.get(CAP_PROP_FPS); struct { uint32_t magic, w, h, fps, q, reserve; } hdr = { htonl(0xA1B2C3D4), htonl(w), htonl(h), htonl(fps), htonl(70), 0 }; send_all(sock, &hdr, sizeof(hdr)); // 第二步:逐帧编码并发送 Mat frame; std::vector<uchar> jpg; while (cap.read(frame)) { imencode(".jpg", frame, jpg, {IMWRITE_JPEG_QUALITY, 70}); uint32_t len = htonl(jpg.size()); if (!send_all(sock, &len, 4)) break; if (!send_all(sock, jpg.data(), jpg.size())) break; } // 第三步:EOF,接收端收到 length=0 就退出 uint32_t eof = 0; send_all(sock, &eof, 4); close(sock); return 0; }

注意 JPEG 质量参数 {IMWRITE_JPEG_QUALITY, 70},70 是一个很稳的折中点:1080p 的一帧大约 30~80KB,一秒钟 30 帧不到 2.4MB,百兆局域网完全没压力。如果你把质量拉到 95,单帧可能到 200KB 以上,带宽翻三倍。另外这个发送端没有按视频帧率做节流,它的语义是“尽快发完”。要模拟实时播放,就在 while 循环末尾加 usleep(1000000 / fps)。用摄像头输入时不需要节流,摄像头天然按采集帧率给帧。

3.3 构建命令、运行顺序与三个必调参数

编译命令取决于你的系统。Ubuntu 上先装 OpenCV 开发库,然后编译链接。我这里只写通用的编译方式,具体安装包名以你系统仓库为准:

# Ubuntu / Debian 系 sudo apt install libopencv-dev g++ server.cpp -o server `pkg-config --cflags --libs opencv` g++ client.cpp -o client `pkg-config --cflags --libs opencv` # 如果系统是 opencv4,pkg-config 包名通常变成 opencv4 g++ server.cpp -o server `pkg-config --cflags --libs opencv4`

运行顺序有个硬规矩:先起接收端,再起发送端。接收端 bind 成功后进入 accept 等待;发送端 connect 才能立刻连上。如果反着来,发送端会拿到 Connection refused,因为端口上还没有监听进程。

这份代码按 Linux/macOS 的 POSIX socket 写的。Windows 移植只需要改三处:开头加 WSAStartup 初始化、把 close 换成 closesocket、错误处理用 WSAGetLastError 替代 errno。代码如下:

#include <winsock2.h> #include <ws2tcpip.h> #pragma comment(lib, "ws2_32.lib") WSADATA ws; WSAStartup(MAKEWORD(2, 2), &ws); // 程序最开头调用,别忘了 // 退出前调用 WSACleanup()

三个必调参数整理成表,照着调就行:

参数位置建议值说明
JPEG 质量imencode 的 IMWRITE_JPEG_QUALITY局域网 70,弱网可降到 50质量越低帧越小,延迟越低
backloglisten 的第二个参数演示 5,生产 128排队的连接数,超过会拒绝
最大帧长接收端长度校验10MB防脏数据导致内存暴涨

源码交付时配的文档说明,我一般就写四部分:协议字节布局、构建命令、运行顺序、这四类故障现象。协议布局最重要,因为 C++ 和 Python 跨语言联调时,所有人都是先对着协议表找问题。

4. Python 端实现:同一份协议几十行跑通实时预览

4.1 Python 发送端代码:struct 大端打包是核心

# client.py —— Python 发送端 import socket import struct import cv2 HOST, PORT = "127.0.0.1", 9000 MAGIC = 0xA1B2C3D4 cap = cv2.VideoCapture("./demo.mp4") s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) # 参数包:!IIIIII 表示 6 个大端无符号整数,顺序与 C++ 端一致 hdr = struct.pack("!IIIIII", MAGIC, int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)), int(cap.get(cv2.CAP_PROP_FPS)), 70, 0) s.sendall(hdr) while True: ok, frame = cap.read() if not ok: break ok, jpg = cv2.imencode(".jpg", frame, [cv2.IMWRITE_JPEG_QUALITY, 70]) data = jpg.tobytes() s.sendall(struct.pack("!I", len(data))) # 帧长度 s.sendall(data) # 帧数据 s.sendall(struct.pack("!I", 0)) # EOF s.close()

Python 端最需要注意的是 sendall 而不是 send。sendall 会循环发送直到全部发完,send 在发送缓冲区满时只发出部分数据就返回了。C++ 端我用 send_all 函数模拟了同样的行为,Python 端直接内置了,省事。struct.pack("!I", ...) 里的感叹号代表大端,和 C++ 的 htonl 完全一致。这一行决定了跨语言兼容性,写习惯了小端,视频长度就会被读成几千万字节,接收端直接判超限退出。

4.2 Python 接收端代码:recv_exact 循环拼包避免半包

# server.py —— Python 接收端:实时预览 import socket import struct import cv2 import numpy as np def recv_exact(sock, n): """精确收 n 字节,不信任单次 recv 的返回值""" data = b"" while len(data) < n: chunk = sock.recv(n - len(data)) if not chunk: raise ConnectionError("connection closed") data += chunk return data srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srv.bind(("0.0.0.0", 9000)) srv.listen(5) conn, _ = srv.accept() # 参数包 magic, w, h, fps, q, _ = struct.unpack("!IIIIII", recv_exact(conn, 24)) if magic != 0xA1B2C3D4: print("bad magic number") conn.close() srv.close() exit(1) print(f"video {w}x{h} @ {fps}fps, jpeg quality {q}") # 循环收帧 while True: size = struct.unpack("!I", recv_exact(conn, 4))[0] if size == 0: # EOF 标记 break jpg = recv_exact(conn, size) frame = cv2.imdecode(np.frombuffer(jpg, dtype=np.uint8), cv2.IMREAD_COLOR) if frame is not None: cv2.imshow("video", frame) if cv2.waitKey(1) == 27: break conn.close() srv.close()

recv_exact 的核心逻辑在 while len(data) < n 这行,每次循环都请求剩余的字节数,而不是固定请求 4096。这样收大帧时不会多读进下一帧的数据,收小帧时也不会卡住等不满。np.frombuffer(jpg, dtype=np.uint8) 把字节串变成 ndarray,再传给 imdecode,这一步是必须的,因为 imdecode 不能直接吃 bytes。

4.3 用 C++ 和 Python 交叉验证协议一致性

四段代码排排列组合才是这套方案的最佳用法。最常见的是 C++ 接收端 + Python 发送端,以及 Python 接收端 + C++ 发送端,两种组合都验证通过,才能说明协议定义是真正的跨语言标准。

# 组合一:C++ 接收端 + Python 发送端 # 终端 1 ./server # 终端 2 python3 client.py # 组合二:Python 接收端 + C++ 发送端 # 终端 1 python3 server.py # 终端 2 ./client

跑交叉验证时尤其注意:一次只能起一个接收端进程。9000 端口被第一个 bind 占住后,第二个进程会报端口被占用。要用另一组测试,先把第一个进程 Ctrl+C 停掉,等一两秒让内核回收端口。

这个环节能暴露 80% 的协议不一致问题:字节序错了、magic 对不上、长度算偏了一字节等等。我第一次做跨语言版本时,C++ 和 Python 单独跑都正常,一交叉就花屏,检查到半夜发现是结构体对齐问题。C++ 端 struct 默认 4 字节对齐,我的参数包恰好每个字段都是 4 字节,没踩坑;如果字段里有 char 或 short 混合类型,内存里就会多出 padding,跟 Python 的 struct.pack 布局就对不上了。所以协议里的多字节字段,尽量统一用 uint32 或 uint16,避免混合宽度。

5. 视频传输的坑与排查清单:端口冲突、粘包、花屏、延迟

5.1 端口冲突与“地址已在使用”:一次踩坑,永久解决

这个报错几乎是每个写 socket 的人都会撞上的墙。Windows 上的提示是“通常每个套接字地址(协议/网络地址/端口)只允许使用一次”,Linux 上就是 bind: Address already in use。现象是服务端进程刚 Ctrl+C 杀掉,立刻重启就 bind 失败。

原因是 TCP 连接关闭后,端口会进入 TIME_WAIT 状态,默认要保持 120 秒左右,防止旧连接的数据包跑到新连接上。解决方法是两板斧:代码里 bind 之前加 setsockopt(SO_REUSEADDR),然后重启前用工具确认端口被谁占用。

# Linux / macOS lsof -i :9000 # 找到 PID kill -9 <PID> # 必要的时候强杀 # Windows netstat -ano | findstr 9000 taskkill /F /PID <PID>

代码里的 SO_REUSEADDR 已经把多数场景挡住了,剩下的就是你某个后台进程还占着端口。记住这个流程,以后再见到这类报错,第一反应不是改端口,而是看谁占着。SO_REUSEADDR 在 Linux 和 Windows 上行为略有差异,但“bind 之前设置它”这个习惯是通用的。

5.2 粘包和半包:画面上出现的“马赛克碎片”从哪来

现象是窗口能打开,但画面时不时变成碎片、花屏、或者一张画面混合了上一帧的下半部分。原因基本就是接收端没有按协议切帧,直接把 recv 的结果当成一整帧去 imdecode 了。比如发送端发了两帧,接收端一次 recv 把两帧的尾部拼到一个 buffer 里,解出来就是废图。

排查思路是先打印长度字段。在接收端把每次收到的 len 都打出来,如果能看到 220000、300、220500 这种奇怪的数字,说明 buffer 错位了——长度字段应该是相对稳定的帧大小,不该出现几百字节的跳变。解决方法是全部读写都收敛到 recv_exact 和 send_all 上,绝对不要直接用一次 recv 的结果去做业务判断。还有一个隐蔽场景:你用 recv(buf, 65536) 收一帧,循环条件写成 while recv > 0,但实际上收进来的可能是两帧甚至更多,下一轮循环长度就全乱了。

5.3 imdecode 返回空:参数包错序和字节序的玄学

这个现象最迷惑人:连接正常、收包正常、长度也对,但 imdecode 就是返回空,或者程序直接在解码时断言崩溃。原因往往在参数包上。你送出去的是 width、height、fps,接收端按 height、width、fps 解析,magic 倒是能对上,但宽高互换会导致后面的帧计算全错。

另一个高频原因是字节序没转。C++ 端忘了 ntohl,直接打印出来的是 0xD4C3B2A1 而不是 0xA1B2C3D4。这种情况我会建议加一行调试代码,把前 24 字节用十六进制打印出来,和协议表逐字节比对。不要靠肉眼看画面猜,协议错位在应用层表现千奇百怪,hex dump 是最诚实的诊断方式。

// 调试用:打印收到的原始字节,与协议表对比 for (int i = 0; i < 24; i++) { printf("%02x ", hdr[i]); if (i % 4 == 3) printf("| "); } printf("\n");

5.4 延迟越来越大:发送方全速推流,接收方消化不及

内网环境带宽够,但延迟却从几百毫秒涨到几秒,这是实时推流最容易遇到的隐性坑。原因是接收端 imshow + waitKey 处理帧的速度赶不上发送端。发送端读文件是全速循环,TCP 发送缓冲区满了就阻塞,发送端被迫排队,队列越长延迟越大,画面像录像回放。

解决手法叫“跳帧策略”。摄像头实时推流时,采集循环里只保留最新帧,跳过来不及编码的旧帧。用 OpenCV 的 grab 可以只取帧不解码,然后对最新帧做 imencode。代码层面就是减少处理量:JPEG 质量降到 60,分辨率降到 720p,帧率从 30 降到 15。TCP 本身是可靠的,但可靠性换来的重传和排队在弱网下会加剧延迟,这也是生产级低延迟方案抛弃 TCP 转用 UDP 的核心原因。做这个 demo 时记住一个原则:宁可丢帧,不可排队。

5.5 优雅退出:EOF 标记和 close 顺序不能乱

发送端直接 Ctrl+C 退出,接收端表现为 recv 返回 0 然后进程退出,窗口立刻消失。这不算错,但会让观察者看不到最后画面,也分不清是“视频放完了”还是“发送端崩了”。所以我在协议里设计了 EOF 标记:发送端正常结束时发一个 length=0 的包,接收端收到后打印 EOF received 再退出。这样调试时能从终端输出判断链路状态。

另外注意 close 顺序。发送端先 close,接收端 recv_exact 会返回 false 并退出——这没问题。但如果接收端先退出,发送端再 send 会触发 SIGPIPE,Linux 上进程直接终止。C++ 端要处理这个信号可以忽略它:

signal(SIGPIPE, SIG_IGN);

6. 验证传输方案可行性的三个手段与一个收尾技巧

方案写完先别急着上真机,按从近到远的顺序验证。第一层是本机回环测试,把客户端 IP 固定成 127.0.0.1,这能排除网卡驱动、交换机、防火墙的干扰,专门验证协议和代码逻辑。回环通了,再把 IP 换成局域网 IP,测试双机传输。这个顺序能帮你把“协议问题”和“网络环境问题”分开排查,省掉一半无效调试时间。

第二层是确认丢帧和乱序。TCP 保证数据顺序,所以乱序不用考虑,但丢帧和跳帧需要肉眼确认。我的常用技巧是发送端在 imencode 之前,用 putText 给每帧右上角写帧号:

cv2.putText(frame, f"{frame_idx:05d}", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 2)

接收端画面上帧号连续说明逐帧全量传输;帧号跳跃说明使用了跳帧策略。这个手段不依赖任何外部分析工具,有眼的都能看。

第三层是端到端延迟估算。在发送端和接收端各打印一次系统时间戳,同一帧号两侧相减,就是粗略延迟。局域网一般应在 30~80ms 以内,超过 200ms 就该去查发送端是否在排队、JPEG 质量是否过高、接收端窗口是否在处理积压帧。

最后分享一个我习惯的做法:程序退出时不要直接 close,而是先发 EOF 再 close,接收端收到后把最后一帧保持显示。这个细节对调试至关重要——画面停住的那一刻,你能确定“最后一帧就是这帧”,而不是进程被信号打断。整个方案做下来,你会发现 TCP socket 传视频的难点不在语言,而在协议设计和对字节流的尊重。C++ 端教会你手动管理缓冲和字节序,Python 端让联调和改版变得飞快,两者配合是入门 socket 网络编程性价比最高的路线。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询