前言
本节将介绍进程间通信的第三种方法------Tcp。对于IPC通信而言,它既可以实现本机的进程间通信,也可以实现跨机器的通信。
然而相比于IPC,Tcp本质上是为网络通信设计的,它是网络通信中绕不开的经典话题,也是实际项目开发中经常使用的通信技术,主要应用在局域网内的数据传输。
如果深入网络通信相关的内容,我认为不大属于进程间通信的范畴,比方说Tcp的粘包问题。因此,本节只简单介绍Tcp在Qt中的使用,并演示如何实现进程间的连接和通信。
一、TCP基础知识
1.TCP 是什么?
Transmission Control Protocol(传输控制协议)
面向连接、可靠、有序、基于字节流的传输层协议。
保证数据不丢失、不重复、按序到达。
2. TCP 用于 IPC 的前提
虽然 TCP 主要用于网络通信,但本机进程可通过 127.0.0.1(IPv4)或 ::1(IPv6)进行通信。
操作系统会将发往 127.0.0.1 的数据包绕过物理网卡,直接在内核协议栈中回环处理,称为 loopback(回环)。
因此,本地 TCP 通信是合法且有效的 IPC 方式,尽管不是最高效的。
3.Qt 对 TCP 的封装:QTcpSocket 与 QTcpServer
QTcpServer 监听 TCP 连接请求的服务端,socket() + bind() + listen() + accept()
QTcpSocket 客户端连接 或 服务端 accepted 的连接,connect() 或 accept() 返回的 socket
核心特性:
继承自 QIODevice,支持 read() / write() / readyRead() 等流式接口。
支持异步事件驱动(信号槽)和同步阻塞(waitForConnected() 等)两种模式。
自动处理字节序、缓冲、重连等细节(但需注意粘包问题)。
4.TCP 作为 IPC 方式的优缺点
✅ 优点
跨平台一致 Windows/Linux/macOS 行为完全一致
开发简单 API 成熟,调试方便(可用 Wireshark、netstat)
天然支持多连接 一个 QTcpServer 可同时服务多个客户端进程
防火墙友好 仅限 localhost,通常无需额外配置
语言无关 任何支持 TCP 的语言(Python/C#/Rust)都能与 Qt 进程通信
❌ 缺点
性能开销大 数据需经过完整 TCP/IP 协议栈(校验和、缓冲、ACK 等)
延迟较高 相比 QLocalSocket 或共享内存,延迟高 2~5 倍
端口管理麻烦 需避免端口冲突(如多个实例运行)
粘包问题 TCP 是字节流,无消息边界,需自行设计协议(如长度前缀)
资源占用 每个连接消耗文件描述符/句柄,大量连接时需优化
5.粘包问题
这个问题没有自行解决过,在面试中曾经回答不上来。其实想要解决粘包问题,主要是通过协议头和数据缓冲来实现的。协议头包含了数据的长度信息,而数据缓冲提供了完整包数据的合并和读取,以此来解决单次接受读取到的信息并不完整的问题。
实际工作中其实曾经遇到过,程序中通过tcp来接受摄像头数据,一帧图像的大小往往比较大,不像简短的文本信息,因此单次接收可能并不完整。因此协议层中做了不少设计,比如协议头、数据帧大小等等。
6.和LocalSocket的关系
上一节也说过了,LocalSocket不属于Tcp的范畴,但概念上同样属于Socket通信的一种。Qt官方对LocalSocket封装时,故意将接口类似TcpSocket的样子,以降低开发者学习成本。所以,QTcpSocket的使用和上一节会很像。
二、代码实例
同样的,我们为QTcpSocket设计单独的测试窗口类:
cpp
#ifndef TCPWINDOW_H
#define TCPWINDOW_H
#include <QWidget>
#include <QTextEdit>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QSpinBox>
#include <QTcpServer>
#include <QTcpSocket>
#include <QProcess>
#include <QDateTime>
class TCPWindow : public QWidget
{
Q_OBJECT
public:
explicit TCPWindow(const QString &role, QWidget *parent = nullptr);
private slots:
void onSendMessage();
void onPortChanged(int port);
void onAcceptConnection();
void onReadyRead();
void onDisconnected();
void onStartServerClicked();
void onConnectClicked();
private:
void appendLog(const QString &msg);
void setupServer();
void setupClient();
QString m_role;
int m_port;
QTextEdit *m_logView;
QLineEdit *m_inputEdit;
QPushButton *m_sendButton;
QPushButton *m_startServerButton;
QPushButton *m_connectButton;
QSpinBox *m_portSpinBox;
QTcpServer *m_server;
QTcpSocket *m_socket;
QProcess *m_workerProcess;
};
#endif // TCPWINDOW_H
cpp
#include "tcpwindow.h"
#include <QLabel>
#include <QApplication>
#include <QTimer>
TCPWindow::TCPWindow(const QString &role, QWidget *parent)
: QWidget(parent)
, m_role(role)
, m_port(9876)
, m_server(nullptr)
, m_socket(nullptr)
, m_workerProcess(nullptr)
{
setWindowTitle("TCP Socket - " + role);
resize(600, 500);
m_logView = new QTextEdit();
m_logView->setReadOnly(true);
m_inputEdit = new QLineEdit();
m_inputEdit->setPlaceholderText("Enter message to send...");
m_sendButton = new QPushButton("Send Message");
m_sendButton->setEnabled(false);
QHBoxLayout *portLayout = new QHBoxLayout();
QLabel *portLabel = new QLabel("Port:");
m_portSpinBox = new QSpinBox();
m_portSpinBox->setRange(1024, 65535);
m_portSpinBox->setValue(m_port);
m_startServerButton = new QPushButton("Start Server");
m_connectButton = new QPushButton("Connect Server");
portLayout->addWidget(portLabel);
portLayout->addWidget(m_portSpinBox);
portLayout->addWidget(m_startServerButton);
portLayout->addWidget(m_connectButton);
QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addWidget(m_logView);
mainLayout->addLayout(portLayout);
mainLayout->addWidget(m_inputEdit);
mainLayout->addWidget(m_sendButton);
setLayout(mainLayout);
connect(m_sendButton, &QPushButton::clicked, this, &TCPWindow::onSendMessage);
connect(m_portSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &TCPWindow::onPortChanged);
connect(m_startServerButton, &QPushButton::clicked, this, &TCPWindow::onStartServerClicked);
connect(m_connectButton, &QPushButton::clicked, this, &TCPWindow::onConnectClicked);
if (m_role == "Server") {
setupServer();
m_connectButton->hide();
} else {
setupClient();
m_startServerButton->hide();
}
appendLog("TCP " + m_role + " initialized on port " + QString::number(m_port));
}
void TCPWindow::setupServer()
{
m_server = new QTcpServer(this);
connect(m_server, &QTcpServer::newConnection, this, &TCPWindow::onAcceptConnection);
}
void TCPWindow::setupClient()
{
m_socket = new QTcpSocket(this);
connect(m_socket, &QTcpSocket::connected, this, [this]() {
appendLog("Connected to server!");
m_sendButton->setEnabled(true);
m_inputEdit->setPlaceholderText("Enter message to send to server...");
});
connect(m_socket, &QTcpSocket::readyRead, this, &TCPWindow::onReadyRead);
connect(m_socket, &QTcpSocket::disconnected, this, &TCPWindow::onDisconnected);
}
void TCPWindow::onAcceptConnection()
{
if (m_socket) {
m_socket->disconnectFromHost();
m_socket->deleteLater();
}
m_socket = m_server->nextPendingConnection();
if (!m_socket) return;
connect(m_socket, &QTcpSocket::readyRead, this, &TCPWindow::onReadyRead);
connect(m_socket, &QTcpSocket::disconnected, this, &TCPWindow::onDisconnected);
appendLog("Client connected!");
m_sendButton->setEnabled(true);
m_inputEdit->setPlaceholderText("Enter message to send to client...");
}
void TCPWindow::onReadyRead()
{
if (!m_socket) return;
QByteArray data = m_socket->readAll();
appendLog("Received: " + QString::fromUtf8(data));
}
void TCPWindow::onDisconnected()
{
appendLog("Disconnected from peer");
m_sendButton->setEnabled(false);
}
void TCPWindow::onStartServerClicked()
{
if(m_server){
if (!m_server->listen(QHostAddress::LocalHost, m_port)) {
appendLog("Failed to start server: " + m_server->errorString());
return;
}
appendLog("Server listening on port " + QString::number(m_port));
}
}
void TCPWindow::onSendMessage()
{
if (!m_socket || m_socket->state() != QAbstractSocket::ConnectedState) {
appendLog("Not connected!");
return;
}
QString msg = m_inputEdit->text();
if (msg.isEmpty()) return;
m_socket->write(msg.toUtf8());
m_socket->flush();
appendLog("Sent: " + msg);
m_inputEdit->clear();
}
void TCPWindow::onPortChanged(int port)
{
m_port = port;
}
void TCPWindow::onConnectClicked()
{
if (!m_socket) return;
m_socket->connectToHost("127.0.0.1", m_port);
appendLog("Attempting to connect to 127.0.0.1:" + QString::number(m_port));
}
void TCPWindow::appendLog(const QString &msg)
{
m_logView->append(QDateTime::currentDateTime().toString("hh:mm:ss") + " | " + msg);
}
具体代码就不啰嗦了,确实比较简单,而且和上一节非常相似。
使用上,注意要使用127.0.0.1的ip,端口的话可以在规则范围内合理选择。
贴上运行效果图:


三、总结
个人认为,在跨机器的场景中,Tcp还是非常实用的。当然,在网络通信当中,用以实现通信的技术和第三方库很多,比如我使用过的MQTT。需要记住的是,在本机场景中,选择LocalSocket的效率其实是更高的。
与Tcp相对的,还有Udp这种通信方式。在IPC通信中,一般不会使用Udp,因为它属于广播/主播,特点是无连接和不可靠,因此进程间通信的安全性和可靠性无法确保。
但在某些场景下,比如一对多(1-30)的消息统一分发中,Udp还是非常实用的。
既然已经写了Tcp,下一节就顺便记录和实现一下Udp好了。