/*******************************************************************************************/
一、linux下的tcp通信過程
其中bind綁定,會固定一個端口,否則是隨機的。
一個鏈接是由雙方的ip和端口組成的,固定端口保證源的不變性,
這樣另一端在任何時候訪問的目的都是一致的,也可以說這個端口提供了什么服務。
同時綁定后直接操作socket id就可以操作對應的鏈接了。
/*******************************************************************************************/
二、QT下的TCP通信過程
Qt中提供的所有的Socket類都是非阻塞的。
Qt中常用的用於socket通信的套接字類:
QTcpServer
用於TCP/IP通信, 作為服務器端套接字使用
QTcpSocket
用於TCP/IP通信,作為客戶端套接字使用。
QUdpSocket
用於UDP通信,服務器,客戶端均使用此套接字。
1.QT下的服務端
1).socket函數變為QTcpServer
2).bind ,listen 統一為listen
同時沒有accept,當有一個鏈接過來的時候,會產生一個信號:newconnection,可以從對應的槽函數中取出建立好的套接字(對方的)QTcpSocket
如果成功和對方建立好鏈接,通信套接字會自動觸發connected信號
3).read :
對方發送數據過來,鏈接的套接字(通信套接字)就會觸發(本機的)readyRead信號,需要在對應的槽函數中接收數據
4).write,
發送數據,對方的(客戶端的)套接字(通信套接字)就會觸發readyRead信號,需要在對應的槽函數中接收數據
如果對方主動斷開連接,對方的(客戶端的)套接字(通信套接字)會自動觸發disconnected信號
2.QT下的客戶端:
1).socket函數變為 QTcpSocket
2).connect變為connetToHost()
如果成功和對方建立好鏈接,就會自動觸發connected信號
3).read :
對方發送數據過來,鏈接的套接字(通信套接字)就會觸發(本機的)readyRead信號,需要在對應的槽函數中接收數據
4).write,
發送數據,對方的(服務器的)套接字(通信套接字)就會觸發readyRead信號,需要在對應的槽函數中接收數據
如果對方主動斷開連接,就會自動觸發disconnected信號
具體見圖《QtTCP通信過程》
/*******************************************************************************************/
三、TCP服務器
Qwidget是基類,比較干凈,QMainWindow相對比較多。
如果輸入頭文件沒有提示,就需要在項目文件中加入對應模塊,同時再編譯不運行一下,讓qt可以構建並
加載對應的模塊。
#include <QTcpServer> //監聽套接字
#include <QTcpSocket> //通信套接字//對方的(客戶端的)套接字(通信套接字)
//監聽套接字,指定父對象,讓其自動回收空間
tcpServer = new QTcpServer(this);
tcpServer->listen(QHostAddress::Any, 8888);
setWindowTitle("服務器: 8888");
connect(tcpServer, &QTcpServer::newConnection,
[=]()//信號無參數,這里也沒有參數
{
//取出建立好連接的套接字
tcpSocket = tcpServer->nextPendingConnection();
//獲取對方的IP和端口
QString ip = tcpSocket->peerAddress().toString();
qint16 port = tcpSocket->peerPort();
QString temp = QString("[%1:%2]:成功連接").arg(ip).arg(port);
ui->textEditRead->setText(temp);
//必須放在里面,因為建立好鏈接才能讀,或者說tcpSocket有指向才能操作
connect(tcpSocket, &QTcpSocket::readyRead,
[=]()
{
//從通信套接字中取出內容
QByteArray array = tcpSocket->readAll();
ui->textEditRead->append(array);
}
);
}
);
void ServerWidget::on_buttonSend_clicked()
{
if(NULL == tcpSocket)
{
return;
}
//獲取編輯區內容
QString str = ui->textEditWrite->toPlainText();
//給對方發送數據, 使用套接字是tcpSocket
tcpSocket->write( str.toUtf8().data() );
}
void ServerWidget::on_buttonClose_clicked()
{
if(NULL == tcpSocket)
{
return;
}
//主動和客戶端斷開連接
tcpSocket->disconnectFromHost();
tcpSocket->close();
tcpSocket = NULL;
}
/*******************************************************************************************/
四、TCP客戶端
可以在項目中添加新文件中選擇Qt--->Qt設計師界面類(這個是帶ui的),選擇這個后項目會多出一個ui
ui->setupUi(this);//顯示ui
tcpSocket = NULL;
//分配空間,指定父對象
tcpSocket = new QTcpSocket(this);
setWindowTitle("客戶端");
connect(tcpSocket, &QTcpSocket::connected,
[=]()
{
ui->textEditRead->setText("成功和服務器建立好連接");
}
);
//因為tcpSocket已經分配了空間,有指向,所以可以放在外面
connect(tcpSocket, &QTcpSocket::readyRead,
[=]()
{
//獲取對方發送的內容
QByteArray array = tcpSocket->readAll();
//追加到編輯區中
ui->textEditRead->append(array);
}
);
void ClientWidget::on_buttonConnect_clicked()
{
//獲取服務器ip和端口
QString ip = ui->lineEditIP->text();
qint16 port = ui->lineEditPort->text().toInt();
//主動和服務器建立連接
tcpSocket->connectToHost(QHostAddress(ip), port);
}
void ClientWidget::on_buttonSend_clicked()
{
//獲取編輯框內容
QString str = ui->textEditWrite->toPlainText();
//發送數據
tcpSocket->write( str.toUtf8().data() );
}
void ClientWidget::on_buttonClose_clicked()
{
//主動和對方斷開連接
tcpSocket->disconnectFromHost();
tcpSocket->close();//這里釋放連接,前面connect的時候會建立連接
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ServerWidget w;
w.show();
ClientWidget w2;
w2.show();//顯示另外一個窗口
return a.exec();
}
上述代碼具體見《TCP》

