TCP
TCP(Transmission Control Protocol,传输控制协议)是一个用于数据传输的低层的网络协议,多个互联网协议(包括 HTTP 和 FTP)都是基于 TCP 协议的。它是可靠的、面向流、面向连接的传输协议,特别适合连续数据的传输。
TCP 通信必须先建立连接,分为客户端和服务端,也就是所谓的 C/S(Client/Server)模型,如图:
客户端
客户端使用 QTcpSocket 与 TCP 服务器建立连接并通信。
QTcpSocket 类除了构造函数和析构函数,其他函数都是从 QAbstractSocket 继承或重定义的。QAbstractSocket 用于 TCP 通信的主要接口函数如图:
客户端的 QTcpSocket 实例首先通过 connectToHost() 尝试连接到服务器,需要指定服务器的 IP 地址和端口号。connectToHost() 是异步方式连接服务器,不会阻塞程序运行,连接后发射 connected() 信号。
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTcpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void onConnected();
void onDisconnected();
void onReadyRead();
void on_btnConnect_clicked();
void on_btnSendMsg_clicked();
private:
Ui::Widget *ui;
QTcpSocket *m_client{};
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "./ui_widget.h"
#include <QHostAddress>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
setWindowTitle("客户端");
m_client = new QTcpSocket(this);
connect(m_client, &QTcpSocket::connected, this, &Widget::onConnected);
connect(m_client, &QTcpSocket::disconnected, this, &Widget::onDisconnected);
connect(m_client, &QTcpSocket::readyRead, this, &Widget::onReadyRead);
}
Widget::~Widget()
{
m_client->abort();
delete ui;
}
void Widget::onConnected()
{
ui->labConnectState->setText("Connecting");
}
void Widget::onDisconnected()
{
ui->labConnectState->setText("Disconnect");
}
void Widget::onReadyRead()
{
ui->textEditRecv->setText(m_client->readAll());
}
void Widget::on_btnConnect_clicked()
{
QString str = ui->btnConnect->text();
if (str == "Connect") {
ui->btnConnect->setText("Disconnect");
if (m_client->state() == QAbstractSocket::SocketState::ConnectingState) {
m_client->close();
}
QString address = ui->leAddress->text();
QString port = ui->lePort->text();
m_client->connectToHost(QHostAddress(address), port.toInt());
} else {
ui->btnConnect->setText("Connect");
m_client->close();
}
}
void Widget::on_btnSendMsg_clicked()
{
QString msg = ui->textEditSend->toPlainText();
m_client->write(msg.toLocal8Bit());
}
界面如图:
服务端
服务端程序必须使用 QTcpServer 进行端口监听,建立服务器。QTcpSocket 用于建立连接后使用套接字。
QTcpServer 是从 QObject 继承的类,它主要用于服务器建立网络监听,创建网络 Socket 连接。QTcpServer 类的主要接口函数如下所示:
服务端可以使用 QTcpServer::listen() 指定监听的 IP 地址和端口,一般一个服务程序只监听某个端口的网络连接。
当有新的客户端接入时,QTcpServer 内部的 incomingConnection() 函数会创建一个与客户端连接的 QTcpSocket 对象,然后发出信号 newConnection()。可以使用 nextPendingConnection() 接受客户端的连接,然后使用 QTcpSocket 与客户端通信。所以在客户端与服务端建立连接后,具体的数据通信是通过 QTcpSocket 完成的。
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void onNewConnection();
void onReadyRead();
void onClientDisconnected();
void on_btnClose_clicked();
void on_btnSendMsg_clicked();
private:
Ui::Widget *ui;
QTcpServer *m_server{};
QTcpSocket *m_socket{};
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "./ui_widget.h"
#include <QHostAddress>
#include <QHostInfo>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
setWindowTitle("服务端");
// 获取本地 ip
QString ip;
QHostInfo info = QHostInfo::fromName(QHostInfo::localHostName());
for (auto &address : info.addresses()) {
if (address != QHostAddress::LocalHost && address.toIPv4Address()) {
ip = address.toString();
break;
}
}
ui->leAddress->setText(ip);
ui->lePort->setText("1234");
m_server = new QTcpServer(this);
m_server->listen(QHostAddress::Any, 1234);
connect(m_server, &QTcpServer::newConnection, this, &Widget::onNewConnection);
}
Widget::~Widget()
{
delete ui;
}
void Widget::onNewConnection()
{
m_socket = m_server->nextPendingConnection();
// 告知客户端连接成功
m_socket->write("Congratulations on successfully connecting !");
connect(m_socket, &QTcpSocket::readyRead, this, &Widget::onReadyRead);
connect(m_socket, &QTcpSocket::disconnected, this, &Widget::onClientDisconnected);
// 更新连接状态显示
ui->labConnectState->setText(QString("new connection %1 %2")
.arg(m_socket->peerAddress().toString())
.arg(m_socket->peerPort()));
}
void Widget::onReadyRead()
{
ui->textEditRecv->setText(m_socket->readAll());
}
void Widget::onClientDisconnected()
{
ui->labConnectState->setText(QString("%1 %2 disconnected")
.arg(m_socket->peerAddress().toString())
.arg(m_socket->peerPort()));
}
void Widget::on_btnClose_clicked()
{
m_server->close();
}
void Widget::on_btnSendMsg_clicked()
{
if (!m_socket)
return;
QString msg = ui->textEditSend->toPlainText();
m_socket->write(msg.toLocal8Bit());
}
界面如图:
总结
本文只简单演示了 TCP 通信的基本原理。服务端只允许一个客户端连接。然而,一般的 TCP 服务器程序允许多个客户端接入,博主将在其他文章中进行讲解。