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_OBJECTpublic: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,每次发送的数据块大小为64KBthis->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_OBJECTpublic: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();
}
运行结果: