Qt在实际的使用Tcp通信中发现,发送端与接收端并不是一一对应的,会出现发送多次只相应一次的情况,且发送端速度远超接收端时会引起程序崩溃,小文件不存在这样的问题,可忽略,大文件发送之所以出现,其问题的根本点在于Tcp发送与接收端不一致引起的粘包。
因此可根据实际情况制定协议,使用一问一答的方式进行数据传输,牺牲效率以满足稳定性和安全可靠性。
客户端代码如下
TcpClientPro::TcpClientPro(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
initClient();
}
TcpClientPro::~TcpClientPro()
{
qDebug() << "~ClientPro()----------------------------";
if (m_client->state() == QAbstractSocket::ConnectedState) {
//如果使用disconnectFromHost()不会重置套接字,isValid还是会为true
m_client->abort();
}
}
void TcpClientPro::initClient() {
//创建client对象
m_client = new QTcpSocket(this);
ui.selectBtn->setEnabled(false);
ui.sendBtn->setEnabled(false);
}
void TcpClientPro::on_connectBtn_clicked() {
if (m_client->state() == QAbstractSocket::ConnectedState) {
//如果使用disconnectFromHost()不会重置套接字,isValid还是会为true
m_client->abort();
}
else if (m_client->state() == QAbstractSocket::UnconnectedState) {
//从界面上读取ip和端口
const QHostAddress address = QHostAddress(ui.addressEt->text());
const unsigned short port = ui.portEt->text().toUShort();
//连接服务器
m_client->connectToHost(address, port);
}
else {
}
connect(m_client, &QTcpSocket::connected, this, &TcpClientPro::connectedSlot);
connect(m_client, &QTcpSocket::disconnected, this, &TcpClientPro::disconnectedSlot);
connect(m_client, &QTcpSocket::readyRead, this, &TcpClientPro::readyReadSlot);
}
void TcpClientPro::on_selectBtn_clicked() {
QString filePath = QFileDialog::getOpenFileName(this, "open", "../");
if (!filePath.isEmpty()) {
m_fileName = "";
QFileInfo info(filePath);
m_fileName = info.fileName();
qDebug() << "m_fileName----------" << m_fileName;
m_file.setFileName(filePath);
if (m_file.open(QIODevice::ReadOnly)) {
FileDate fdata;
int size = m_file.size();
if (size == 0) {
m_file.close();
return;
}
m_lastSize = size % sizeof(fdata.data);
m_readNum = size / sizeof(fdata.data) + (m_lastSize > 0 ? 1 : 0);
QByteArray array1 = m_fileName.toStdString().c_str();//
strncpy(fdata.fileName, array1.data(), sizeof(fdata.fileName));//
//fdata.FileName = m_fileName;
fdata.readCnt = m_readNum;
fdata.lastSize = m_lastSize;
fdata.type = TransFileInfo;
qDebug() << "sizeof(fdata)-----------------------" << sizeof(fdata);
QByteArray array;
array.resize(sizeof(fdata));
memcpy(array.data(), &fdata, sizeof(fdata));//把结构体存入数组
m_client->write(array);
m_client->waitForBytesWritten();
}
}
}
void TcpClientPro::on_sendBtn_clicked() {
}
void TcpClientPro::connectedSlot() {
ui.connectBtn->setText("Disconnect");
ui.selectBtn->setEnabled(true);
ui.addressEt->setEnabled(false);
ui.portEt->setEnabled(false);
}
void TcpClientPro::disconnectedSlot() {
ui.connectBtn->setText("Connect");
ui.addressEt->setEnabled(true);
ui.portEt->setEnabled(true);
ui.selectBtn->setEnabled(false);
ui.sendBtn->setEnabled(false);
}
void TcpClientPro::readyReadSlot() {
if (m_client->bytesAvailable() <= 0)
return;
FileDate fdata;
QByteArray array;
array = m_client->readAll();
memcpy(&fdata, array.data(), sizeof(fdata));//转化到结构体
if (fdata.state == CorrectState) {
if (fdata.type == TransFileInfoDone) {
m_readIndex = 0;
readFileData();
}
else if (fdata.type == TransFileData) {
readFileData();
}
if (m_readIndex == m_readNum) {
m_file.close();
}
}
else {
m_file.close();
}
}
void TcpClientPro::readFileData() {
QByteArray r_array;
FileDate r_fdata;
r_array.resize(sizeof(r_fdata));
r_fdata.type = TransFileData;
m_file.read(r_fdata.data, sizeof(r_fdata.data));
memcpy(r_array.data(), &r_fdata, sizeof(r_fdata));//把结构体存入数组
m_client->write(r_array);
m_client->waitForBytesWritten(1000);
m_readIndex++;
qDebug() << "TcpClientPro::readFileData----" << m_readIndex;
}
服务端代码如下:
```go
TcpServerPro::TcpServerPro(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
setWindowTitle("Server");
iniServer();
}
TcpServerPro::~TcpServerPro()
{
closeServer();
if (m_socket != NULL) {
delete m_socket;
m_socket = NULL;
}
}
void TcpServerPro::iniServer() {
m_fileName = "";
m_readCnt = 0;
m_lastSize = 0;
m_socket = NULL;
ui.clearBtn->setEnabled(false);
m_server = new QTcpServer(this);
}
void TcpServerPro::closeServer() {
m_server->close();
if (m_socket == NULL) {
return;
}
//断开与客户端的连接
if (m_socket->state() == QAbstractSocket::ConnectedState) {
m_socket->disconnectFromHost();
if (m_socket->state() != QAbstractSocket::UnconnectedState) {
m_socket->abort();
}
}
}
void TcpServerPro::on_listenBtn_clicked() {
if (m_server->isListening()) {
closeServer();
//关闭server后恢复界面状态
ui.listenBtn->setText("Listen");
ui.addressEt->setEnabled(true);
ui.portEt->setEnabled(true);
}
else {
//可以使用 QHostAddress::Any 监听所有地址的对应端口
const QString address_text = ui.addressEt->text();
const unsigned short port = ui.portEt->text().toUShort();
const QHostAddress address = (address_text == "Any")
? QHostAddress::Any
: QHostAddress(address_text);
//开始监听,并判断是否成功
if (m_server->listen(address, port)) {
//连接成功就修改界面按钮提示,以及地址栏不可编辑
ui.listenBtn->setText("Close");
ui.addressEt->setEnabled(false);
ui.portEt->setEnabled(false);
}
connect(m_server, &QTcpServer::newConnection, this, &TcpServerPro::newConnectionSlot);
}
}
void TcpServerPro::on_clearBtn_clicked() {
ui.receiveEt->clear();
}
void TcpServerPro::newConnectionSlot() {
if (m_server->hasPendingConnections())
{
//nextPendingConnection返回下一个挂起的连接作为已连接的QTcpSocket对象
//套接字是作为服务器的子级创建的,这意味着销毁QTcpServer对象时会自动删除该套接字。
m_socket = m_server->nextPendingConnection();
ui.receiveEt->append("connected.........");
connect(m_socket, &QTcpSocket::readyRead, this, &TcpServerPro::readyReadSlot);
ui.clearBtn->setEnabled(true);
}
}
void TcpServerPro::readyReadSlot() {
if (m_socket->bytesAvailable() <= 0)
return;
FileDate fdata;
QByteArray array;
//array.resize(sizeof(fdata));
array = m_socket->readAll();
memcpy(&fdata, array.data(), sizeof(fdata));//转化到结构体
if (fdata.type == TransFileInfo) {
parseFileData(fdata);
}
else if (fdata.type == TransFileData) {
writeFile(fdata);
}
}
void TcpServerPro::writeFile(FileDate& fdata) {
m_readIndex++;
ui.progressBar->setValue(m_readIndex);
if (m_readIndex == m_readCnt)//最后一个包
{
m_file.write(fdata.data, m_lastSize);
m_readIndex = 0;
m_file.close();//传输完毕
qDebug() << "transfer over------------------------";
return;
}
else {
m_file.write(fdata.data, sizeof(fdata.data));
QByteArray array1;
array1.resize(sizeof(fdata));
FileDate buf;
buf.state = CorrectState;
buf.type = TransFileData;
memcpy(array1.data(), &buf, sizeof(buf));
qDebug() << "transfer progress-------" << double(1.0*m_readIndex / m_readCnt) * 100<<"%";
m_socket->write(array1);
m_socket->waitForBytesWritten(1000);
}
}
void TcpServerPro::parseFileData(FileDate &fdata) {
QString dirname = QFileDialog::getExistingDirectory(this, "SelectDirectory", "/");
QString filename(fdata.fileName);
//qDebug() << "dirname: " << dirname;
QString filePath = dirname.append("/").append(filename);
QFileInfo info(filePath);
m_fileName = info.fileName();
m_readCnt = fdata.readCnt;
m_lastSize = fdata.lastSize;
qDebug() << "TcpServerPro::parseFileData m_readCnt: " << m_readCnt << " m_lastSize: " << m_lastSize;
m_file.setFileName(filePath);
FileDate fdata1;
if (m_file.open(QIODevice::WriteOnly)) {
fdata1.type = TransFileInfoDone;
fdata1.state = CorrectState;
ui.progressBar->setMaximum(m_readCnt);
ui.progressBar->setValue(0);
}
else {
fdata1.type = TransFileData;
fdata1.state = ErrorState;
}
m_readIndex = 0;
QByteArray array1;
array1.resize(sizeof(fdata1));
memcpy(array1.data(), &fdata1 , sizeof(fdata1));
m_socket->write(array1);
m_socket->waitForBytesWritten();
}
动态显示效果如下: