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_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic: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_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic: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("服务端");// 获取本地 ipQString 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 服务器程序允许多个客户端接入,博主将在其他文章中进行讲解。