1 #ifndef SERVERWIDGET_H 2 #define SERVERWIDGET_H 3 4 #include <QWidget> 5 #include <QTcpServer> //監聽套接字 6 #include <QTcpSocket> //通信套接字 7 8 namespace Ui { 9 class ServerWidget; 10 } 11 12 class ServerWidget : public QWidget 13 { 14 Q_OBJECT 15 16 public: 17 explicit ServerWidget(QWidget *parent = 0); 18 ~ServerWidget(); 19 20 private slots: 21 void on_buttonSend_clicked(); 22 23 void on_buttonClose_clicked(); 24 25 private: 26 Ui::ServerWidget *ui; 27 28 QTcpServer *tcpServer; //監聽套接字 29 QTcpSocket *tcpSocket; //通信套接字 30 31 }; 32 33 #endif // SERVERWIDGET_H

1 #include "serverwidget.h" 2 #include "ui_serverwidget.h" 3 4 ServerWidget::ServerWidget(QWidget *parent) : 5 QWidget(parent), 6 ui(new Ui::ServerWidget) 7 { 8 ui->setupUi(this); 9 10 tcpServer = NULL; 11 tcpSocket = NULL; 12 13 //監聽套接字,指定父對象,讓其自動回收空間 14 tcpServer = new QTcpServer(this); 15 16 tcpServer->listen(QHostAddress::Any, 8888); 17 18 setWindowTitle("服務器: 8888"); 19 20 connect(tcpServer, &QTcpServer::newConnection, 21 [=]() 22 { 23 //取出建立好連接的套接字 24 tcpSocket = tcpServer->nextPendingConnection(); 25 26 //獲取對方的IP和端口 27 QString ip = tcpSocket->peerAddress().toString(); 28 qint16 port = tcpSocket->peerPort(); 29 QString temp = QString("[%1:%2]:成功連接").arg(ip).arg(port); 30 31 ui->textEditRead->setText(temp); 32 33 connect(tcpSocket, &QTcpSocket::readyRead, 34 [=]() 35 { 36 //從通信套接字中取出內容 37 QByteArray array = tcpSocket->readAll(); 38 ui->textEditRead->append(array); 39 } 40 41 ); 42 43 44 } 45 46 ); 47 48 } 49 50 ServerWidget::~ServerWidget() 51 { 52 delete ui; 53 } 54 55 void ServerWidget::on_buttonSend_clicked() 56 { 57 if(NULL == tcpSocket) 58 { 59 return; 60 } 61 //獲取編輯區內容 62 QString str = ui->textEditWrite->toPlainText(); 63 //給對方發送數據, 使用套接字是tcpSocket 64 tcpSocket->write( str.toUtf8().data() ); 65 66 } 67 68 void ServerWidget::on_buttonClose_clicked() 69 { 70 if(NULL == tcpSocket) 71 { 72 return; 73 } 74 75 //主動和客戶端端口連接 76 tcpSocket->disconnectFromHost(); 77 tcpSocket->close(); 78 tcpSocket = NULL; 79 }

1 #ifndef CLIENTWIDGET_H 2 #define CLIENTWIDGET_H 3 4 #include <QWidget> 5 #include <QTcpSocket> //通信套接字 6 7 namespace Ui { 8 class ClientWidget; 9 } 10 11 class ClientWidget : public QWidget 12 { 13 Q_OBJECT 14 15 public: 16 explicit ClientWidget(QWidget *parent = 0); 17 ~ClientWidget(); 18 19 private slots: 20 void on_buttonConnect_clicked(); 21 22 void on_buttonSend_clicked(); 23 24 void on_buttonClose_clicked(); 25 26 private: 27 Ui::ClientWidget *ui; 28 29 QTcpSocket *tcpSocket; //通信套接字 30 }; 31 32 #endif // CLIENTWIDGET_H

