QT中实现TCP通信
1.实现原理
在Qt中,要使用TCP通信必须要在.pro文件中加入network模块。在客户端部分,主要是用到QTcpsocket类创建socket对象去连接服务器端口,连接成功后即可正常传输数据。在判断连接状态时,主要会用到connected()、disconnected()、stateChanged()等相关信号 ;在数据读写的过程中,主要会用到类QIODevice中的bytesWritten()、readyRead()等信号。
在创建socket对象后, 可以绑定信号后直接连接服务器。
QTcpsocket *socket = new QTcpsocket(this);
connect(socket, SIGNAL(readyRead()), this, SLOT(SlotReadyRead()));
connect(socket, SIGNAL(bytesWritten(qint64)), this, SLOT(SlotBytesWritten(qint64)));
QHostAddress addr = g_controller_address;
socket->abort(); //这里是用来复位
socket->connectToHost(addr, 5566);
if(socket->waitForConnected(500))
{
//作连接成功的处理
}
else
{
//作连接失败的处理
}2. 自己封装数据进行传输
在TCP传输的过程中,可以自己指定对应的数据包进行传输。
void NetClient::SlotSendCommand(qint64 cmd)
{
m_total_bytes = 0;
m_cur_command = cmd;
QByteArray m_in_out_block;
QDataStream send_cmd(&m_in_out_block, QIODevice::WriteOnly);
send_cmd.setVersion(QDataStream::Qt_4_5);
//这里进行数据包的定义
send_cmd<<qint64(0)<<qint64(0)<<qint64(0)<<QString("");
m_total_bytes += m_in_out_block.size();
semd_cmd.device()->seek(0); //将设备的指针位置置为0,方便后面重新给值
//打包数据
send_cmd<<cmd<<m_total_bytes<<qint64(m_in_out_block.size()-sizeof(qint64)*3)<<QString("test");
//发送数据
socket->write(m_in_out_block);
//清空缓冲区
m_in_out_block.resize(0);
}3.用TCP传输文件
在传送文件的过程中,要用到几个变量用来统计传送的进度,包括:文件句柄、传送的总大小、文件名、已发送数据的大小、待发送数据的大小、每次发送的最大数据大小等。
bool IsHaveFile(const QString &file_path, const QString &file_name)
{
QStringList file_list; //将找到的文件存放于此
if(file_path.isEmpty() || file_name.isEmpty())
return false;
file_list.clear();
QDir dir;
QStringList filters;
filters<<file_name; //这是过滤条件,可以添加多个选项,这里指定文件名字
dir.setPath(file_path);
dir.setNameFilters(filters);
QDirIterator iter(dir, QDirIterator::Subdirectories); //目录递归迭代器
while(iter.hasNext())
{
iter.next();
QFileInfo info = iter.fileInfo();
if(info.isFile() && (info.size()>0)) //若是文件且文件不为空
file_list.append(info.fileName());
}
//判断文件file_list中文件的个数
if(file_list.isEmpty())
return false;
else
return true;
}void NetClient::SendFile()
{
QString file_path("./config.xml");
m_local_file = new QFile(file_path);
if(!m_local_file->open(QFile::ReadOnly))
{
//打开失败的处理
}
m_cur_command = _TCP_COMMIT_FILE;
m_total_bytes = m_local_file->size();
QDataStream send_out(&m_in_out_block, QIODevice::WriteOnly);
send_out.setVersion(QDataStream::Qt:4_5);
m_file_name = file_path.right(file_path.size() - file_path.lastIndexOf('/')-1);
//设置文件头的结构
send_out<<qint64(0)<<qint64(0)<<qint64(0)<<QString(m_file_name);
m_total_bytes += m_in_out_block.size();
send_out.device()->seek(0)
//打包数据
send_out<<qint64(_TCP_COMMIT_FILE_)<<m_total_bytes<<qint64(m_in_out_block.size()-sizeof(qint64)*3)
m_bytes_to_write = m_total_bytes - socket->write(m_in_out_block);
m_in_out_block.resize(0);
//上面已经将文件头发送,下面可以来显示进度条相关的操作
}在向socket中写入数据时,会触发bytesWritten()信号,这里为了保证是写文件触发的,在其槽函数中做如下处理:
void NetClient::SlotBytesWritten(qint64 numBytes)
{
if(m_cur_command == _TCP_COMMIT_FILE_)
SlotSendUpdateProgress(numBytes); //直接处理接下来的文件发送过程
else
return;
}下面是文件内容部分的发送过程:
void NetClient::SlotSendUpdateProgress(qint64 numBytes)
{
m_bytes_written += numBytes;
if(m_bytes_to_write > 0)
{
m_in_out_block = m_local_file->read(qMin(m_bytes_to_write, m_pay_load_size)); //m_pay_load_size是最大传输负载
m_bytes_to_write -= socket->write(m_in_out_block);
m_in_out_block.resize(0);
}
else
m_local_file->close(); //关闭发送的配置文件
//这里可以设置进度条的进度
m_progress_bar->setValue(m_bytes_written);
if(m_bytes_written == m_total_bytes) //传输完成
{
m_local_file->close();
//这里可以设置传输完成的相关操作
m_bytes_written = 0;
}
}4. 文件的接收
在TCP数据传输文件时,如何封装发送的,接收时就如何解包,数据格式一定要一致,否则就会出错。
void NetClient::SlotReadyRead()
{
QDataStream recv_in(socket);
recv_in.setVersion(QDataStream::Qt_4_5);
//若已接收的字节小于24字节
if(m_bytes_received <= sizeof(qint64)*3)
{
if((socket->bytesAvailable()>=sizeof(qint64)*3 && (m_file_name_size == 0))
{
//接收相应的包字段
recv_in>>command>>m_total_bytes>>m_file_name_size;
m_bytes_received += sizeof(qint64)*3;
if(command == _TCP_ACCEPT_FILE_)
//可以用来设置接收文件的进度
}
if(socket->bytesAvailable()>=m_file_name_size) && (m_file_name_size != 0))
{
//接收文件名及其它相关命令
recv_in>>m_file_name;
m_bytes_received += m_file_name_size;
if(command == _TCP_ACCEPT_FILE_) //返回的是文件
{
m_local_file = new QFile("./" + m_file_name); //文件句柄
if(!m_local_file->open(QFile::WriteOnly)
//做相应的处理
}
}
}
//若已接收的数据小于总数据,则写入文件
if(m_bytes_received < m_total_bytes)
{
m_bytes_received += socket->bytesAvailable();
m_in_out_block = socket->readAll(); //读取缓冲区的数据
m_local_file->write(m_in_out_block);
m_in_out_block.resize(0);
//下面可以设置接收进度条的数据
}
//当接收数据完成时
if(m_bytes_received == m_total_bytes)
{
m_local_file->close();
//设置接收完成的相关信息
m_bytes_received = 0;
m_file_name_size = 0;
m_total_bytes = 0;
command = 0;
m_file_name = "";
}
}在TCP通信过程中,传输数据一定要定义好格式,以什么样的格式发送就以什么样的格式接收,否则就会出现数据错误。
5.用大量数据的接收方式
在传输大量数据时,发送要定义特定的格式,接收时也要注意相应的粘包问题
void NetClient::SlotReadyDataCar()
{
//socket缓冲区没有数据直接返回
if(data_socket->bytesAvailable()<=0)
return;
QByteArray buffer = data_socket->readAll();
m_buffer_car.append(buffer); //将读取的数据放入包缓冲区
qint64 tune_cmd;
qint64 struct_count;
qint64 total_bytes;
int total_length = m_buffer_car.size();
while(total_length)
{
QDataStream recv_data(m_buffer_car);
recv_data.setVersion(QDataStream::Qt_4_5);
if(total_length<sizeof(qint64)*3)
break; //不够包头长度下次解析
recv_data>>tune_cmd>>struct_count>>total_bytes;
if(total_length < total_bytes + sizeof(qint64)*3)
break; //不够数据长度下次解析
if(tune_cmd ==_TCP_TUNE_CAR_)
{
for(int i=0; i<struct_count; ++i)
{
recv_data>>st_car_monitor_info; //这里是接收的数据结构体信息
//这里可以对数据进行处理
}
}
//一次处理结束后,缓存多余的数据
buffer = m_buffer_car.right(total_length - total_bytes - sizeof(qint64)*3);
//更新长度
total_length = buffer.size();
m_buffer_car = buffer;
}
}
















