最近需要用到TCP/IP通訊,這邊就先找個簡單的例程學習一下。Qt的TCP通訊編程可以使用QtNetwork模塊,QtNetwork模塊提供的類能夠創(chuàng)建基于TCP/IP的客戶端與服務端應用程序,一般會使用QTcpSocket、QTcpServer類
TCP和UDP通訊
網(wǎng)絡通信方式主要有兩種:TCP與UDP。以下拷貝網(wǎng)絡上總結(jié)兩者之間的區(qū)別:
1、TCP面向連接(如打電話要先撥號建立連接);UDP是無連接的,即發(fā)送數(shù)據(jù)之前不需要建立連接
2、TCP提供可靠的服務。也就是說,通過TCP連接傳送的數(shù)據(jù),無差錯,不丟失,不重復,且按序到達;UDP盡最大努力交付,即不保證可靠交付
3、TCP面向字節(jié)流,實際上是TCP把數(shù)據(jù)看成一連串無結(jié)構(gòu)的字節(jié)流;UDP是面向報文的。
UDP沒有擁塞控制,因此網(wǎng)絡出現(xiàn)擁塞不會使源主機的發(fā)送速率降低(對實時應用很有用,如IP電話,實時視頻會議等)
4、每一條TCP連接只能是點到點的;UDP支持一對一,一對多,多對一和多對多的交互通信
5、TCP首部開銷20字節(jié);UDP的首部開銷小,只有8個字節(jié)
6、TCP的邏輯通信信道是全雙工的可靠信道,UDP則是不可靠信道。
另外在編程時TCP通訊可能涉及到粘包/拆包。UDP是基于報文傳輸?shù)模l(fā)送幾次Write(),接收端就會用幾次Read(),每次讀取一個報文,報文間不合并,多于緩沖區(qū)的報文會丟棄。TCP是基于數(shù)據(jù)流傳輸?shù)模琖rite()和Read()的次數(shù)不固定,報文間會以隨機的方式合并,這就需要在接收時進行粘包/拆包處理,這里暫不涉及。
服務器
TCP服務器流程一般包括:
1.創(chuàng)建QTcpServer對象
2.啟動服務器(監(jiān)聽)調(diào)用成員方法listen
3.當有客戶端鏈接時候會發(fā)送newConnection信號,觸發(fā)槽函數(shù)接受連接
4.QTcpsocket發(fā)送數(shù)據(jù)用成員方法write
5.當客戶端有數(shù)據(jù)進來,QTcpSocket對象就會發(fā)送readyRead信號,關(guān)聯(lián)槽函數(shù)讀取數(shù)據(jù),使用read或readall方法
找了一個博文,見引用,寫得很詳細,這邊幾乎也是全搬過來。
源碼
源碼如下
#include "widget.h"
#include "ui_widget.h"
#include <QtNetwork>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
/*讀取本機網(wǎng)卡信息...*/
QString localHostName = QHostInfo::localHostName();
QHostInfo info = QHostInfo::fromName(localHostName);
/*將本機所有的IPV4地址添加到comBox_hostIP下.*/
foreach(QHostAddress ipAddress, info.addresses())
{
if(ipAddress.protocol() == QAbstractSocket::IPv4Protocol)
{
qDebug() << ipAddress.toString();
ui->comBox_hostIP->addItem(ipAddress.toString());
}
}
ui->comBox_hostIP->addItem("127.0.0.1");
ui->comBox_hostIP->setCurrentIndex(ui->comBox_hostIP->count()-1);
ui->lineEdit_Port->setText("12345");
server = new QTcpServer(this);
connect(server, &QTcpServer::newConnection, this,&Widget::on_newConnection);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushBtn_listen_clicked()
{
/*監(jiān)聽本地IP加端口號.*/
if(server->listen(QHostAddress(ui->comBox_hostIP->currentText()), ui->lineEdit_Port->text().toInt()) == false)
{
/*監(jiān)聽失敗,打印信息.*/
qDebug()<<"listen false";
}
else
{
/*監(jiān)聽成功,則將一些控件鎖死.*/
ui->pushBtn_listen->setDisabled(true);
ui->lineEdit_Port->setDisabled(true);
ui->comBox_hostIP->setDisabled(true);
qDebug()<<"listen successfully";
/*激活關(guān)閉按鈕*/
ui->pushBtn_close->setEnabled(true);
}
}
void Widget::on_newConnection()
{
/*獲取新連接客戶端的socket*/
QTcpSocket *socket = server->nextPendingConnection();
/*將這個socket添加到List容器中...*/
sockList.append(socket);
/*獲取客戶端的IP地址和端口號信息,并轉(zhuǎn)換為字符串.*/
QString info = socket->peerAddress().toString() \
+ ':' + QString::number(socket->peerPort());
/*將信息打印到文本框.*/
ui->textEdit_rcv->append("Connected:"+info);
/*將客戶端的信息添加到comBox_clientIP下.*/
ui->comBox_clientIP->addItem(info);
/*將新連接的socket對象的可以讀取信號連接到接收槽函數(shù).*/
connect(socket, &QTcpSocket::readyRead, this, &Widget::on_recv);
/*將新連接的socket對象的斷開連接信號連接到斷開槽函數(shù).*/
connect(socket, &QTcpSocket::disconnected, this, &Widget::on_disconnect);
if(ui->comBox_clientIP->count()==2)
ui->comBox_clientIP->insertItem(0,"All");
}
void Widget::on_recv()
{
/*找到觸發(fā)信號的那個socket對象.*/
QTcpSocket *sock = qobject_cast<QTcpSocket *>(sender());
/*讀取信息并轉(zhuǎn)化為字符串.*/
QString info = "From " + sock->peerAddress().toString() \
+ ':' + QString::number(sock->peerPort());
/*將客戶端的信息打印到文本框.*/
ui->textEdit_rcv->append(info);
/*將接收到的數(shù)據(jù)也打印到文本框.*/
ui->textEdit_rcv->append(sock->readAll());
}
void Widget::on_disconnect()
{
/*找到觸發(fā)信號的那個socket對象.*/
QTcpSocket *sock = qobject_cast<QTcpSocket *>(sender());
sockList.removeOne(sock);
/*讀取信息并轉(zhuǎn)化為字符串.*/
QString clientinfo = sock->peerAddress().toString() \
+ ':' + QString::number(sock->peerPort());
/*將客戶端的信息打印到文本框.*/
ui->textEdit_rcv->append(clientinfo+" Disconneted!");
/*根據(jù)字符串找到comBox_clientIP中的對應元素的索引號*/
int index = ui->comBox_clientIP->findText(clientinfo);
/*刪除那個元素.*/
ui->comBox_clientIP->removeItem(index);
/*將接收到的數(shù)據(jù)也打印到文本框.*/
// ui->textEdit_rcv->append(sock->readAll());
if(ui->comBox_clientIP->count()<=2)
ui->comBox_clientIP->removeItem(0);
/*將新連接的socket對象的可以讀取信號與接收槽函數(shù)斷開.*/
disconnect(sock, &QTcpSocket::readyRead, this, &Widget::on_recv);
/*將新連接的socket對象的斷開連接信號與斷開槽函數(shù)斷開.*/
disconnect(sock, &QTcpSocket::disconnected, this, &Widget::on_disconnect);
}
void Widget::on_pushBtn_clear_clicked()
{
ui->textEdit_rcv->clear();
}
void Widget::on_pushBtn_send_clicked()
{
if(currSock == NULL)
{
foreach(QTcpSocket *sock, sockList)
{
sock->write(ui->textEdit_tx->toPlainText().toUtf8());
}
}
else
{
/*如果選擇的是一個特定的客戶端,則只向它發(fā)送.*/
currSock->write(ui->textEdit_tx->toPlainText().toUtf8());
}
}
void Widget::on_comBox_clientIP_currentTextChanged(const QString &arg1)
{
if(arg1 == "All")
{
currSock = NULL;
return;
}
if (sockList.empty())
return;
/*不然就讀取選中的信息,將其拆分為IP地址和端口號.*/
QStringList info = arg1.split(':');
QString ip = info[0];
int port = info[1].toInt();
/*遍歷容器,找到對應的那個socket.*/
foreach(QTcpSocket *sock, sockList)
{
if(sock->peerAddress().toString() == ip && sock->peerPort() == port)
{
/*當前sock指針指向找到的那個socket.*/
currSock = sock;
break;
}
}
}
void Widget::on_pushBtn_kickoff_clicked()
{
if(currSock == NULL)
{
foreach(QTcpSocket *sock, sockList)
{
sock->close();
}
}
else
{
currSock->close();
}
}
void Widget::on_pushBtn_close_clicked()
{
currSock = NULL;
/*關(guān)閉監(jiān)聽.*/
server->close();
/*將一些控件恢復.*/
ui->pushBtn_close->setDisabled(true);
ui->pushBtn_listen->setEnabled(true);
ui->lineEdit_Port->setEnabled(true);
ui->comBox_hostIP->setEnabled(true);
/*遍歷之前全部連接的socket,并一一斷開.*/
foreach(QTcpSocket *sock, sockList)
{
sock->close();
}
}
客戶端
客戶端更簡單些,基本流程如下:
1.創(chuàng)建QTcpSocket對象
2.鏈接服務器connectToHost
3.QTcpsocket發(fā)送數(shù)據(jù)用成員方法write
4.當對方有數(shù)據(jù)來,QTcpSocket對象就會發(fā)送readyRead信號,關(guān)聯(lián)槽函數(shù)讀取數(shù)據(jù)
參考了另一篇博文,代碼也是幾乎照抄。
源碼
#include "tcpclient.h"
#include "ui_tcpclient.h"
TcpClient::TcpClient(QWidget *parent)
: QWidget(parent)
, ui(new Ui::TcpClient)
{
ui->setupUi(this);
m_socket = new QTcpSocket();
ui->lEdit_serverIP->setText("127.0.0.1");
ui->lEdit_serverPort->setText("12345");
ui->Btn_connect->setEnabled(true);
ui->Btn_Send->setEnabled(false);
}
TcpClient::~TcpClient()
{
delete this->m_socket;
delete ui;
}
void TcpClient::on_Btn_connect_clicked()
{
if(ui->Btn_connect->text() == tr("Connect"))
{
QString IP;
int port;
//獲取IP地址
IP = ui->lEdit_serverIP->text();
//獲取端口號
port = ui->lEdit_serverPort->text().toInt();
//取消已有的連接
m_socket->abort();
//連接服務器
m_socket->connectToHost(IP, port);
//等待連接成功
if(!m_socket->waitForConnected(30000))
{
qDebug() << "Connection failed!";
ui->textEdit_recv->append("Connection failed!");
return;
}
qDebug() << "Connect successfully!";
//發(fā)送按鍵使能
ui->Btn_Send->setEnabled(true);
//修改按鍵文字
ui->Btn_connect->setText("Disconnect");
ui->textEdit_recv->append("Connect successfully!");
connect(m_socket,&QTcpSocket::readyRead, this,&TcpClient::socket_readData);
connect(m_socket,&QTcpSocket::disconnected, this,&TcpClient::socket_disconnect);
}
else
{
//斷開連接
m_socket->disconnectFromHost();
//修改按鍵文字
ui->Btn_connect->setText("Connect");
ui->Btn_Send->setEnabled(false);
disconnect(m_socket,&QTcpSocket::readyRead, this,&TcpClient::socket_readData);
disconnect(m_socket,&QTcpSocket::disconnected, this,&TcpClient::socket_disconnect);
}
}
void TcpClient::on_Btn_Send_clicked()
{
qDebug() << "Send: " << ui->textEdit_tx->toPlainText();
//獲取文本框內(nèi)容并以ASCII碼形式發(fā)送
m_socket->write(ui->textEdit_tx->toPlainText().toLatin1());
m_socket->flush();
}
void TcpClient::socket_readData()
{
QByteArray buffer;
//讀取緩沖區(qū)數(shù)據(jù)
buffer = m_socket->readAll();
if(!buffer.isEmpty())
{
// QString str = ui->textEdit_recv->toPlainText();
// str+=tr(buffer)+"\n";
//刷新顯示
ui->textEdit_recv->append("From server: " +tr(buffer) );
// ui->textEdit_recv->setText(str);
}
}
void TcpClient::socket_disconnect(){
//發(fā)送按鍵失能
ui->Btn_Send->setEnabled(false);
//修改按鍵文字
ui->Btn_connect->setText("Connect");
qDebug() << "Disconnected...";
ui->textEdit_recv->append("Disconnected!");
disconnect(m_socket,&QTcpSocket::readyRead, this,&TcpClient::socket_readData);
disconnect(m_socket,&QTcpSocket::disconnected, this,&TcpClient::socket_disconnect);
}
void TcpClient::on_Btn_clear_clicked()
{
ui->textEdit_recv->clear();
}
結(jié)果
基本功能都有,程序運行正確無誤哈??文章來源:http://www.zghlxwxcb.cn/news/detail-426186.html
引用
Qt學習記錄之簡單的TCP服務器
Qt 實現(xiàn)簡單的TCP通信
QT之TCP通信
TCP粘包產(chǎn)生的原因、解決方法及Qt項目代碼實現(xiàn)文章來源地址http://www.zghlxwxcb.cn/news/detail-426186.html
到了這里,關(guān)于Qt 服務器/客戶端TCP通訊的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!