1 #include "clientwidget.h" 2 #include "ui_clientwidget.h" 3 #include <QHostAddress> 4 5 ClientWidget::ClientWidget(QWidget *parent) : 6 QWidget(parent), 7 ui(new Ui::ClientWidget) 8 { 9 ui->setupUi(this); 10 11 tcpSocket = NULL; 12 13 //分配空間,指定父對象 14 tcpSocket = new QTcpSocket(this); 15 16 setWindowTitle("客戶端"); 17 18 19 connect(tcpSocket, &QTcpSocket::connected, 20 [=]() 21 { 22 ui->textEditRead->setText("成功和服務器建立好連接"); 23 } 24 ); 25 26 connect(tcpSocket, &QTcpSocket::readyRead, 27 [=]() 28 { 29 //獲取對方發送的內容 30 QByteArray array = tcpSocket->readAll(); 31 //追加到編輯區中 32 ui->textEditRead->append(array); 33 } 34 35 ); 36 37 } 38 39 ClientWidget::~ClientWidget() 40 { 41 delete ui; 42 } 43 44 void ClientWidget::on_buttonConnect_clicked() 45 { 46 //獲取服務器ip和端口 47 QString ip = ui->lineEditIP->text(); 48 qint16 port = ui->lineEditPort->text().toInt(); 49 50 //主動和服務器建立連接 51 tcpSocket->connectToHost(QHostAddress(ip), port); 52 53 } 54 55 void ClientWidget::on_buttonSend_clicked() 56 { 57 //獲取編輯框內容 58 QString str = ui->textEditWrite->toPlainText(); 59 //發送數據 60 tcpSocket->write( str.toUtf8().data() ); 61 62 } 63 64 void ClientWidget::on_buttonClose_clicked() 65 { 66 //主動和對方斷開連接 67 tcpSocket->disconnectFromHost(); 68 tcpSocket->close(); 69 }
/*******************************************************************************************/
五、UDP通信過程
使用Qt提供的QUdpSocket進行UDP通信。在UDP方式下,客戶端並不與服務器建立連接,它只負責調用發送函數向服務器發送數據。
類似的服務器也不從客戶端接收連接,只負責調用接收函數,等待來自客戶端的數據的到達。
在UDP通信中,服務器端和客戶端的概念已經顯得有些淡化,兩部分做的工作都大致相同
1.QT下的服務端
socket函數變為QUdpSocket
bind ,還是bind,(固定端口,讓別人可以知道往哪里發,客戶端也可以綁定)
readDatagram :
對方發送數據過來,套接字就會觸發readyRead信號,需要在對應的槽函數中接收數據
writeDatagram,
發送數據,對方的(客戶端的)套接字就會觸發readyRead信號,需要在對應的槽函數中接收數據
close 還是close
2.QT下的客戶端:
socket函數變為 QUdpSocket
readDatagram :
對方發送數據過來,套接字就會觸發readyRead信號,需要在對應的槽函數中接收數據
writeDatagram,
發送數據,對方的(客戶端的)套接字就會觸發readyRead信號,需要在對應的槽函數中接收數據
close 還是close
具體見圖《QtUDP通信過程》
/*******************************************************************************************/
六、UDP文本發送
UDP中沒有嚴格的區分服務端和客戶端。
關閉按鈕是用於關閉窗口的,這主要是由於udp不是面向連接的,沒有斷開連接的說法。
#include <QUdpSocket> //UDP套接字
//分配空間,指定父對象,這是為了讓父對象來回收,其實也可以不用指定,自己來回收資源也行
udpSocket = new QUdpSocket(this);
//綁定
udpSocket->bind(8888);
setWindowTitle("服務器端口為:8888");
//當對方成功發送數據過來
//自動觸發 readyRead()
connect(udpSocket, &QUdpSocket::readyRead, this, &Widget::dealMsg);
void Widget::dealMsg()
{
//讀取對方發送的內容
char buf[1024] = {0};
QHostAddress cliAddr; //對方地址
quint16 port; //對方端口
qint64 len = udpSocket->readDatagram(buf, sizeof(buf), &cliAddr, &port);
if(len > 0)
{
//格式化 [192.68.2.2:8888]aaaa
QString str = QString("[%1:%2] %3")
.arg(cliAddr.toString())
.arg(port)
.arg(buf);
//給編輯區設置內容
ui->textEdit->setText(str);
}
}
//發送按鈕
void Widget::on_buttonSend_clicked()
{
//先獲取對方的IP和端口
QString ip = ui->lineEditIP->text();
qint16 port = ui->lineEditPort->text().toInt();
//獲取編輯區內容
QString str = ui->textEdit->toPlainText();
//給指定的IP發送數據
udpSocket->writeDatagram(str.toUtf8(), QHostAddress(ip), port);
}
/*******************************************************************************************/
七、UDP多播組播
1.廣播
廣播地址:255.255.255.255,在某個局域網上就自動會變為那個局域網的廣播地址,如果指定了
是某個局域網的廣播地址如:192.168.1.255,則只能在這個局域網192.168.1.x上廣播。
只要是網段是一樣的,對應的端口就都會收到。
比如廣播地址:255.255.255.255,端口8999,則其他同網段中的端口8999就會收到。
2.組播
總是廣播容易造成網絡阻塞,所以就需要組播了,另外,
我們再使用廣播發送消息的時候會發送給所有用戶,但是有些用戶是不想接受消息的,這時候我們就應該使用組播,
接收方只有先注冊到組播地址中才能收到組播消息,否則則接受不到消息。另外組播是可以在Internet中使用的。
組播地址屬於D類地址,D類地址又分出其他的,關於組播地址的分類:
224.0.0.0~224.0.0.255為預留的組播地址(永久組地址),地址224.0.0.0保留不做分配,其它地址供路由協議使用;
224.0.1.0~224.0.1.255是公用組播地址,可以用於Internet;
224.0.2.0~238.255.255.255為用戶可用的組播地址(臨時組地址),全網范圍內有效;
239.0.0.0~239.255.255.255為本地管理組播地址,僅在特定的本地范圍內有效。
在使用QUdpSocket類的writeDatagram()函數發送數據的時候,其中第二個參數host應該指定為組播地址,
注冊加入到組播地址需要使用QUdpSocket類的成員函數:
bool joinMulticastGroup(const QHostAddress & groupAddress)
現實生活中的qq群,拉在一起這種的,用的就是組播
綁定后加入某個組播,在組播內你發組成員就都能收到,其他組成員發你也會收到
//綁定
//udpSocket->bind(8888);//使用組播只能使用(綁定)ipv4的ip,不能使用任意的ip,所以這里注釋掉
udpSocket->bind(QHostAddress::AnyIPv4, 8888);//所以這里就要指定為ipv4
//加入某個組播 //廣播不需要加入的操作就直接能發能收
//組播地址是D類地址
udpSocket->joinMulticastGroup( QHostAddress("224.0.0.2") );//加入后,其他人就可以向這個ip以及綁定的端口發送數據了
//udpSocket->leaveMulticastGroup(QHostAddress("224.0.0.2")); //退出組播
上述代碼具體見《UDP》

