Qt TCP网络通信学习
作者:skynetkang
TCP简介:
Transmission Control Protocol,传输控制协议 。用于数据传输的低层网络协议,多个物联网协议都是基于TCP协议的。它是一个面向数据流和连接的可靠传输协议。
TCP头部格式:
QTcpSocket类为TCP提供了一个接口,继承自QAbstractSocket。可实现POP3、SMTP、NNTP等标准的网络协议,也可以实现自定义的网络协议。异步工作,依靠事件循环来检测到来的数据,并且自动刷新输出的数据。而QAbstractSocket继承自QIODevice,故该类具备文件的读写功能,可以使用QTextStream和QDataStream。
QHostAddress QAbstractSocket::peerAddress () const 获取主机的IP地址
quint16 QAbstractSocket::peerPort () const 获取主机的端口号
qint64 QIODevice::write ( const QByteArray & byteArray ) //写入数据,即通过TCP发送数据。
QByteArray QIODevice::read ( qint64 maxSize ) //读取数据,最多读取maxSize。即获取TCP接收的存放在缓冲区的数据
QByteArray QIODevice::readAll () //获取TCP存放在缓冲区可读取的所有数据。
从一个QTcpSocket中读取数据前,必须先调用qint64 QIODevice::bytesAvailable () const 函数来确保已经有足够的数据可用。
涉及到的信号:
void QAbstractSocket::connected () [signal] 当连接建立成功发射连接信号,指示一个已建立的新连接。
void QAbstractSocket::error ( QAbstractSocket::SocketError socketError ) [signal] 连接发生了错误,就会发送error()信号,参数指示发生了什么错误。
void QAbstractSocket::disconnected () [signal] 断开一个已经存在的连接时,发射断开信号。
void QAbstractSocket::stateChanged ( QAbstractSocket::SocketState socketState ) [signal] 状态改变都会发射stateChanged()信号。
void QIODevice::bytesWritten ( qint64 bytes ) [signal] 表示数据写入完成,对应的可以调用bytesToWrite()方法了解写入了多少字节的数据。
void QIODevice::readyRead () [signal] 表示有数据可以读取,对应的可以调用bytesAvailable()方法了解可以读取多少字节的数据。
一个简单的TCP客户端和服务端程序,单连接。
客户端程序:
#ifndef QTTCPCLIENT_H #define QTTCPCLIENT_H #include <QObject> #include <QAbstractSocket> class QTcpSocket; //前置声明 //客户端程序 单连接,即一个客户端一个服务端 class QtTcpClient : public QObject { Q_OBJECT public: explicit QtTcpClient(QObject *parent = 0); ~QtTcpClient(); void sendMessage(); //发送信息 private slots: void readMessage(); //获取返回的信息 void displayError(QAbstractSocket::SocketError); //获取连接发生的错误 private: QTcpSocket *tcpSocket; //tcp连接 quint16 blockSize; //发送数据的大小 }; #endif // QTTCPCLIENT_H #include "qttcpclient.h" #include <QtNetwork> #include <QDataStream> #include <QString> #include <QByteArray> #include <QIODevice> #include <QObject> #include <QDebug> QtTcpClient::QtTcpClient(QObject *parent) : QObject(parent) { tcpSocket=new QTcpSocket(this); //建立信号连接,readyRead表示有数据过来,需要读取 connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readMessage())); //建立信号连接,error(QAbstractSocket::SocketError)连接发生错误或关闭时会发射此信号 connect(tcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocket::SocketError))); blockSize=0; tcpSocket->abort();//终止已有的连接 tcpSocket->connectToHost(QHostAddress(QString("127.0.0.1")),6666);//建立新的连接 qDebug()<<"client run......"<<endl; } QtTcpClient::~QtTcpClient() { delete tcpSocket; tcpSocket=NULL; } void QtTcpClient::sendMessage() { QByteArray block; QDataStream out(&block,QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_0); out<<(quint16)0; out<<QString("hi server this is my first connect!!!"); out.device()->seek(0);//定位到数据的开头 out<<(quint16)(block.size()-sizeof(quint16)); //添加数据大小信息到数据开头 tcpSocket->write(block); //发送数据 } void QtTcpClient::readMessage() { QString message; QDataStream in(tcpSocket); in.setVersion(QDataStream::Qt_4_0); if(blockSize==0) { //判断接收的数据是否大于两字节,也就是文件的大小信息所占的空间 //如果是则保存到blockSize中,否则直接返回,继续接收数据。 if(tcpSocket->bytesAvailable()<(int)sizeof(quint16)) { return; } in>>blockSize; } //如果没有接收完全部数据,则返回继续接收 if(tcpSocket->bytesAvailable()<blockSize) { return; } //将接收的数据存放到变量中 in>>message; qDebug()<<message; this->sendMessage(); //断开连接 tcpSocket->disconnectFromHost(); } void QtTcpClient::displayError(QAbstractSocket::SocketError) { switch(tcpSocket->error()) { case QAbstractSocket::RemoteHostClosedError: //远程服务端关闭连接错误 tcpSocket->disconnectFromHost(); qDebug()<<"client close now"; break; default: qDebug()<<"error id: "<<tcpSocket->error()<<endl; qDebug()<<"error message: "<<tcpSocket->errorString()<<endl; break; } } #include <QCoreApplication> #include "qttcpclient.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QtTcpClient tcpclient_t; return a.exec(); }
服务端程序:
#ifndef QTTCPSERVER_H #define QTTCPSERVER_H #include <QObject> #include <QAbstractSocket> class QTcpServer; //前置声明 class QTcpSocket; //服务端程序 单连接,即一个客户端一个服务端 class QtTcpServer : public QObject { Q_OBJECT public: explicit QtTcpServer(QObject *parent = 0); ~QtTcpServer(); private slots: void sendMessage();//发送信息 void readMessage();//获取返回的信息 void displayError(QAbstractSocket::SocketError);//获取连接发生的错误 private: QTcpServer *tcpServer; //tcp服务端 QTcpSocket *clientConnect;//来自客户端的连接 quint16 blockSize; //接收数据的大小 }; #endif // QTTCPSERVER_H #include "qttcpserver.h" #include <QtNetwork> #include <QDataStream> #include <QString> #include <QByteArray> #include <QIODevice> #include <QObject> #include <QDebug> QtTcpServer::QtTcpServer(QObject *parent) : QObject(parent) { blockSize=0; tcpServer=new QTcpServer(this); //开始监听 if(!tcpServer->listen(QHostAddress(QString("127.0.0.1")),6666)) { qDebug()<<tcpServer->serverError()<<endl; qDebug()<<tcpServer->errorString()<<endl; qApp->exit(); } //建立信号连接,每来一个新的连接,就发送服务端信息 connect(tcpServer,SIGNAL(newConnection()),this,SLOT(sendMessage())); qDebug()<<"server run....."<<endl; } QtTcpServer::~QtTcpServer() { delete tcpServer; tcpServer=NULL; delete clientConnect; clientConnect=NULL; } void QtTcpServer::sendMessage() { QByteArray block; QDataStream out(&block,QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_0); out<<(quint16)0; out<<QString("hello client the connect build!!!"); out.device()->seek(0); out<<(quint16)(block.size()-sizeof(quint16)); clientConnect=tcpServer->nextPendingConnection();//获取客户端的连接 tcpServer->close(); //关联套接字的disconnected()和deleteLater(),表明当连接断开时删除该套接字 connect(clientConnect,SIGNAL(disconnected()),clientConnect,SLOT(deleteLater())); clientConnect->write(block); connect(clientConnect,SIGNAL(readyRead()),this,SLOT(readMessage())); connect(clientConnect,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocket::SocketError))); } void QtTcpServer::readMessage() { QString message; QDataStream in(clientConnect); in.setVersion(QDataStream::Qt_4_0); if(blockSize==0) { if(clientConnect->bytesAvailable()<(int)sizeof(quint16)) { return; } in>>blockSize; } if(clientConnect->bytesAvailable()<blockSize) { return; } in>>message; qDebug()<<message; } void QtTcpServer::displayError(QAbstractSocket::SocketError) { switch(clientConnect->error()) { case QAbstractSocket::RemoteHostClosedError: clientConnect->disconnectFromHost(); //disconnectFromHost函数会一直等待套接字将所有数据发送完毕,然后关闭该套接字,并发射disconnected信号 qDebug()<<"server connect close"<<endl; break; default: qDebug()<<"error id: "<<clientConnect->error()<<endl; qDebug()<<"error message: "<<clientConnect->errorString()<<endl; clientConnect->disconnectFromHost(); qDebug()<<"server connect close"<<endl; break; } } #include <QCoreApplication> #include "qttcpserver.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QtTcpServer tcpserver_t; return a.exec(); }
运行结果:
基于TCP的文件传输程序:
客户端程序:
#ifndef CLIENT_H #define CLIENT_H #include <QAbstractSocket> #include <QDialog> #include <QFile> #include <QTcpSocket> #include <QString> #include <QByteArray> namespace Ui { class Client; } class Client : public QDialog { Q_OBJECT public: explicit Client(QWidget *parent = 0); ~Client(); private slots: void openFile(); void send(); void startTransfer(); void updateClientProgress(qint64); void displayError(QAbstractSocket::SocketError); void on_pushButton_open_clicked(); void on_pushButton_send_clicked(); private: Ui::Client *ui; QTcpSocket *tcpClient; //tcp连接 QFile *localFile; //要发送的文件 qint64 totalBytes; //发送数据的总大小 qint64 bytesWritten; //已经发送的数据大小 qint64 bytesToWrite; //剩余的数据大小 qint64 payloadSize; //每次发送数据的大小 QString fileName; //保存文件路径 QByteArray outBlock; //数据缓冲区,即存放每次要发送的数据块 }; #endif // CLIENT_H #include "client.h" #include "ui_client.h" #include <QtNetwork> #include <QFileDialog> #include <QDebug> Client::Client(QWidget *parent) : QDialog(parent), ui(new Ui::Client) { ui->setupUi(this); //初始化变量 this->payloadSize=64*1024; //64KB,每次发送的数据块大小为64KB this->totalBytes=0; this->bytesWritten=0; this->bytesToWrite=0; this->tcpClient=new QTcpSocket(this); //当连接服务器成功时,发送connected()信号,开始传送文件 connect(this->tcpClient,SIGNAL(connected()),this,SLOT(startTransfer())); //每次发送成功后发送bytesWritten(qint64))信号,告诉已成功发送的数据块大小,并更新进度条 connect(this->tcpClient,SIGNAL(bytesWritten(qint64)),this,SLOT(updateClientProgress(qint64))); //接收tcp连接发生的错误 connect(this->tcpClient,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocket::SocketError))); ui->pushButton_send->setEnabled(false); } Client::~Client() { delete ui; } void Client::openFile() { this->fileName=QFileDialog::getOpenFileName(this); if(!this->fileName.isEmpty()) { ui->pushButton_send->setEnabled(true); ui->label_state->setText(QString("打开文件 %1 成功").arg(this->fileName)); } } void Client::send() { ui->pushButton_send->setEnabled(false); this->bytesWritten=0; ui->label_state->setText(QString("连接中...")); //连接服务器 this->tcpClient->connectToHost(ui->lineEdit_host->text(),ui->lineEdit_port->text().toInt()); } void Client::startTransfer() { this->localFile=new QFile(this->fileName); //打开文件 if(!this->localFile->open(QFile::ReadOnly)) { qDebug()<<"client: open file error!"<<endl; return; } //获取打开文件的大小 this->totalBytes=this->localFile->size(); QDataStream sendOut(&this->outBlock,QIODevice::WriteOnly); sendOut.setVersion(QDataStream::Qt_4_0); //获取文件名(去掉文件前面的路径) QString currentFileName=this->fileName.right(this->fileName.size()-this->fileName.lastIndexOf('/')-1); //保留总大小信息空间,文件名大小信息空间,然后输入文件名 sendOut<<qint64(0)<<qint64(0)<<currentFileName; //总大小变量为总大小信息、文件名大小信息、文件名和实际文件大小的总和 this->totalBytes+=this->outBlock.size(); //返回outBlock的开始,填入总大小信息 sendOut.device()->seek(0); //填入各个项的大小信息以及文件名 sendOut<<this->totalBytes<<qint64(this->outBlock.size()-sizeof(qint64)*2); //发送完文件头结构后剩余数据的大小 this->bytesToWrite=this->totalBytes-this->tcpClient->write(this->outBlock); ui->label_state->setText(QString("已连接")); this->outBlock.resize(0); } void Client::updateClientProgress(qint64 numBytes) { //更新已经发送数据的大小 this->bytesWritten+=(int)numBytes; //如果已经发送了数据 if(this->bytesToWrite>0) { //每次发送payloadSize大小的数据,不足就发送剩余数据大小 this->outBlock=this->localFile->read(qMin(this->bytesToWrite,this->payloadSize)); //发送完一次数据后还剩余数据的大小 this->bytesToWrite-=(int)this->tcpClient->write(this->outBlock); //清空发送缓冲区 this->outBlock.resize(0); } else { //如果没有发送任何数据,就关闭文件 this->localFile->close(); } //更新进度条 ui->progressBar->setMaximum(this->totalBytes); ui->progressBar->setValue(this->bytesWritten); //发送完毕 if(this->bytesWritten==this->totalBytes) { ui->label_state->setText(QString("传送文件 %1 成功").arg(this->fileName)); this->localFile->close(); //关闭文件 this->tcpClient->close(); //关闭tcp连接 } } void Client::displayError(QAbstractSocket::SocketError) { qDebug()<<this->tcpClient->errorString()<<endl; this->tcpClient->close(); ui->progressBar->reset(); ui->label_state->setText(QString("客户端已就绪!")); ui->pushButton_send->setEnabled(true); } void Client::on_pushButton_open_clicked() { ui->progressBar->reset(); ui->label_state->setText(QString("状态:等待文件打开!")); this->openFile(); } void Client::on_pushButton_send_clicked() { this->send(); } #include <QApplication> #include "client.h" #include <QTextCodec> int main(int argc, char *argv[]) { QApplication a(argc, argv); QTextCodec::setCodecForCStrings(QTextCodec::codecForLocale()); QTextCodec::setCodecForLocale(QTextCodec::codecForLocale()); QTextCodec::setCodecForTr(QTextCodec::codecForLocale()); Client w; w.show(); return a.exec(); }
服务器程序:
#ifndef SERVER_H #define SERVER_H #include <QDialog> #include <QFile> #include <QTcpSocket> #include <QTcpServer> #include <QAbstractSocket> #include <QString> #include <QByteArray> namespace Ui { class Server; } class Server : public QDialog { Q_OBJECT public: explicit Server(QWidget *parent = 0); ~Server(); private slots: void start(); void acceptConnection(); void updateServerProgress(); void displayError(QAbstractSocket::SocketError socketError); void on_pushButton_clicked(); private: Ui::Server *ui; QTcpServer tcpServer; //服务器监听 QTcpSocket *tcpServerConnection; //来自客户端的连接 qint64 totalBytes; //存放总大小信息 qint64 bytesReceived; //已收到的数据大小 qint64 fileNameSize; //存放文件名的大小信息 QString fileName; //存放文件名 QFile *localFile; //本地文件 QByteArray inBlock; //数据缓冲区 }; #endif // SERVER_H #include "server.h" #include "ui_server.h" #include <QtNetwork> #include <QDebug> Server::Server(QWidget *parent) : QDialog(parent), ui(new Ui::Server) { ui->setupUi(this); //有新的连接到来,发射newConnection()信号,获取新的连接 connect(&this->tcpServer,SIGNAL(newConnection()),this,SLOT(acceptConnection())); } Server::~Server() { delete ui; } void Server::start() { //建立监听 if(!this->tcpServer.listen(QHostAddress(QString("127.0.0.1")),6666)) { qDebug()<<this->tcpServer.errorString()<<endl; close(); return; } ui->pushButton->setEnabled(false); //初始化变量 this->totalBytes=0; this->bytesReceived=0; this->fileNameSize=0; ui->label->setText(QString("监听")); ui->progressBar->reset(); } void Server::acceptConnection() { //获取来自客户端的连接 this->tcpServerConnection=this->tcpServer.nextPendingConnection(); //建立信号连接 connect(this->tcpServerConnection,SIGNAL(readyRead()),this,SLOT(updateServerProgress())); connect(this->tcpServerConnection,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocket::SocketError))); ui->label->setText(QString("接收连接")); this->tcpServer.close(); } void Server::updateServerProgress() { QDataStream in(this->tcpServerConnection); in.setVersion(QDataStream::Qt_4_0); //如果接收到的数据小于16个字节,保存到来的文件头结构 if(this->bytesReceived<=sizeof(qint64)*2) { if((this->tcpServerConnection->bytesAvailable()>=sizeof(qint64)*2)&&(this->fileNameSize==0)) { //接收数据总大小信息和文件名大小信息 in>>this->totalBytes>>this->fileNameSize; this->bytesReceived+=sizeof(qint64)*2; } if((this->tcpServerConnection->bytesAvailable()>=this->fileNameSize)&&(this->fileNameSize!=0)) { //接收文件名,并建立文件 in>>this->fileName; ui->label->setText(QString("接收文件 %1 ......").arg(this->fileName)); this->bytesReceived+=this->fileNameSize; this->localFile=new QFile(this->fileName); if(!this->localFile->open(QFile::WriteOnly)) { qDebug()<<"server: open file error"<<endl; return; } } else { return; } } //开始接收文件里面的数据 if(this->bytesReceived<this->totalBytes) { this->bytesReceived+=this->tcpServerConnection->bytesAvailable(); this->inBlock=this->tcpServerConnection->readAll(); this->localFile->write(this->inBlock); this->inBlock.resize(0); } ui->progressBar->setMaximum(this->totalBytes); ui->progressBar->setValue(this->bytesReceived); //接收数据完成时 if(this->bytesReceived==this->totalBytes) { this->tcpServerConnection->close(); this->localFile->close(); ui->pushButton->setEnabled(true); ui->label->setText(QString("接收文件 %1 成功").arg(this->fileName)); } } void Server::displayError(QAbstractSocket::SocketError socketError) { qDebug()<<this->tcpServerConnection->errorString(); this->tcpServerConnection->close(); ui->progressBar->reset(); ui->label->setText(QString("服务端就绪")); ui->pushButton->setEnabled(true); } void Server::on_pushButton_clicked() { start(); } #include <QApplication> #include "server.h" #include <QTextCodec> int main(int argc, char *argv[]) { QApplication a(argc, argv); QTextCodec::setCodecForCStrings(QTextCodec::codecForLocale()); QTextCodec::setCodecForLocale(QTextCodec::codecForLocale()); QTextCodec::setCodecForTr(QTextCodec::codecForLocale()); Server w; w.show(); return a.exec(); }
运行结果:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。