QT編寫多線程TCP文件接收服務器

本文介紹的是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文件接收服務器


發佈了49 篇原創文章 · 獲贊 30 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章