1 #ifndef WIDGET_H 2 #define WIDGET_H 3 4 #include <QWidget> 5 #include <QUdpSocket> //UDP套接字 6 7 namespace Ui { 8 class Widget; 9 } 10 11 class Widget : public QWidget 12 { 13 Q_OBJECT 14 15 public: 16 explicit Widget(QWidget *parent = 0); 17 ~Widget(); 18 19 void dealMsg(); //槽函數,處理對方發過來的數據 20 21 private slots: 22 void on_buttonSend_clicked(); 23 24 private: 25 Ui::Widget *ui; 26 27 QUdpSocket *udpSocket; //UDP套接字 28 }; 29 30 #endif // WIDGET_H

1 #include "widget.h" 2 #include "ui_widget.h" 3 #include <QHostAddress> 4 5 Widget::Widget(QWidget *parent) : 6 QWidget(parent), 7 ui(new Ui::Widget) 8 { 9 ui->setupUi(this); 10 11 //分配空間,指定父對象 12 udpSocket = new QUdpSocket(this); 13 14 //綁定 15 //udpSocket->bind(8888); 16 udpSocket->bind(QHostAddress::AnyIPv4, 8888); 17 18 //加入某個組播 19 //組播地址是D類地址 20 udpSocket->joinMulticastGroup( QHostAddress("224.0.0.2") ); 21 //udpSocket->leaveMulticastGroup(); //退出組播 22 23 setWindowTitle("服務器端口為:8888"); 24 25 //當對方成功發送數據過來 26 //自動觸發 readyRead() 27 connect(udpSocket, &QUdpSocket::readyRead, this, &Widget::dealMsg); 28 } 29 30 void Widget::dealMsg() 31 { 32 //讀取對方發送的內容 33 char buf[1024] = {0}; 34 QHostAddress cliAddr; //對方地址 35 quint16 port; //對方端口 36 qint64 len = udpSocket->readDatagram(buf, sizeof(buf), &cliAddr, &port); 37 if(len > 0) 38 { 39 //格式化 [192.68.2.2:8888]aaaa 40 QString str = QString("[%1:%2] %3") 41 .arg(cliAddr.toString()) 42 .arg(port) 43 .arg(buf); 44 //給編輯區設置內容 45 ui->textEdit->setText(str); 46 } 47 48 49 } 50 51 Widget::~Widget() 52 { 53 delete ui; 54 } 55 56 //發送按鈕 57 void Widget::on_buttonSend_clicked() 58 { 59 //先獲取對方的IP和端口 60 QString ip = ui->lineEditIP->text(); 61 qint16 port = ui->lineEditPort->text().toInt(); 62 63 //獲取編輯區內容 64 QString str = ui->textEdit->toPlainText(); 65 66 //給指定的IP發送數據 67 udpSocket->writeDatagram(str.toUtf8(), QHostAddress(ip), port); 68 69 70 }
/*******************************************************************************************/
八、QTimer定時器的使用
QTimer 定時器對象,相對於那個事件的定時器好用多了。多個定時器創建多個對象即可
#include <QTimer> //定時器對象
定時器對象里面有個timeout的信號,當設置的定時時間到了的時候就會發出這樣的一個信號。
當然如果停止了這個定時器就不會發送。
myTimer = new QTimer(this);
i = 0;
connect(myTimer, &QTimer::timeout,
[=]()
{
i++;
ui->lcdNumber->display(i);
}
);
void Widget::on_buttonStart_clicked()
{
//啟動定時器
//時間間隔為100ms
//每隔100ms,定時器myTimer內部自動觸發timeout()信號
//如果定時器沒有激活,才啟動
if(myTimer->isActive() == false)
{
myTimer->start(100);
}
}
void Widget::on_buttonStop_clicked()
{
if(true == myTimer->isActive())
{
myTimer->stop();
i = 0;
}
}
上述代碼具體見《QTimer》

