本文介绍的是QT 多线程 TCP 文件接收服务器实例,如果你想深入了解这方面的资料的话,请关注本文末尾,不多说,我们先来看内容。

因为项目需要,需要跨平台编写网络传输程序。

目标:

用户端:Linux(arm平台),完成文件的传输

服务器:Windows ,使用多线程的文件的接收

实现无线的文件传输功能

用户端程序,用标准的socket完成文件传输的功能,代码如下:

 
 
  1.  // Linux下网络编程,客户端程序代码    
  2.  
  3.  //程序运行参数:    
  4.  // ./client IPADDRESS PORTNUMBER    
  5.  // (其中IPADDRESS是服务端IP地址,PORTNUMBER是服务端用于监听的端口)    
  6.  //    
  7.    
  8.  #include <stdio.h> 
  9.  #include <stdlib.h> 
  10.  #include <errno.h> 
  11.  #include <string.h> 
  12.  #include <netdb.h> 
  13.  #include <ctype.h> 
  14.  #include <unistd.h> 
  15. #include <sys/types.h> 
  16.  #include <sys/socket.h> 
  17.   #include <netinet/in.h> 
  18. #include <sys/time.h> 
  19.  
  20.  //用这个my_read()函数代替本来的read()函数原因有以下几点:   
  21. //   
  22.  //ssize_t read(int fd,void *buf,size_t nbyte)   
  23. //read函数是负责从fd中读取内容。当读成功时,read返回实际所读的字节数;如果   
  24.  //返回的值是0,表示已经读到文件的结束了;小于0表示出现了错误。   
  25.  //   
  26.  // 1)如果错误为EINTR说明read出错是由中断引起的,继续读。   
  27.  // 2)如果是ECONNREST表示网络连接出了问题,www.linuxidc.com停止读取。   
  28.  
  29.  size_t min(size_t a,size_t b)  
  30. {  
  31.  return( (a<b) ? a : b);  
  32.  }  
  33.    
  34.  ssize_t my_write(int fd,void *buffer,size_t length)  
  35. {  
  36.  size_t bytes_left; //尚未写的文件大小   
  37.  size_t writesize = 4* 1024;  
  38.  ssize_t written_bytes; //已经写的文件大小   
  39.  char *ptr;  
  40.  ptr=buffer;  
  41. bytes_left=length;  
  42.   while(bytes_left>0)  
  43.  {  
  44.  //开始写   
  45.  written_bytes=write(fd,ptr,min(bytes_left,writesize));  
  46.  //出现了写错误   
  47. if(written_bytes<=0)  
  48.  {  
  49.  //中断错误,置零重新写   
  50. if(errno==EINTR)  
  51. written_bytes=0;  
  52.  //其他错误,退出不写了   
  53.  else   
  54.  return(-1);  
  55. }  
  56.  //从剩下的地方继续写   
  57. bytes_left-=written_bytes;  
  58.  ptr+=written_bytes;  
  59.  }  
  60. return(0);  
  61. }  
  62.  
  63. : int main(int argc, char *argv[])  
  64. {  
  65.  int sockfd; //通信套接字描述符   
  66. char *buffer; //缓冲区   
  67. struct sockaddr_in server_addr; //服务器地址结构   
  68.  struct hostent *host; //主机地址与名称信息结构www.linuxidc.com   
  69.  int nbytes; //端口号、字节数   
  70.  FILE *fp; //文件指针   
  71. int nfilesize; //文件大小   
  72.  char str[128]; //文件名   
  73.  char yes='Y'; //流程控制   
  74.  struct timeval tpstart,tpend; //用于记录文件传输时间   
  75.  float timeuse; //文件传输所用时间   
  76. char *hostname="127.0.0.1";//主机名/ip地址   
  77.  int portnumber=4321;//端口号   
  78.    
  79.  //提示用户输入完整的命令行参数   
  80.  if(argc!=3)  
  81.  {  
  82.  fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);  
  83.  printf("using defaults:\nhostname: %s\nportnumber: %d\n",hostname,portnumber);  
  84.  }  
  85.  
  86.  //如果利用用户输入的域名无法获得正确的主机地址信息,则退出   
  87.  if (argc>1)  
  88.  {  
  89.  if((host=gethostbyname(argv[1]))==NULL)  
  90. {  
  91.  fprintf(stderr,"Gethostname error\n");  
  92. exit(1);  
  93.  }  
  94.  }  
  95.  
  96. else   
  97.  if((host=gethostbyname(hostname))==NULL)  
  98.  {  
  99. fprintf(stderr,"Gethostname error\n");  
  100.  exit(1);  
  101. }   
  102. if(argc>2)  
  103.  //如果用户输入的端口不正确,则提示并退出   
  104.  if((portnumber=atoi(argv[2]))<0)  
  105. {  
  106. fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);  
  107.  exit(1);  
  108. }  
  109. //客户程序开始建立 sockfd描述符,创建通信套接字   
  110.  if((sockfd=socket(AF_INET,SOCK_STREAM,6))==-1)  
  111. {  
  112.  fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));  
  113. exit(1);  
  114.  }  
  115. //客户程序填充服务端的地址信息   
  116.  bzero(&server_addr,sizeof(server_addr));  
  117.  server_addr.sin_family=AF_INET;  
  118.  server_addr.sin_port=htons(portnumber);  
  119.  server_addr.sin_addr=*((struct in_addr *)host->h_addr);   
  120. //客户程序发起连接请求    
  121. if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)  
  122.  {  
  123.  fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));  
  124.  exit(1);  
  125. }  
  126.  printf("Connection Succeed!\n");  
  127.  while (toupper(yes)=='Y')  
  128.  {  
  129.  //提示用户输入文件路径   
  130.  printf("Please input the file location:");  
  131.  scanf("%s",str);  
  132.  while ((fp=fopen(str,"r"))==NULL)  
  133. {  
  134.  fprintf(stderr,"File open error,Retry!\n");  
  135.  printf("Please input the file location:");  
  136.  scanf("%s",str);  
  137.  //exit(1);   
  138.  }  
  139.   getchar();  
  140.    
  141.  //获取打开的文件的大小,www.linuxidc.com并将文件整个读入内存中   
  142.  fseek(fp,0L,SEEK_END);  
  143.  nfilesize=ftell(fp);  
  144.  rewind(fp);//most important!!!!!   
  145.  char *p=(char *)malloc(nfilesize);  
  146. if (fread((void *)p,nfilesize,1,fp)<1) {  
  147. if (feof(fp))  
  148. printf("read end of file!\nquit!\n");  
  149. else   
  150.  printf("read file error!\n");  
  151.  }  
  152.  //将要传输的文件的大小信息发送给客户端   
  153.  if (my_write(sockfd,(void *)&nfilesize,4)==-1)  
  154. {  
  155.  fprintf(stderr,"Write Error:%s\n",strerror(errno));  
  156. exit(1);  
  157. }  
  158.  printf("Begin to transfer the file!\n");  
  159. getchar();  
  160.  
  161. //获取传输初始时间   
  162. gettimeofday(&tpstart,NULL);  
  163.  
  164.  //传输文件   
  165. if (my_write(sockfd,p,nfilesize)==-1)  
  166.   {  
  167. fprintf(stderr,"Transfer failed!");  
  168.  exit(1);  
  169.  }  
  170.  //获取传输结束时间   
  171.  gettimeofday(&tpend,NULL);  
  172. //计算整个传输用时   
  173. timeuse=1000000*(tpend.tv_sec-tpstart.tv_sec)+(tpend.tv_usec-tpstart.tv_usec);  
  174. timeuse/=1000000;   
  175.  printf("Transfer Succeed!\nFile Name: %s\nFile Size: %d bytes\nTotal Time: 
  176. %f seconds\nTransfer Speed: %f bytes/second",str,nfilesize,timeuse,((float)nfilesize)/timeuse);  
  177.  free(p); //释放文件内存   
  178.  fclose(fp); //关闭文件   
  179.  // printf("\nTransfer another file?(Y/N): ");   
  180.  //scanf("%c",&yes);   
  181. // getchar();   
  182.  yes='n';  
  183.  }  
  184.  //结束通讯,关闭套接字,www.linuxidc.com关闭连接   
  185.  close(sockfd);  
  186.  printf("\nClient Exit!~~\n");  
  187.  exit(0);  
  188.  } 

