TCP简介:TransmissionControlProtocol,传输控制协议。用于数据传输的低层网络协议,多个物联网协议都是基于TCP协议的。它是一个面向数据流和连接的可靠传输协议。TCP头...

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()方法了解可以读取多少字节的数据。

客户端程序:

#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();
}

运行结果:

客户端程序:

#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();
}

运行结果:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。