1 #ifndef WIDGET_H 2 #define WIDGET_H 3 4 #include <QWidget> 5 #include <QTimer> //定時器對象 6 7 namespace Ui { 8 class Widget; 9 } 10 11 class Widget : public QWidget 12 { 13 Q_OBJECT 14 15 public: 16 explicit Widget(QWidget *parent = 0); 17 ~Widget(); 18 19 private slots: 20 void on_buttonStart_clicked(); 21 22 void on_buttonStop_clicked(); 23 24 private: 25 Ui::Widget *ui; 26 27 QTimer *myTimer; //定時器對象 28 int i; 29 }; 30 31 #endif // WIDGET_H

1 #include "widget.h" 2 #include "ui_widget.h" 3 4 Widget::Widget(QWidget *parent) : 5 QWidget(parent), 6 ui(new Ui::Widget) 7 { 8 ui->setupUi(this); 9 10 myTimer = new QTimer(this); 11 i = 0; 12 13 connect(myTimer, &QTimer::timeout, 14 [=]() 15 { 16 i++; 17 ui->lcdNumber->display(i); 18 } 19 20 ); 21 22 23 } 24 25 Widget::~Widget() 26 { 27 delete ui; 28 } 29 30 void Widget::on_buttonStart_clicked() 31 { 32 //啟動定時器 33 //時間間隔為100ms 34 //每隔100ms,定時器myTimer自動觸發timeout() 35 //如果定時器沒有激活,才啟動 36 if(myTimer->isActive() == false) 37 { 38 myTimer->start(100); 39 } 40 41 } 42 43 void Widget::on_buttonStop_clicked() 44 { 45 if(true == myTimer->isActive()) 46 { 47 myTimer->stop(); 48 i = 0; 49 } 50 }
/*******************************************************************************************/
九、TCP傳文件流程圖
tcp中當兩包數據發送間隔很短的時候,接收的時候就會出現兩個包粘在一起的情況,也就是粘包。
比如簡單的解決方法是控制發送間隔,使用定時器延時(圖形界面不要用sleep除非開線程)讓不能粘在一起的包分開。
當然也可以通過在數據包中增加包頭,包長,包校驗,包尾等信息來保證每一包數據的准確性。
還有一種辦法是,不在乎粘的數據(比如文件數據)放在一個鏈接里,需要區分出來的數據(比如命令或者信息數據)放在另一個tcp鏈接里,
具體見圖《TCP傳文件流程圖》
/*******************************************************************************************/
十、TCP傳文件服務器
.......
//兩個按鈕都不能按,按鈕顏色變灰並且不能按
ui->buttonFile->setEnabled(false);
ui->buttonSend->setEnabled(false);
.......
//成功連接后,才能按選擇文件
ui->buttonFile->setEnabled(true);
.......
connect(&timer, &QTimer::timeout,
[=]()
{
//關閉定時器
timer.stop();
//發送文件
sendData();
}
);
.......
//選擇文件的按鈕
void ServerWidget::on_buttonFile_clicked()
{
QString filePath = QFileDialog::getOpenFileName(this, "open", "../");
if(false == filePath.isEmpty()) //如果選擇文件路徑有效
{
fileName.clear();
fileSize = 0;
//獲取文件信息
QFileInfo info(filePath);
fileName = info.fileName(); //獲取文件名字
fileSize = info.size(); //獲取文件大小
sendSize = 0; //發送文件的大小
//只讀方式打開文件
//指定文件的名字
file.setFileName(filePath);
//打開文件
bool isOk = file.open(QIODevice::ReadOnly);
if(false == isOk)
{
qDebug() << "只讀方式打開文件失敗 106";
}
//提示打開文件的路徑
ui->textEdit->append(filePath);
ui->buttonFile->setEnabled(false);
ui->buttonSend->setEnabled(true);
}
else
{
qDebug() << "選擇文件路徑出錯 118";
}
}
//發送文件按鈕
void ServerWidget::on_buttonSend_clicked()
{
ui->buttonSend->setEnabled(false);
//先發送文件頭信息 文件名##文件大小
QString head = QString("%1##%2").arg(fileName).arg(fileSize);
//發送頭部信息
qint64 len = tcpSocket->write( head.toUtf8() );
if(len > 0)//頭部信息發送成功
{
//發送真正的文件信息
//防止TCP黏包
//需要通過定時器延時 20 ms
timer.start(20);
}
else
{
qDebug() << "頭部信息發送失敗 142";
file.close();
ui->buttonFile->setEnabled(true);
ui->buttonSend->setEnabled(false);
}
}
void ServerWidget::sendData()
{
ui->textEdit->append("正在發送文件……");
qint64 len = 0;
do
{
//每次發送數據的大小
char buf[4*1024] = {0};
len = 0;
//往文件中讀數據
len = file.read(buf, sizeof(buf));
//發送數據,讀多少,發多少
len = tcpSocket->write(buf, len);
//發送的數據需要累積
sendSize += len;
}while(len > 0);
// //是否發送文件完畢
// if(sendSize == fileSize)
// {
// ui->textEdit->append("文件發送完畢");
// file.close();
// //把客戶端端口
// tcpSocket->disconnectFromHost();
// tcpSocket->close();
// }
}
/*******************************************************************************************/
十一、TCP傳文件客戶端
connect(tcpSocket, &QTcpSocket::readyRead,
[=]()
{
//取出接收的內容
QByteArray buf = tcpSocket->readAll();
if(true == isStart)
{//接收頭
isStart = false;
//解析頭部信息 QString buf = "hello##1024"
// QString str = "hello##1024#mike";
// str.section("##", 0, 0);//"##"分段符號,0第一段開始,0第一段結束,所以取出來是hello
//初始化
//文件名
fileName = QString(buf).section("##", 0, 0);
//文件大小
fileSize = QString(buf).section("##", 1, 1).toInt();
recvSize = 0; //已經接收文件大小
//打開文件
//關聯文件名字
file.setFileName(fileName);
//只寫方式方式,打開文件
bool isOk = file.open(QIODevice::WriteOnly);
if(false == isOk)
{
qDebug() << "WriteOnly error 49";
tcpSocket->disconnectFromHost(); //斷開連接
tcpSocket->close(); //關閉套接字
return; //如果打開文件失敗,中斷函數
}
//彈出對話框,顯示接收文件的信息
QString str = QString("接收的文件: [%1: %2kb]").arg(fileName).arg(fileSize/1024);
QMessageBox::information(this, "文件信息", str);
//設置進度條
ui->progressBar->setMinimum(0); //最小值
ui->progressBar->setMaximum(fileSize/1024); //最大值
ui->progressBar->setValue(0); //當前值
}
else //文件信息
{
qint64 len = file.write(buf);
if(len >0) //接收數據大於0
{
recvSize += len; //累計接收大小
qDebug() << len;
}
//更新進度條
ui->progressBar->setValue(recvSize/1024);
if(recvSize == fileSize) //文件接收完畢
{
//先給服務發送(接收文件完成的信息)
tcpSocket->write("file done");
QMessageBox::information(this, "完成", "文件接收完成");
file.close(); //關閉文件
//斷開連接
tcpSocket->disconnectFromHost();
tcpSocket->close();
}
}
}
);
/*******************************************************************************************/
十二、TCP傳文件進度條和黏包
//注意設置進度條使用除以1024的方法,不然太大,因為有可能文件太大,而進度條的那個值是int的
.......
//設置進度條
ui->progressBar->setMinimum(0); //最小值
ui->progressBar->setMaximum(fileSize/1024); //最大值//注意使用除以1024的方法,不然太大
ui->progressBar->setValue(0); //當前值
.......
//更新進度條
ui->progressBar->setValue(recvSize/1024);//注意使用除以1024的方法,不然太大
.......
上述代碼具體見《TCPFile》