服务器端代码列表:

具体代码如下:

 
 
  1. “tcpserver.h”  
  2. #ifndef TCPSERVER_H  
  3.  #define TCPSERVER_H   
  4.  #include <QTcpServer> 
  5.  //继承自QTcpServer,完成TCPSEVER的建立的类   
  6.  class TcpServer : public QTcpServer  
  7. {  
  8. Q_OBJECT  
  9.  public:  
  10.  explicit TcpServer(QObject *parent = 0);  
  11.   //此信号用来更新UI   
  12.  signals:  
  13. void bytesArrived(qint64,qint32,int);  
  14. //QTcpServer类自带的函数,详情参考Class Reference   
  15. protected:  
  16.  void incomingConnection(int socketDescriptor);  
  17. };  
  18.  #endif // TCPSERVER_H  

TCPSERVER继承QTcpServer,主要完成TCP服务器的建立,类中最主要的成员函数为虚函数incomingConnection(int socketDescriptor)的定义。


“tcpserver.cpp”

 
 
  1.  #include "tcpserver.h"   
  2.  #include "tcpthread.h"   
  3.  //构造函数   
  4.  TcpServer::TcpServer(QObject *parent) :  
  5.  QTcpServer(parent)  
  6.  {  
  7. }  
  8.   //重新定义了incomingConnection这个虚函数,   
  9. //开辟一个新的tcpsocket线程,从TcpServer获得socketDescriptor,   
  10.  //并完成相应的信号连接。   
  11.  void TcpServer::incomingConnection(int socketDescriptor)  
  12. {  
  13.  TcpThread *thread = new TcpThread(socketDescriptor, this);  
  14.  //将线程结束信号与线程的deleteLater槽关联   
  15.  connect(thread, SIGNAL(finished()),  
  16.  thread, SLOT(deleteLater()));  
  17.  //关联相应的UI更新槽   
  18.  connect(thread,SIGNAL(bytesArrived(qint64,qint32,int)),  
  19. this,SIGNAL(bytesArrived(qint64,qint32,int)));   
  20. //QT的线程都是从start开始,调用run()函数   
  21.  thread->start();  
  22.  } 

