1. 效果演示
- 客戶端
- 伺服器
連線成功之後
2. 預備知識
如果不知道網路程式設計的可以去看我的上一篇文章C++網路程式設計
在Qt中,實現網路程式設計的方式比用C++或C實現要方便簡單許多,因為Qt已經替我們封裝好了,我們會使用就可以了,然後大家還需要了解Qt 的訊號槽機制,可以參考我這篇文章,Qt訊號槽
2.1 QTcpServer
QTcpServer 類用於監聽客戶端連線以及和客戶端建立連線,在使用之前先介紹一下這個類提供的一些常用 API 函式:
建構函式
QTcpServer::QTcpServer(QObject *parent = Q_NULLPTR);
給監聽的套接字設定監聽
bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);
// 判斷當前物件是否在監聽, 是返回true,沒有監聽返回false
bool QTcpServer::isListening() const;
// 如果當前物件正在監聽返回監聽的伺服器地址資訊, 否則返回 QHostAddress::Null
QHostAddress QTcpServer::serverAddress() const;
// 如果伺服器正在監聽連線,則返回伺服器的埠; 否則返回0
quint16 QTcpServer::serverPort() const
引數:
address:通過類 QHostAddress 可以封裝 IPv4、IPv6 格式的 IP 地址,QHostAddress::Any 表示自動繫結
port:如果指定為 0 表示隨機繫結一個可用埠。
返回值:繫結成功返回 true,失敗返回 false
得到和客戶端建立連線之後用於通訊的 QTcpSocket 套接字物件,它是 QTcpServer 的一個子物件,當 QTcpServer 物件析構的時候會自動析構這個子物件,當然也可自己手動析構,建議用完之後自己手動析構這個通訊的 QTcpSocket 物件。
QTcpSocket *QTcpServer::nextPendingConnection();
阻塞等待客戶端發起的連線請求,不推薦在單執行緒程式中使用,建議使用非阻塞方式處理新連線,即使用訊號 newConnection() 。
bool QTcpServer::waitForNewConnection(int msec = 0, bool *timedOut = Q_NULLPTR);
引數:
msec:指定阻塞的最大時長,單位為毫秒(ms)
timeout:傳出引數,如果操作超時 timeout 為 true,沒有超時 timeout 為 false
2.2 QTcpServer訊號
當接受新連線導致錯誤時,將發射如下訊號。socketError 引數描述了發生的錯誤相關的資訊
[signal] void QTcpServer::acceptError(QAbstractSocket::SocketError socketError);
每次有新連線可用時都會發出 newConnection () 訊號。
[signal] void QTcpServer::newConnection();
2.3 QTcpSocket
QTcpSocket 是一個套接字通訊類,不管是客戶端還是伺服器端都需要使用。在 Qt 中傳送和接收資料也屬於 IO 操作(網路 IO)
建構函式
QTcpSocket::QTcpSocket(QObject *parent = Q_NULLPTR);
連線伺服器,需要指定伺服器端繫結的IP和埠資訊。
[virtual] void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol);
[virtual] void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode = ReadWrite);
在 Qt 中不管呼叫讀操作函式接收資料,還是呼叫寫函式傳送資料,操作的物件都是本地的由 Qt 框架維護的一塊記憶體。因此,呼叫了傳送函式資料不一定會馬上被髮送到網路中,呼叫了接收函式也不是直接從網路中接收資料,關於底層的相關操作是不需要使用者來維護的。
接收資料
// 指定可接收的最大位元組數 maxSize 的資料到指標 data 指向的記憶體中
qint64 QIODevice::read(char *data, qint64 maxSize);
// 指定可接收的最大位元組數 maxSize,返回接收的字串
QByteArray QIODevice::read(qint64 maxSize);
// 將當前可用運算元據全部讀出,通過返回值返回讀出的字串
QByteArray QIODevice::readAll();
2.4 QTcpSocket訊號
在使用 QTcpSocket 進行套接字通訊的過程中,如果該類物件發射出 readyRead() 訊號,說明對端傳送的資料達到了,之後就可以呼叫 read 函式接收資料了。
[signal] void QIODevice::readyRead();
呼叫 connectToHost() 函式併成功建立連線之後發出 connected() 訊號。
在套接字斷開連線時發出 disconnected() 訊號。
呼叫 connectToHost() 函式併成功建立連線之後發出 connected() 訊號。
[signal] void QAbstractSocket::disconnected();
3. 通訊流程
3.1 伺服器端
- 建立套接字伺服器 QTcpServer 物件
- 通過 QTcpServer 物件設定監聽,即:QTcpServer::listen()
- 基於 QTcpServer::newConnection() 訊號檢測是否有新的客戶端連線
- 如果有新的客戶端連線呼叫 QTcpSocket *QTcpServer::nextPendingConnection() 得到通訊的套接字物件
- 使用通訊的套接字物件 QTcpSocket 和客戶端進行通訊
標頭檔案
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_startServer_clicked();
void on_sendMsg_clicked();
private:
Ui::MainWindow *ui;
QTcpServer* m_server;
QTcpSocket* m_tcp;
};
原始檔
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle("TCP - 伺服器");
// 建立 QTcpServer 物件
m_server = new QTcpServer(this);
// 檢測是否有新的客戶端連線
connect(m_server, &QTcpServer::newConnection, this, [=]()
{
m_tcp = m_server->nextPendingConnection();
ui->record->append("成功和客戶端建立了新的連線...");
m_status->setPixmap(QPixmap(":/connect.png").scaled(20, 20));
// 檢測是否有客戶端資料
connect(m_tcp, &QTcpSocket::readyRead, this, [=]()
{
// 接收資料
QString recvMsg = m_tcp->readAll();
ui->record->append("客戶端Say: " + recvMsg);
});
// 客戶端斷開了連線
connect(m_tcp, &QTcpSocket::disconnected, this, [=]()
{
ui->record->append("客戶端已經斷開了連線...");
m_tcp->deleteLater();
m_status->setPixmap(QPixmap(":/disconnect.png").scaled(20, 20));
});
});
}
MainWindow::~MainWindow()
{
delete ui;
}
// 啟動伺服器端的服務按鈕
void MainWindow::on_startServer_clicked()
{
unsigned short port = ui->port->text().toInt();
// 設定伺服器監聽
m_server->listen(QHostAddress::Any, port);
ui->startServer->setEnabled(false);
}
// 點選傳送資料按鈕
void MainWindow::on_sendMsg_clicked()
{
QString sendMsg = ui->msg->toPlainText();
m_tcp->write(sendMsg.toUtf8());
ui->record->append("伺服器Say: " + sendMsg);
ui->msg->clear();
}
3.2 客戶端
通訊流程
- 建立通訊的套接字類 QTcpSocket 物件
- 使用伺服器端繫結的 IP 和埠連線伺服器 QAbstractSocket::connectToHost()
- 使用 QTcpSocket 物件和伺服器進行通訊
標頭檔案
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_connectServer_clicked();
void on_sendMsg_clicked();
void on_disconnect_clicked();
private:
Ui::MainWindow *ui;
QTcpSocket* m_tcp;
};
原始檔
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle("TCP - 客戶端");
// 建立通訊的套接字物件
m_tcp = new QTcpSocket(this);
// 檢測伺服器是否回覆了資料
connect(m_tcp, &QTcpSocket::readyRead, [=]()
{
// 接收伺服器傳送的資料
QByteArray recvMsg = m_tcp->readAll();
ui->record->append("伺服器Say: " + recvMsg);
});
// 檢測是否和伺服器是否連線成功了
connect(m_tcp, &QTcpSocket::connected, this, [=]()
{
ui->record->append("恭喜, 連線伺服器成功!!!");
m_status->setPixmap(QPixmap(":/connect.png").scaled(20, 20));
});
// 檢測伺服器是否和客戶端斷開了連線
connect(m_tcp, &QTcpSocket::disconnected, this, [=]()
{
ui->record->append("伺服器已經斷開了連線, ...");
ui->connectServer->setEnabled(true);
ui->disconnect->setEnabled(false);
});
}
MainWindow::~MainWindow()
{
delete ui;
}
// 連線伺服器按鈕按下之後的處理動作
void MainWindow::on_connectServer_clicked()
{
QString ip = ui->ip->text();
unsigned short port = ui->port->text().toInt();
// 連線伺服器
m_tcp->connectToHost(QHostAddress(ip), port);
ui->connectServer->setEnabled(false);
ui->disconnect->setEnabled(true);
}
// 傳送資料按鈕按下之後的處理動作
void MainWindow::on_sendMsg_clicked()
{
QString sendMsg = ui->msg->toPlainText();
m_tcp->write(sendMsg.toUtf8());
ui->record->append("客戶端Say: " + sendMsg);
ui->msg->clear();
}
// 斷開連線按鈕被按下之後的處理動作
void MainWindow::on_disconnect_clicked()
{
m_tcp->close();
ui->connectServer->setEnabled(true);
ui->disconnect->setEnabled(false);
}