文章目錄
1.Linux下的TCP通信過程
2.Qt下的TCP通信過程
注意,服務器有兩個套接字:QTcpServer
、QTcpSocket
,即監聽套接字和通信套接字.
3. TCP通信
TCP服務器
TextEdit設置只讀:
serverwidget.h
#ifndef SERVERWIDGET_H
#define SERVERWIDGET_H
#include <QWidget>
#include <QTcpServer> //監聽套接字
#include <QTcpSocket> //通信套接字
namespace Ui {
class ServerWidget;
}
class ServerWidget : public QWidget
{
Q_OBJECT
public:
explicit ServerWidget(QWidget *parent = nullptr);
~ServerWidget();
private slots:
void on_buttonSent_clicked();
void on_buttonClose_clicked();
private:
Ui::ServerWidget *ui;
QTcpServer *tcpServer; //監聽套接字
QTcpSocket *tcpSocket; //通信套接字
};
#endif // SERVERWIDGET_H
serverwidget.cpp
#include "serverwidget.h"
#include "ui_serverwidget.h"
ServerWidget::ServerWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ServerWidget)
{
ui->setupUi(this);
tcpServer = NULL;
tcpSocket = NULL;
setWindowTitle("服務器:8888");
//監聽套接字,指定父對象,讓其自動回收空間
tcpServer = new QTcpServer(this);
tcpServer->listen(QHostAddress::Any,8888);//綁定網卡所有IP,端口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);
//注意不要放錯位置
connect(tcpSocket,&QTcpSocket::readyRead,
[=]()
{
//從通信套接字中取出內容
QByteArray array = tcpSocket->readAll();
ui->textEditRead->append(array);
}
);
}
);
}
ServerWidget::~ServerWidget()
{
delete ui;
}
void ServerWidget::on_buttonSent_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客戶端
clientwidget.h
#ifndef CLIENTWIDGET_H
#define CLIENTWIDGET_H
#include <QWidget>
#include <QTcpSocket> //通信套接字
namespace Ui {
class ClientWidget;
}
class ClientWidget : public QWidget
{
Q_OBJECT
public:
explicit ClientWidget(QWidget *parent = nullptr);
~ClientWidget();
private slots:
void on_buttonConnect_clicked();
void on_buttonSend_clicked();
void on_buttonClose_clicked();
private:
Ui::ClientWidget *ui;
QTcpSocket *tcpSocket;
};
#endif // CLIENTWIDGET_H
clientwidget.cpp
#include "clientwidget.h"
#include "ui_clientwidget.h"
#include <QHostAddress>
ClientWidget::ClientWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ClientWidget)
{
ui->setupUi(this);
setWindowTitle("客戶端");
tcpSocket = NULL;
//分配空間,指定父對象
tcpSocket = new QTcpSocket(this);
connect(tcpSocket,&QTcpSocket::connected,
[=]()
{
ui->textEditRead->setText("成功和服務器建立連接!");
});
connect(tcpSocket,&QTcpSocket::readyRead,
[=]()
{
//獲取對方發送的內容
QByteArray array = tcpSocket->readAll();
//追加到編輯區
ui->textEditRead->append(array);
}
);
}
ClientWidget::~ClientWidget()
{
delete ui;
}
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();
}
4. UDP通信
UDP通信過程
Linux下的UDP
Qt下的UDP
UDP與TCP的區別
- UDP像寫信,只要知道地址就可以發
- TCP像打電話,只有兩人同時在線才能通信
UDP 文本發送
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QUdpSocket> //UDP套接字
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
void dealMsg();//槽函數,處理對方發過來的數據
private slots:
void on_buttonSend_clicked();
private:
Ui::Widget *ui;
QUdpSocket *udpSocket;//UDP套接字
};
#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("服務器端口爲:8888");
//分配空間,指定父對象
udpSocket = new QUdpSocket(this);
//綁定
udpSocket->bind(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);
}
}
Widget::~Widget()
{
delete ui;
}
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多播組播
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("服務器端口爲:8888");
//分配空間,指定父對象
udpSocket = new QUdpSocket(this);
//綁定
//udpSocket->bind(8888);
//注意,羣播時綁定的是IPv4
udpSocket->bind(QHostAddress::AnyIPv4,8888);
//加入某個組播
//組播地址必須是D類地址
udpSocket->joinMulticastGroup(QHostAddress("224.0.0.2"));
//udpSocket->leaveMulticastGroup(); //退出組播
//當對方成功發送數據過來
//自動觸發 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);
}
}
Widget::~Widget()
{
delete ui;
}
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);
}
5. QTimer定時器
5.TCP傳文件
務必注意TCP的黏包問題,通常通過發送頭部數據
+延時
來處理!!!
main.cpp
#include "serverwidget.h"
#include <QApplication>
#include "clientwidget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ServerWidget w;
w.show();
ClientWidget w2;
w2.show();
return a.exec();
}
服務器
serverwidget.h
#ifndef SERVERWIDGET_H
#define SERVERWIDGET_H
#include <QWidget>
#include <QTcpServer> //監聽套接字
#include <QTcpSocket> //通信套接字
#include <QFile>
#include <QTimer>
namespace Ui {
class ServerWidget;
}
class ServerWidget : public QWidget
{
Q_OBJECT
public:
explicit ServerWidget(QWidget *parent = nullptr);
~ServerWidget();
void sendData();//發送文件數據
private slots:
void on_buttonFile_clicked();
void on_buttonSend_clicked();
private:
Ui::ServerWidget *ui;
QTcpServer *tcpServer;//監聽套接字
QTcpSocket *tcpSocket;//通信套接字
QFile file;//文件對象
QString fileName;//文件名字
qint64 fileSize;//文件大小
qint64 sendSize;//已發送的大小
QTimer timer;//定時器
};
#endif // SERVERWIDGET_H
serverwidget.cpp
#include "serverwidget.h"
#include "ui_serverwidget.h"
#include <QFileDialog>
#include <QDebug>
#include <QFileInfo>
ServerWidget::ServerWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ServerWidget)
{
ui->setupUi(this);
//監聽套接字
tcpServer = new QTcpServer(this);
//監聽
tcpServer->listen(QHostAddress::Any,8888);
setWindowTitle("服務器端口爲:8888");
//兩個按鈕都不能按
ui->buttonFile->setEnabled(false);
ui->buttonSend->setEnabled(false);
//如果客戶端成功和服務器連接
connect(tcpServer,&QTcpServer::newConnection,
[=]()
{
//取出建立好連接的套接字
tcpSocket = tcpServer->nextPendingConnection();
//獲取對方的IP和端口
QString ip = tcpSocket->peerAddress().toString();
quint16 port = tcpSocket->peerPort();
QString str = QString("[%1:%2] 成功連接").arg(ip).arg(port);
ui->textEdit->setText(str);//顯示到編輯區
//成功連接後,才能按按鈕選擇文件
ui->buttonFile->setEnabled(true);
});
connect(&timer,&QTimer::timeout,
[=]()
{
//關閉定時器
timer.stop();
//發送文件
sendData();
}
);
}
ServerWidget::~ServerWidget()
{
delete ui;
}
//選擇文件的按鈕
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() << "只讀方式打開文件失敗";
}
//提示打開文件路徑
ui->textEdit->append(filePath);
ui->buttonFile->setEnabled(false);
ui->buttonSend->setEnabled(true);
}
else {
qDebug() << "選擇文件路徑出錯!";
}
}
//發送文件按鍵
void ServerWidget::on_buttonSend_clicked()
{
//先發送文件頭信息
QString head = QString("%1##%2").arg(fileName).arg(fileSize);
qint64 len = tcpSocket->write(head.toUtf8());
if(len > 0)//說明頭部信息發送成功
{
//發送真正的文件信息
//防止TCP黏包文件
//需要通過定時器延時20ms
timer.start(20);//加延時,防止黏包
}
else {
qDebug()<<"頭部信息發送失敗!";
file.close();
ui->buttonFile->setEnabled(true);
ui->buttonSend->setEnabled(false);
}
}
void ServerWidget::sendData()
{
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);//如果len≤0,發送完畢
//文件是否發送完畢
if(sendSize == fileSize)
{
ui->textEdit->append("文件發送完畢!");
file.close();
//把客戶端端口關閉
tcpSocket->disconnectFromHost();
tcpSocket->close();
}
}
客戶端
clientwidget.h
#ifndef CLIENTWIDGET_H
#define CLIENTWIDGET_H
#include <QWidget>
#include <QTcpSocket>
#include <QFile>
namespace Ui {
class ClientWidget;
}
class ClientWidget : public QWidget
{
Q_OBJECT
public:
explicit ClientWidget(QWidget *parent = nullptr);
~ClientWidget();
private slots:
void on_buttonConnect_clicked();
private:
Ui::ClientWidget *ui;
QTcpSocket *tcpSocket;
QFile file;//文件對象
QString fileName;//文件名字
qint64 fileSize;//文件大小
qint64 recvSize;//已接收文件的大小
bool isStart;
};
#endif // CLIENTWIDGET_H
clientwidget.cpp
#include "clientwidget.h"
#include "ui_clientwidget.h"
#include <QDebug>
#include <QMessageBox>
#include <QHostAddress>
ClientWidget::ClientWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ClientWidget)
{
ui->setupUi(this);
ui->progressBar->setValue(0);//設置進度條當前值爲0
tcpSocket = new QTcpSocket(this);
isStart = true;
connect(tcpSocket,&QTcpSocket::readyRead,
[=]()
{
//取出接收的內容
QByteArray buf = tcpSocket->readAll();
if(true == isStart)//接收頭
{
isStart = false;
//解析頭部信息 QString buf = "hello##1024"
//QString str = "hello##1024#mike";
//取hello: str.section("##",0,0)
//取1024:str.section("##",1,1).toInt()
//取mike:str.section("##",2,2)
//初始化
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!!!";
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;//累計接收大小
QString str = QString::number(recvSize);
tcpSocket->write(str.toUtf8().data());
qDebug()<<"str = "<<str;
}
//更新進度條
ui->progressBar->setValue(recvSize/1024);
if(recvSize == fileSize)//文件接收完畢
{
//先給服務器發送(接收文件完成的信息)
tcpSocket->write("file done");
file.close(); //關閉文件
QMessageBox::information(this,"完成","文件接收完成!");
//斷開連接
tcpSocket->disconnectFromHost();
tcpSocket->close();
}
}
});
}
ClientWidget::~ClientWidget()
{
delete ui;
}
void ClientWidget::on_buttonConnect_clicked()
{
//獲取服務器IP和端口
QString ip = ui->lineEditIP->text();
quint16 port = ui->lineEditPort->text().toInt();
tcpSocket->connectToHost(QHostAddress(ip),port);
}
小技巧:
- 有時加載完模塊不會立即生效,這時可以點下
錘子
,只編譯不運行!或者重新打開項目即可~