1 #include "serverwidget.h" 2 #include <QApplication> 3 #include "clientwidget.h" 4 5 int main(int argc, char *argv[]) 6 { 7 QApplication a(argc, argv); 8 ServerWidget w; 9 w.show(); 10 11 ClientWidget w2; 12 w2.show(); 13 14 return a.exec(); 15 }

1 #ifndef SERVERWIDGET_H 2 #define SERVERWIDGET_H 3 4 #include <QWidget> 5 #include <QTcpServer> //監聽套接字 6 #include <QTcpSocket> //通信套接字 7 #include <QFile> 8 #include <QTimer> 9 10 namespace Ui { 11 class ServerWidget; 12 } 13 14 class ServerWidget : public QWidget 15 { 16 Q_OBJECT 17 18 public: 19 explicit ServerWidget(QWidget *parent = 0); 20 ~ServerWidget(); 21 22 void sendData(); //發送文件數據 23 24 private slots: 25 void on_buttonFile_clicked(); 26 27 void on_buttonSend_clicked(); 28 29 private: 30 Ui::ServerWidget *ui; 31 32 QTcpServer *tcpServer; //監聽套接字 33 QTcpSocket *tcpSocket; //通信套接字 34 35 QFile file; //文件對象 36 QString fileName; //文件名字 37 qint64 fileSize; //文件大小 38 qint64 sendSize; //已經發送文件的大小 39 40 QTimer timer; //定時器 41 42 43 44 45 46 }; 47 48 #endif // SERVERWIDGET_H