极其简单的构造函数,在incomingConnection()中,定义一个线程TcpThread,并将socketDescriptor传递给其构造函数,完成线程的创建,并且调用QThread的start函数,开始执行线程的虚函数run()。

“tcpthread.h”

 
 
  1. #ifndef TCPTHREAD_H  
  2. #define TCPTHREAD_H  
  3.  #include <QThread> 
  4. #include <QTcpSocket> 
  5.  #include <QtNetwork> 
  6.  //继承QThread的TCP传输线程   
  7.  //主要是完成run()虚函数的定义   
  8.  //还有一些辅助变量的声明   
  9. class QFile;  
  10. class QTcpSocket;  
  11.  class TcpThread : public QThread  
  12.  {  
  13.  Q_OBJECT  
  14.  public:  
  15.  TcpThread(int socketDescriptor, QObject *parent);  
  16. void run();  
  17. signals:  
  18. void error(QTcpSocket::SocketError socketError);  
  19. void bytesArrived(qint64,qint32,int);  
  20.  public slots:  
  21.  void receiveFile();  
  22. private:  
  23.  int socketDescriptor;  
  24.  qint64 bytesReceived; //收到的总字节   
  25.  qint64 byteToRead; //准备读取的字节   
  26.  qint32 TotalBytes; //总共传输的字节   
  27.  QTcpSocket *tcpSocket;  
  28.  QHostAddress fileName; //文件名   
  29.  QFile *localFile;  
  30.  QByteArray inBlock; //读取缓存   
  31.  };  
  32.  #endif // TCPTHREAD_H  

继承自QThread类,在此线程中完成TCPSOCKET的建立,www.linuxidc.com和文件的接收。