1 #include "serverwidget.h" 2 #include "ui_serverwidget.h" 3 #include <QFileDialog> 4 #include <QDebug> 5 #include <QFileInfo> 6 7 ServerWidget::ServerWidget(QWidget *parent) : 8 QWidget(parent), 9 ui(new Ui::ServerWidget) 10 { 11 ui->setupUi(this); 12 13 //監聽套接字 14 tcpServer = new QTcpServer(this); 15 16 //監聽 17 tcpServer->listen(QHostAddress::Any, 8888); 18 setWindowTitle("服務器端口為:8888"); 19 20 //兩個按鈕都不能按 21 ui->buttonFile->setEnabled(false); 22 ui->buttonSend->setEnabled(false); 23 24 //如果客戶端成功和服務器連接 25 //tcpServer會自動觸發 newConnection() 26 connect(tcpServer, &QTcpServer::newConnection, 27 [=]() 28 { 29 //取出建立好連接的套接字 30 tcpSocket = tcpServer->nextPendingConnection(); 31 //獲取對方的ip和端口 32 QString ip = tcpSocket->peerAddress().toString(); 33 quint16 port = tcpSocket->peerPort(); 34 35 QString str = QString("[%1:%2] 成功連接").arg(ip).arg(port); 36 ui->textEdit->setText(str); //顯示到編輯區 37 38 //成功連接后,才能按選擇文件 39 ui->buttonFile->setEnabled(true); 40 41 connect(tcpSocket, &QTcpSocket::readyRead, 42 [=]() 43 { 44 //取客戶端的信息 45 QByteArray buf = tcpSocket->readAll(); 46 if(QString(buf) == "file done") 47 {//文件接收完畢 48 ui->textEdit->append("文件發送完畢"); 49 file.close(); 50 51 //斷開客戶端端口 52 tcpSocket->disconnectFromHost(); 53 tcpSocket->close(); 54 } 55 56 } 57 58 ); 59 60 } 61 ); 62 63 connect(&timer, &QTimer::timeout, 64 [=]() 65 { 66 //關閉定時器 67 timer.stop(); 68 69 //發送文件 70 sendData(); 71 } 72 73 ); 74 75 } 76 77 ServerWidget::~ServerWidget() 78 { 79 delete ui; 80 } 81 82 //選擇文件的按鈕 83 void ServerWidget::on_buttonFile_clicked() 84 { 85 QString filePath = QFileDialog::getOpenFileName(this, "open", "../"); 86 if(false == filePath.isEmpty()) //如果選擇文件路徑有效 87 { 88 fileName.clear(); 89 fileSize = 0; 90 91 //獲取文件信息 92 QFileInfo info(filePath); 93 fileName = info.fileName(); //獲取文件名字 94 fileSize = info.size(); //獲取文件大小 95 96 sendSize = 0; //發送文件的大小 97 98 //只讀方式打開文件 99 //指定文件的名字 100 file.setFileName(filePath); 101 102 //打開文件 103 bool isOk = file.open(QIODevice::ReadOnly); 104 if(false == isOk) 105 { 106 qDebug() << "只讀方式打開文件失敗 106"; 107 } 108 109 //提示打開文件的路徑 110 ui->textEdit->append(filePath); 111 112 ui->buttonFile->setEnabled(false); 113 ui->buttonSend->setEnabled(true); 114 115 } 116 else 117 { 118 qDebug() << "選擇文件路徑出錯 118"; 119 } 120 121 } 122 //發送文件按鈕 123 void ServerWidget::on_buttonSend_clicked() 124 { 125 ui->buttonSend->setEnabled(false); 126 127 //先發送文件頭信息 文件名##文件大小 128 QString head = QString("%1##%2").arg(fileName).arg(fileSize); 129 //發送頭部信息 130 qint64 len = tcpSocket->write( head.toUtf8() ); 131 if(len > 0)//頭部信息發送成功 132 { 133 //發送真正的文件信息 134 //防止TCP黏包 135 //需要通過定時器延時 20 ms 136 timer.start(20); 137 138 139 } 140 else 141 { 142 qDebug() << "頭部信息發送失敗 142"; 143 file.close(); 144 ui->buttonFile->setEnabled(true); 145 ui->buttonSend->setEnabled(false); 146 } 147 } 148 149 void ServerWidget::sendData() 150 { 151 ui->textEdit->append("正在發送文件……"); 152 qint64 len = 0; 153 do 154 { 155 //每次發送數據的大小 156 char buf[4*1024] = {0}; 157 len = 0; 158 159 //往文件中讀數據 160 len = file.read(buf, sizeof(buf)); 161 //發送數據,讀多少,發多少 162 len = tcpSocket->write(buf, len); 163 164 //發送的數據需要累積 165 sendSize += len; 166 167 }while(len > 0); 168 169 170 // //是否發送文件完畢 171 // if(sendSize == fileSize) 172 // { 173 // ui->textEdit->append("文件發送完畢"); 174 // file.close(); 175 176 // //把客戶端端口 177 // tcpSocket->disconnectFromHost(); 178 // tcpSocket->close(); 179 // } 180 181 182 }