“tcpthread.cpp”

 
 
  1. #include "tcpthread.h"   
  2. #include <QtGui> 
  3.  #include <QtNetwork> 
  4.  //构造函数完成简单的赋值/   
  5. TcpThread::TcpThread(int socketDescriptor, QObject *parent):  
  6. QThread(parent),socketDescriptor(socketDescriptor)  
  7.  {  
  8.  bytesReceived = 0;  
  9.  }  
  10.   //因为QT的线程的执行都是从run()开始,   
  11.  //所以在此函数里完成tcpsocket的创建,相关信号的绑定www.linuxidc.com  
  12.  void TcpThread::run()   
  13.  {  
  14.  tcpSocket = new QTcpSocket;  
  15. //将Server传来的socketDescriptor与刚创建的tcpSocket关联   
  16. if (!tcpSocket->setSocketDescriptor(socketDescriptor)) {  
  17. emit error(tcpSocket->error());  
  18. return;  
  19.  }  
  20. qDebug()<<socketDescriptor;   
  21. : //这是重中之重,必须加Qt::BlockingQueuedConnection!   
  22.  //这里困扰了我好几天,原因就在与开始没加,默认用的Qt::AutoConnection。   
  23.  //简单介绍一下QT信号与槽的连接方式:   
  24. //Qt::AutoConnection表示系统自动选择相应的连接方式,如果信号与槽在同一线程,就采用Qt::DirectConnection,
  25. 如果信号与槽不在同一线程,将采用Qt::QueuedConnection的连接方式。   
  26.  //Qt::DirectConnection表示一旦信号产生,立即执行槽函数。   
  27.  //Qt::QueuedConnection表示信号产生后,将发送Event给你的receiver所在的线程,postEvent(QEvent::MetaCall,...),
  28. slot函数会在receiver所在的线程的event loop中进行处理。   
  29.  //Qt::BlockingQueuedConnection表示信号产生后调用sendEvent(QEvent::MetaCall,...),
  30. 在receiver所在的线程处理完成后才会返回;只能当sender,receiver不在同一线程时才可以。   
  31.  //Qt::UniqueConnection表示只有它不是一个重复连接,连接才会成功。www.linuxidc.com如果之前已经有了一个链接(相同的信号连接到同一对象的同一个槽上),那么连接将会失败并将返回false。   
  32.  //Qt::AutoCompatConnection与QT3保持兼容性   
  33.  //说明一下,对于任何的QThread来说,其线程只存在于run()函数内,其它的函数都不在线程内,所以此处要采用Qt::BlockingQueuedConnection,   
  34.  //因为当SOCKET有数据到达时就会发出readyRead()信号,但是此时可能之前的receiveFile()还未执行完毕,之前使用的Qt::AutoConnection,   
  35.  //结果传输大文件的时候就会出错,原因就在于只要有数据到达的时候,就会连接信号,但是数据接收还没处理完毕,而Qt::BlockingQueuedConnection会阻塞   
  36.  //此连接,直到receiveFile()处理完毕并返回后才发送信号。   
  37.  
  38. connect(tcpSocket, SIGNAL(readyRead()),  
  39. this, SLOT(receiveFile()),Qt::BlockingQueuedConnection);  
  40.  exec();  
  41.  
  42.  }  
  43.  void TcpThread::receiveFile()  
  44. {  
  45. //将tcpsocket封装到QDataStream里,便于使用操作符>>   
  46.  QDataStream in(tcpSocket);  
  47.  if(bytesReceived < sizeof(qint32))  
  48. {  
  49. //先接收32bit的文件大小   
  50.  if(tcpSocket->bytesAvailable() >= sizeof(qint32))  
  51. {  
  52.  63: in.setByteOrder(QDataStream::LittleEndian); //必须的,因为发送端为LINUX系统   
  53.  in>>TotalBytes;  
  54. TotalBytes += 4;  
  55. qDebug()<<TotalBytes;  
  56.    
  57.  bytesReceived += sizeof(qint32);  
  58. fileName = tcpSocket->peerAddress();  
  59.  quint16 port = tcpSocket->peerPort();  
  60.   localFile = new QFile(fileName.toString()+(tr(".%1").arg(port))); //用户端的IP地址作为保存文件名   
  61.  if (!localFile->open(QFile::WriteOnly ))  
  62.  
  63. {  
  64. }  
  65.  }   
  66.  }  
  67.  //如果读取的文件小于文件大小就继续读   
  68.  if (bytesReceived < TotalBytes){  
  69.  byteToRead = tcpSocket->bytesAvailable();  
  70.  bytesReceived += byteToRead;  
  71.  inBlock = tcpSocket->readAll();  
  72.  qDebug()<<"bytesReceived is:"<<bytesReceived;  
  73. localFile->write(inBlock);  
  74.  inBlock.resize(0);  
  75.  }   
  76. emit bytesArrived(bytesReceived,TotalBytes,socketDescriptor);  
  77.  if (bytesReceived == TotalBytes) {  
  78.  localFile->close();  
  79.  qDebug()<<bytesReceived;  
  80.  emit finished();  
  81.  QApplication::restoreOverrideCursor();  
  82.  }  
  83.  } 

代码中已经有很详细的注释,需要再说明的一点就是在多线程的编写中,信号/槽的连接方式一定要根据实际情况来进行选择!

“widget.h”

 
 
  1.  #ifndef WIDGET_H  
  2.  #define WIDGET_H  
  3.  #include <QWidget>   
  4.  #include "tcpthread.h"   
  5.  #include "tcpserver.h"    
  6. : class QDialogButtonBox;   
  7.  class QTcpSocket;  
  8.  namespace Ui {  
  9.  class Widget;  
  10.  }  
  11.    
  12. : class Widget : public QWidget  
  13. : {  
  14.  Q_OBJECT   
  15. public:  
  16.  explicit Widget(QWidget *parent = 0);  
  17.  ~Widget();  
  18.  private:  
  19.  Ui::Widget *ui;  
  20.  TcpServer tcpServer;  
  21.  private slots:  
  22.  void on_OkButton_clicked();  
  23.  void updateProgress(qint64,qint32,int);  
  24.   };   
  25.  #endif // WIDGET_H  

简单的widget类。

“widget.cpp”

 
 
  1.  #include "widget.h"   
  2. #include "ui_widget.h"   
  3.  #include <QtNetwork> 
  4.  #include <QtGui>   
  5.  Widget::Widget(QWidget *parent) :  
  6.  QWidget(parent),  
  7.  ui(new Ui::Widget)  
  8. {  
  9.  ui->setupUi(this);  
  10.  ui->progressBar->setMaximum(2);  
  11. ui->progressBar->setValue(0);  
  12.  }   
  13.  Widget::~Widget()  
  14.  {  
  15. delete ui;  
  16.  }   
  17.  void Widget::on_OkButton_clicked()  
  18.  {  
  19.  ui->OkButton->setEnabled(false);   
  20.  QApplication::setOverrideCursor(Qt::WaitCursor);  
  21.  //bytesReceived = 0;   
  22.  while (!tcpServer.isListening() && !tcpServer.listen(QHostAddress::Any,12345))  
  23.  {  
  24.  QMessageBox::StandardButton ret = QMessageBox::critical(this,  
  25. tr("回环"),  
  26.  tr("无法开始测试: %1.")  
  27.  .arg(tcpServer.errorString()),  
  28.  QMessageBox::Retry  
  29.  | QMessageBox::Cancel);  
  30.  if (ret == QMessageBox::Cancel)  
  31.  return;  
  32.  }  
  33.  ui->statuslabel->setText(tr("监听端口:%1").arg("12345"));  
  34.  connect(&tcpServer,SIGNAL(bytesArrived(qint64,qint32,int)),  
  35.  this,SLOT(updateProgress(qint64,qint32,int)));  
  36.  }  
  37.  void Widget::updateProgress(qint64 bytesReceived, qint32 TotalBytes, int socketDescriptor)  
  38.  {  
  39.  ui->progressBar->setMaximum(TotalBytes);  
  40.  ui->progressBar->setValue(bytesReceived);  
  41.  ui->statuslabel->setText(tr("已接收 %1MB")  
  42. .arg(bytesReceived / (1024 * 1024)));  
  43.  ui->textBrowser->setText(tr("现在连接的socket描述符:%1").arg(socketDescriptor));  
  44.  } 

完成服务器的监听,和进度条的更新。

点击开始后,处于监听状态。

QT编写多线程TCP文件接收服务器

传输文件时:

QT编写多线程TCP文件接收服务器


Logo

更多推荐