1 #ifndef CLIENTWIDGET_H 2 #define CLIENTWIDGET_H 3 4 #include <QWidget> 5 #include <QTcpSocket> 6 #include <QFile> 7 8 namespace Ui { 9 class ClientWidget; 10 } 11 12 class ClientWidget : public QWidget 13 { 14 Q_OBJECT 15 16 public: 17 explicit ClientWidget(QWidget *parent = 0); 18 ~ClientWidget(); 19 20 private slots: 21 void on_buttonConnect_clicked(); 22 23 private: 24 Ui::ClientWidget *ui; 25 26 QTcpSocket *tcpSocket; 27 28 QFile file; //文件對象 29 QString fileName; //文件名字 30 qint64 fileSize; //文件大小 31 qint64 recvSize; //已經接收文件的大小 32 33 bool isStart; //標志位,是否為頭部信息 34 }; 35 36 #endif // CLIENTWIDGET_H

1 #include "clientwidget.h" 2 #include "ui_clientwidget.h" 3 #include <QDebug> 4 #include <QMessageBox> 5 #include <QHostAddress> 6 7 ClientWidget::ClientWidget(QWidget *parent) : 8 QWidget(parent), 9 ui(new Ui::ClientWidget) 10 { 11 ui->setupUi(this); 12 13 tcpSocket = new QTcpSocket(this); 14 15 isStart = true; 16 17 ui->progressBar->setValue(0); //當前值 18 19 setWindowTitle("客戶端"); 20 21 connect(tcpSocket, &QTcpSocket::readyRead, 22 [=]() 23 { 24 //取出接收的內容 25 QByteArray buf = tcpSocket->readAll(); 26 27 if(true == isStart) 28 {//接收頭 29 isStart = false; 30 //解析頭部信息 QString buf = "hello##1024" 31 // QString str = "hello##1024#mike"; 32 // str.section("##", 0, 0) 33 34 //初始化 35 //文件名 36 fileName = QString(buf).section("##", 0, 0); 37 //文件大小 38 fileSize = QString(buf).section("##", 1, 1).toInt(); 39 recvSize = 0; //已經接收文件大小 40 41 //打開文件 42 //關聯文件名字 43 file.setFileName(fileName); 44 45 //只寫方式方式,打開文件 46 bool isOk = file.open(QIODevice::WriteOnly); 47 if(false == isOk) 48 { 49 qDebug() << "WriteOnly error 49"; 50 51 tcpSocket->disconnectFromHost(); //斷開連接 52 tcpSocket->close(); //關閉套接字 53 54 return; //如果打開文件失敗,中斷函數 55 } 56 57 //彈出對話框,顯示接收文件的信息 58 QString str = QString("接收的文件: [%1: %2kb]").arg(fileName).arg(fileSize/1024); 59 QMessageBox::information(this, "文件信息", str); 60 61 //設置進度條 62 ui->progressBar->setMinimum(0); //最小值 63 ui->progressBar->setMaximum(fileSize/1024); //最大值 64 ui->progressBar->setValue(0); //當前值 65 66 } 67 else //文件信息 68 { 69 qint64 len = file.write(buf); 70 if(len >0) //接收數據大於0 71 { 72 recvSize += len; //累計接收大小 73 qDebug() << len; 74 } 75 76 //更新進度條 77 ui->progressBar->setValue(recvSize/1024); 78 79 if(recvSize == fileSize) //文件接收完畢 80 { 81 82 //先給服務發送(接收文件完成的信息) 83 tcpSocket->write("file done"); 84 85 QMessageBox::information(this, "完成", "文件接收完成"); 86 file.close(); //關閉文件 87 //斷開連接 88 tcpSocket->disconnectFromHost(); 89 tcpSocket->close(); 90 91 } 92 } 93 94 } 95 96 ); 97 98 } 99 100 ClientWidget::~ClientWidget() 101 { 102 delete ui; 103 } 104 105 void ClientWidget::on_buttonConnect_clicked() 106 { 107 //獲取服務器的ip和端口 108 QString ip = ui->lineEditIP->text(); 109 quint16 port = ui->lineEditPort->text().toInt(); 110 111 //主動和服務器連接 112 tcpSocket->connectToHost(QHostAddress(ip), port); 113 114 isStart = true; 115 116 //設置進度條 117 ui->progressBar->setValue(0); 118 }