【C/C++】項目_9_文件傳輸系統(tcpput/getfile.cpp,tcpfileserver.cpp)


1.TCP粘包/超時:全雙工,拆粘包,select

採用ftp協議進行文件傳輸性能不夠【FTP協議是TCP/IP協議(五層,拆包)的一部分,嚴格意義上來說是應用層協議,TCP通信兩大瓶頸:帶寬,交互次數過多(獲取對方服務器時間,文件列表,改名等)】。windows平臺ftp安裝服務端麻煩,不同ftp服務器在使用時略有區別,兼容性不好【比如ftp.list裏*號圓點都可以,有的不行】。系統內部不用ftp,外部用ftp(因爲不能把自己程序部署到別人服務器上,別人服務器上也只能裝個ftp服務端)。如下比如B站作者將視頻文件發到視頻上傳服務器進行審覈,審覈完後通過文件傳輸系統發給視頻播放服務器,一個播放服務器是不可能響應那麼多播放請求的,肯定是服務器集羣。
在這裏插入圖片描述
下面是短信系統,業務系統有發送短信需求的話把文件傳輸給接入服務器
在這裏插入圖片描述
爲什麼要傳文件而不是tcp報文(有些數據就是文件方式組織的,比如視頻),因爲傳文件更快。比如一條短信是一個tcp報文,tcp報文傳1000條/秒。若以文件傳,一個文件存100000條短信記錄(1條短信不到1k,兩三百字節)
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
發的報文大小大於tcp的一個報文,tcp就幫我們拆包,爲了達到高效率,不讓tcp拆大的包。發數據時也要考慮tcp協議包大小,一次就發幾個字節也讓tcp一個包也會影響效率。
在這裏插入圖片描述
在這裏插入圖片描述
如下是第一種方法,粘包還是會出現,但可以區分開
在這裏插入圖片描述
如下是第三種方法,自定義分隔符
在這裏插入圖片描述
如下tcp報文有報文格式,tcp底層把報文做拆分
在這裏插入圖片描述
在這裏插入圖片描述
如下在_public.h客戶端
在這裏插入圖片描述
在這裏插入圖片描述
如下在_public.h服務端,InitServer函數中沒指定ip,說明哪任何ip都能連
在這裏插入圖片描述
在這裏插入圖片描述
如下是_public.h定義的幾個方法,TcpRead函數調用recv函數,客戶端連上啓動一進程消耗資源,客戶端因網絡或其他原因斷開,但服務端recv函數不知道。如下TcpRead方法最後調用recv函數,recv會阻塞一直等待,實際開發不會等,加上一個超時機制:比如一分鐘還沒給我發信息的話,我會斷開你釋放資源進程退出。Readn方法是對recv函數擴展,如下還有一個Writen方法是對send函數擴展。TcpWrite中我們可寫入C語言格式字符串(以空字符結束),也可以寫二進制流數據。buflen爲空表示寫入字符串,buflen不爲空寫入二進制流數據,寫100個字節過去就寫100,不管裏面什麼字符,0也好非0也好,類似於文件操作的read和write,比如gets函數讀取一行,read讀取指定字節數,不管是不是一行
在這裏插入圖片描述
如下在_public.h定義解決粘包問題,TCPBUFLEN是自定義的宏
在這裏插入圖片描述
如下在_public.cpp中TcpRead(),TcpWrite(),Readn(),Writen()4個函數實現,5個字節,/0是字符串結尾符。如下有人做法是先發報文頭,再發報文體,兩次發有交互確認耗時效率低,假設總的報文加起來小於TCP報文長度(默認1448字節)一次就可以。大於TCP報文長度,tcp底層會拆分。
在這裏插入圖片描述
如上報文頭爲什麼發送一個字符串(char strBuflen)?若定義爲int ilen=,能存20億大小,字符串5個字節最多放5個9這麼長。這樣若是服務端C寫,客戶端java寫,java將5個字節就一個字符串讀出來轉爲整數,直接讀一個整數困難。一般定義5個字節夠了,再大的話直接用文件傳輸了,不用字符串了。
在這裏插入圖片描述
如下還在TcpRead函數中首先從socket裏讀取報頭長度(5個字節就讀取5個字節),再讀報文體長度(讀報文頭長度123\0\0\0個字節),後面是下一次TcpRead事情
在這裏插入圖片描述
如下長度n就是報頭指定的長度,Writen不要調用兩次,CTcpServer::Read中Readn可調用兩次,因爲接收端接收數據從系統的緩衝區取數據,並沒有增加網絡的交互次數,Readn解決recv讀不完整缺點
在這裏插入圖片描述
如下在CTcpClient::Read()成員函數中,select不是sql語句是系統調用函數。select原本用於I/O複用,現用於超時機制。客戶端連上來後啓動一個進程消耗資源,所以服務端在客戶端某原因斷開時主動將客戶端進程退出釋放資源
在這裏插入圖片描述
如下CTcpClient::Read()沒用超時機制(超時不管在客戶端還是服務端都用得着)

// 本程序演示採用CTcpClient類,實現socket通訊的客戶端,demo11.cpp
#include "_public.h"
int main(int argc,char *argv[])
{
  if (argc != 3)
  {
    printf("\n");
    printf("Using:./demo11 ip port\n\n");
    printf("Example:./demo11 118.89.50.198 5010\n\n");
    printf("本程序演示採用CTcpClient類,實現socket通訊的客戶端。\n\n");
    return -1;
  }
  CTcpClient TcpClient;

////////////////////////////////////////////////////////////////////1.向服務器發起連接
  if (TcpClient.ConnectToServer(argv[1],atoi(argv[2])) == false)
  {
    printf("TcpClient.ConnectToServer(%s,%d) failed.\n",argv[1],atoi(argv[2])); return -1;
  }

  char strRecvBuffer[1024],strSendBuffer[1024];
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  strcpy(strSendBuffer,\
        "英超最後一輪,卡里克踢完了在曼聯的最後一場正式比賽,這意味着紅魔上次稱霸歐冠的黃金一代全部退場。");

///////////////////////////////////////////////////////////////2.把strSendBuffer內容發送給服務端
  if (TcpClient.Write(strSendBuffer)==false)
  {
    printf("TcpClient.Write() failed.\n"); return -1;
  }
  printf("send ok:%s\n",strSendBuffer);
  memset(strRecvBuffer,0,sizeof(strRecvBuffer));

///////////////////////////////////////////////////////////3.接收服務端返回的報文(下面Read沒有超時機制)
  if (TcpClient.Read(strRecvBuffer)==false)
  {
    if (TcpClient.m_btimeout==true) printf("timeout\n");
    printf("TcpClient.Read() failed.\n"); return -1;
  }
  printf("recv ok:%s\n",strRecvBuffer);
  return 0;
}
// 本程序演示採用CTcpServer類,實現socket通訊的服務端,demo12.cpp
#include "_public.h"
int main(int argc,char *argv[])
{
  if (argc != 2)
  {
    printf("\n");
    printf("Using:./demo12 port\n\n");
    printf("Example:./demo12 5010\n\n");
    printf("本程序演示採用CTcpServer類,實現socket通訊的服務端。\n\n");
    return -1;
  }
  CTcpServer TcpServer;
  
///////////////////////////////////////////////////////1.服務端初始化
  if (TcpServer.InitServer(atoi(argv[1])) == FALSE)
  {
    printf("TcpServer.InitServer(%s) failed.\n",argv[1]); return -1;
  }

///////////////////////////////////////////////////2.等待客戶端的連接
  if (TcpServer.Accept() == FALSE)
  {
    printf("TcpServer.Accept() failed.\n"); return -1;
  }
  char strRecvBuffer[1024],strSendBuffer[1024];
  memset(strRecvBuffer,0,sizeof(strRecvBuffer));

/////////////////////////////////////////3.讀取客戶端的報文,等時間是20秒,有超時機制
  if (TcpServer.Read(strRecvBuffer,20)==FALSE) 
  {
    printf("TcpServer.Read() failed.\n"); return -1;
  }
  printf("recv ok:%s\n",strRecvBuffer);
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  strcpy(strSendBuffer,"ok");

////////////////////////////////////////4.向客戶端返回響應內容
  if (TcpServer.Write(strSendBuffer)==FALSE) 
  {
    printf("TcpServer.Write() failed.\n"); return -1;
  }
  printf("send ok:%s\n",strSendBuffer);
  return 0;
}

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
下面添加睡眠改爲超時機制
在這裏插入圖片描述
下面利用m_btimeout成員(超時爲true)打印出是否超時
在這裏插入圖片描述
在這裏插入圖片描述
服務端分不清客戶端是網絡原因還是程序自身出問題斷開
在這裏插入圖片描述
下面是GetIP()
在這裏插入圖片描述

2.簡單文件傳輸:CTcpClient,CTcpServer

// 本程序演示採用CTcpClient類,實現socket通訊的客戶端和文件傳輸,demo13.cpp
#include "_public.h"
// 把文件的內容發送給服務端
bool SendFile(int sockfd,char *filename,int filesize);
int main(int argc,char *argv[])
{
  if (argc != 4)
  {
    printf("\n");
    printf("Using:./demo13 ip port filename\n\n");
    printf("Example:./demo13 118.89.50.198 5010 test1.jpg\n\n");
    printf("本程序演示採用CTcpClient類,實現socket通訊的客戶端和文件傳輸。\n\n");
    return -1;
  }
  // 判斷文件是否存
  if (access(argv[3],R_OK) != 0)
  {
    printf("file %s not exist.\n",argv[3]); return -1;
  }
  int uFileSize=0;
  char strMTime[20],strRecvBuffer[1024],strSendBuffer[1024];

  // 獲取文件的時間和大小
  memset(strMTime,0,sizeof(strMTime));
  FileMTime(argv[3],strMTime);
  // 獲取文件的大小
  uFileSize=FileSize(argv[3]);

  // 把文件的信息封裝成一個xml,發送給服務端
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  snprintf(strSendBuffer,100,"<filename>%s</filename><mtime>%s</mtime><size>%lu</size>",argv[3],strMTime,uFileSize);
  CTcpClient TcpClient;
  
//////////////////////////////////////////////////////////////////1.向服務器發起連接
  if (TcpClient.ConnectToServer(argv[1],atoi(argv[2])) == false)
  {
    printf("TcpClient.ConnectToServer(%s,%d) failed.\n",argv[1],atoi(argv[2])); return -1;
  }

////////////////////////////////////////////////////2.把文件信息的xml發送給服務端,不需要回復減少交互次數
  if (TcpClient.Write(strSendBuffer)==false)
  {
    printf("TcpClient.Write() failed.\n"); return -1;
  }
  printf("send xml:%s\n",strSendBuffer);
  printf("send file ...");

///////////////////////////////////////////////////////3.把文件的內容發送給服務端
  if (SendFile(TcpClient.m_sockfd,argv[3],uFileSize)==false)
  {
    printf("SendFile(%s) failed.\n",argv[3]); return -1;
  }  
  memset(strRecvBuffer,0,sizeof(strRecvBuffer));
  
////////////////////////////////////////////////////4.接收服務端返回的確認報文
  if (TcpClient.Read(strRecvBuffer)==false)
  {
    printf("TcpClient.Read() failed.\n"); return -1;
  }
  if (strcmp(strRecvBuffer,"ok")==0)
    printf("ok.\n");
  else
    printf("failed.\n");
  return 0;
}

/////////////////////////////////////////////////3.把文件的內容發送給服務端
bool SendFile(int sockfd,char *filename,int filesize)
{
  int  bytes=0;
  int  total_bytes=0;
  int  onread=0;
  char buffer[1000];
  FILE *fp=NULL;
  if ( (fp=fopen(filename,"rb")) == NULL ) 
  {
    printf("fopen(%s) failed.\n",filename); return false;
  }
  while (true)
  {
    memset(buffer,0,sizeof(buffer));

    if ((filesize-total_bytes) > 1000) onread=1000;
    else onread=filesize-total_bytes;
    bytes=fread(buffer,1,onread,fp); 
    if (bytes > 0)
    {
      if (Writen(sockfd,buffer,bytes) == false)
      {
        printf("Writen() failed.\n"); fclose(fp); fp=NULL; return false;
      }
    }
    total_bytes = total_bytes + bytes;
    if ((int)total_bytes == filesize) break;
  }
  fclose(fp);
  return true;
}
// 本程序演示採用CTcpServer類,實現socket通訊的服務端和文件傳輸,demo14.cpp
#include "_public.h"
// 接收文件的內容
bool RecvFile(char *strRecvBuffer,int sockfd,char *strfilename);
int main(int argc,char *argv[])
{
  if (argc != 3)
  {
    printf("\n");
    printf("Using:./demo14 port filename\n\n");
    printf("Example:./demo14 5010 test2.jpg\n\n"); //test2.jpg重新命名
    printf("本程序演示採用CTcpServer類,實現socket通訊的服務端和文件傳輸。\n\n");
    return -1;
  }

  CTcpServer TcpServer;
////////////////////////////////////////////////////////////1.服務端初始化
  if (TcpServer.InitServer(atoi(argv[1])) == false)
  {
    printf("TcpServer.InitServer(%s) failed.\n",argv[1]); return -1;
  }

//////////////////////////////////////////////////////////2.等待客戶端的連接
  if (TcpServer.Accept() == false)
  {
    printf("TcpServer.Accept() failed.\n"); return -1;
  }

  char strRecvBuffer[1024],strSendBuffer[1024];
  memset(strRecvBuffer,0,sizeof(strRecvBuffer));
//////////////////////////////////////////////////3.讀取客戶端的報文,等時間是20秒
  if (TcpServer.Read(strRecvBuffer,20)==false) 
  {
    printf("TcpServer.Read() failed.\n"); return -1;
  }
  printf("recv:%s\n",strRecvBuffer);
  printf("recv file ...");

  memset(strSendBuffer,0,sizeof(strSendBuffer));
/////////////////////////////////////////////////////////4.接收文件的內容
  if (RecvFile(strRecvBuffer,TcpServer.m_connfd,argv[2])==true)
  {
    strcpy(strSendBuffer,"ok");
    printf("ok.\n");
  }
  else
  {
    strcpy(strSendBuffer,"failed");
    printf("failed.\n");
  }

//////////////////////////////////////////////5.向客戶端返回響應內容
  if (TcpServer.Write(strSendBuffer)==false) 
  {
    printf("TcpServer.Write() failed.\n"); return -1;
  }
  printf("send:%s\n",strSendBuffer);
  return 0;
}

///////////////////////////////////////////////4.接收文件的內容
bool RecvFile(char *strRecvBuffer,int sockfd,char *strfilename)
{
  int  ufilesize=0;
  char strmtime[20]; 
  memset(strmtime,0,sizeof(strmtime));
  // 獲取待接收的文件的時間和大小
  GetXMLBuffer(strRecvBuffer,"mtime",strmtime);
  GetXMLBuffer(strRecvBuffer,"size",&ufilesize);

  FILE *fp=NULL;
  if ( (fp=fopen(strfilename,"wb")) ==NULL)
  {
    printf("create %s failed.\n",strfilename); return false;
  }

  int  total_bytes=0;
  int  onread=0;
  char buffer[1000];
  while (true)
  {
    memset(buffer,0,sizeof(buffer));
    if ((ufilesize-total_bytes) > 1000) onread=1000; //根據文件大小知道文件接下來讀取多少內容
    else onread=ufilesize-total_bytes;

    if (Readn(sockfd,buffer,onread) == false)
    {
      printf("Readn() failed.\n"); fclose(fp); fp=NULL; return false;
    }
    
    fwrite(buffer,1,onread,fp); //一次讀1個字節讀onread次
    total_bytes = total_bytes + onread;
    if ((int)total_bytes == ufilesize) break;
  }
  fclose(fp);
  // 讀完後重置文件原始的時間,不是本地接收生成的時間
  UTime(strfilename,strmtime);
  return true;
}

如下傳二進制文件
在這裏插入圖片描述

3.文件上傳模塊:重連,認證

一個服務端:同時處理髮送文件和接收數據。兩個客戶端:一個發送文件一個接收文件。下面是客戶端流程,登錄就是把一些信息(比如srvpath參數)告訴服務端我們怎麼傳
在這裏插入圖片描述
如下okfilename缺省爲空,ptype=1時okfilename纔有意義,andchild也缺省爲空。timetvl若超過了60s被交換機斷開,一般網絡管理人員會把TCP連接強制斷開空閒時間爲60s,timetvl要sleep,1秒讀寫對磁盤造成壓力。
在這裏插入圖片描述
服務端流程:接收連接請求後,接收登錄的報文,等待文件描述信息報文(文件名,文件大小,文件時間),等到客戶端這報文後根據報文接收文件,接收完後寫到本地再發回一個確認給客戶端,繼續等待文件信息。服務端用多進程,多線程懶得去處理全局變量問題,因爲服務端程序不需要共享什麼變量

//這是一個通用的功能模塊,採用TCP協議發送文件的客戶端,tcpputfile.cpp。
#include "_public.h"
struct st_arg
{
  char ip[31];              // 服務器端的IP地址。
  int  port;                // 服務器端的端口。
  int  ptype;               // 文件發送成功後文件的處理方式:1-保留文件;2-刪除文件;3-移動到備份目錄。
  char clientpath[301];     // 本地文件存放的根目錄。
  char clientpathbak[301];  // 文件成功發送後,本地文件備份的根目錄,當ptype==3時有效。
  char srvpath[301];        // 服務端文件存放的根目錄。
  bool andchild;            // 是否發送clientpath目錄下各級子目錄的文件,true-是;false-否。
  char matchname[301];      // 待發送文件名的匹配方式,如"*.TXT,*.XML",注意用大寫。
  char okfilename[301];     // 已發送成功文件名清單。
  int  timetvl;             // 掃描本地目錄文件的時間間隔,單位:秒。
} starg;
char strRecvBuffer[TCPBUFLEN+10]; // 接收報文的緩衝區
char strSendBuffer[TCPBUFLEN+10]; // 發送報文的緩衝區
vector<struct st_fileinfo> vlistfile,vlistfile1;
vector<struct st_fileinfo> vokfilename,vokfilename1;
// 把clientpath目錄下的文件加載到vlistfile容器中
bool LoadListFile();
// 把okfilename文件內容加載到vokfilename容器中
bool LoadOKFileName();
// 把vlistfile容器中的文件與vokfilename容器中文件對比,得到兩個容器
// 一、在vlistfile中存在,並已經採集成功的文件vokfilename1
// 二、在vlistfile中存在,新文件或需要重新採集的文件vlistfile1
bool CompVector();
// 把vokfilename1容器中的內容先寫入okfilename文件中,覆蓋之前的舊okfilename文件
bool WriteToOKFileName();
// 如果ptype==1,把採集成功的文件記錄追加到okfilename文件中
bool AppendToOKFileName(struct st_fileinfo *stfileinfo);

CTcpClient TcpClient;
CLogFile logfile;
// 本程序的業務流程主函數
bool _tcpgetfile();
void EXIT(int sig);
// 顯示程序的幫助
void _help(char *argv[]);  
// 把xml解析到參數starg結構中
bool _xmltoarg(char *strxmlbuffer);
// 登錄服務器
bool ClientLogin(const char *argv);
// 向服務端發送心跳報文
bool ActiveTest();
// 實現文件發送的功能
bool _tcpputfiles();
int main(int argc,char *argv[])
{
  if (argc!=3) { _help(argv); return -1; }
  // 關閉全部的信號和輸入輸出
  CloseIOAndSignal();
  // 處理程序退出的信號
  signal(SIGINT,EXIT); signal(SIGTERM,EXIT);
  if (logfile.Open(argv[1],"a+")==false)
  {
    printf("打開日誌文件失敗(%s)。\n",argv[1]); return -1;
  }
  // 把xml解析到參數starg結構中
  if (_xmltoarg(argv[2])==false) return -1;
  while (true)
  {
    // 向服務器發起連接並登錄
    ClientLogin(argv[2]);
    // 實現文件發送的功能,不管返回成功失敗都不理,有通訊錯誤會在ActiveTest()體現出來
    _tcpputfiles();
    if (vlistfile.size()==0) //空着時纔去做心跳和睡眠,不加的話目錄下有新文件生成重新發心跳sleep,不需要
    {
     //向服務端發送心跳報文,通訊失敗會在這函數裏檢查出來,到前面ClientLogin函數重新登錄即自動重連
      ActiveTest();     
      sleep(starg.timetvl);
    }
  }
  return 0;
}
void EXIT(int sig)
{
  logfile.Write("程序退出,sig=%d\n\n",sig);
  TcpClient.Close();
  exit(0);
}

// 顯示程序的幫助
void _help(char *argv[])
{
  printf("\n");
  printf("Using:/htidc/public/bin/tcpputfiles logfilename xmlbuffer\n\n");

  printf("Sample:/htidc/public/bin/tcpputfiles /log/shqx/tcpputfiles_surfdata.log \"<ip>172.16.0.15</ip><port>5010</port><ptype>1</ptype><clientpath>/data/shqx/sdata/surfdata</clientpath><clientpathbak>/data/shqx/sdata/surfdatabak</clientpathbak><srvpath>/data/shqx/tcp/surfdata</srvpath><andchild>true</andchild><matchname>SURF_*.TXT,*.DAT</matchname><okfilename>/data/shqx/tcplist/tcpputfiles_surfdata.xml</okfilename><timetvl>10</timetvl>\"\n\n\n");

  printf("本程序是數據中心的公共功能模塊,採用TCP協議把文件發送給服務端。\n");
  printf("logfilename  本程序運行的日誌文件。\n");
  printf("xmlbuffer    本程序運行的參數,如下:\n");
  printf("ip           服務器端的IP地址。\n");
  printf("port         服務器端的端口。\n");
  printf("ptype        文件發送成功後的處理方式:1-保留文件;2-刪除文件;3-移動到備份目錄。\n");
  printf("clientpath    本地文件存放的根目錄。\n");
  printf("clientpathbak 文件成功發送後,本地文件備份的根目錄,當ptype==3時有效,缺省爲空。\n");
  printf("srvpath      服務端文件存放的根目錄。\n");
  printf("andchild     是否發送clientpath目錄下各級子目錄的文件,true-是;false-否,缺省爲false。\n");
  printf("matchname    待發送文件名的匹配方式,如\"*.TXT,*.XML\",注意用大寫。\n");
  printf("okfilename   已發送成功文件名清單,缺省爲空。\n");
  printf("timetvl      掃描本地目錄文件的時間間隔,單位:秒,取值在1-50之間。\n\n\n");
}

// 把xml解析到參數starg結構中
bool _xmltoarg(char *strxmlbuffer)
{
  memset(&starg,0,sizeof(struct st_arg));
  GetXMLBuffer(strxmlbuffer,"ip",starg.ip);
  if (strlen(starg.ip)==0) { logfile.Write("ip is null.\n"); return false; }
  
  GetXMLBuffer(strxmlbuffer,"port",&starg.port);
  if ( starg.port==0) { logfile.Write("port is null.\n"); return false; }
  
  GetXMLBuffer(strxmlbuffer,"ptype",&starg.ptype);
  if ((starg.ptype!=1)&&(starg.ptype!=2)&&(starg.ptype!=3) ) { logfile.Write("ptype not in (1,2,3).\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"clientpath",starg.clientpath);
  if (strlen(starg.clientpath)==0) { logfile.Write("clientpath is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"clientpathbak",starg.clientpathbak);
  if ((starg.ptype==3)&&(strlen(starg.clientpathbak)==0)) { logfile.Write("clientpathbak is null.\n"); return false; }
  
  GetXMLBuffer(strxmlbuffer,"srvpath",starg.srvpath);
  if (strlen(starg.srvpath)==0) { logfile.Write("srvpath is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"andchild",&starg.andchild);

  GetXMLBuffer(strxmlbuffer,"matchname",starg.matchname);
  if (strlen(starg.matchname)==0) { logfile.Write("matchname is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"okfilename",starg.okfilename);
  if ((starg.ptype==1)&&(strlen(starg.okfilename)==0)) { logfile.Write("okfilename is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"timetvl",&starg.timetvl);
  if (starg.timetvl==0) { logfile.Write("timetvl is null.\n"); return false; }
  if (starg.timetvl>50) starg.timetvl=50; //tcp連接超過60秒容易被交換機斷開
  return true;
}

///////////////////////////////////////////////////////////////1.登錄服務器
bool ClientLogin(const char *argv)
{
  if (TcpClient.m_sockfd>0) return true; //>0表示不用重連,通訊失敗的話socket關掉將m_sockfd置爲-1
  //m_sockfd和m_state連接狀態一樣,大於0不用重連
  int ii=0;
  while (true)
  {
    if (ii++>0) sleep(20);    // 第一次進入循環不休眠
    //tcpputfile.cpp客戶端連接服務端失敗後每20秒重連一次
    // 向服務器發起連接
    if (TcpClient.ConnectToServer(starg.ip,starg.port) == false)
    {
      logfile.Write("TcpClient.ConnectToServer(%s,%d) failed.\n",starg.ip,starg.port); continue;
    }
    memset(strRecvBuffer,0,sizeof(strRecvBuffer));
    memset(strSendBuffer,0,sizeof(strSendBuffer));
    // clienttype爲1表示這個客戶端用來發送文件,2表示客戶端接收服務端文件
    strcpy(strSendBuffer,argv); strcat(strSendBuffer,"<clienttype>1</clienttype>");

    // logfile.Write("strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
    if (TcpClient.Write(strSendBuffer) == false)
    {
      logfile.Write("1 TcpClient.Write() failed.\n"); continue;
    }

    if (TcpClient.Read(strRecvBuffer,20) == false)
    {
      logfile.Write("1 TcpClient.Read() failed.\n"); continue;
    }
    // logfile.Write("strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx
    break;
  }
  logfile.Write("login(%s,%d) ok.\n",starg.ip,starg.port);
  return true;
}

/////////////////////////////////////////////////////2.向服務端發送心跳報文
bool ActiveTest()
{
  memset(strRecvBuffer,0,sizeof(strRecvBuffer));
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  strcpy(strSendBuffer,"<activetest>ok</activetest>");
  // logfile.Write("strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
  if (TcpClient.Write(strSendBuffer) == false)
  {
    logfile.Write("2 TcpClient.Write() failed.\n"); TcpClient.Close(); return false;
  }
  if (TcpClient.Read(strRecvBuffer,20) == false)
  {
    logfile.Write("2 TcpClient.Read() failed.\n"); TcpClient.Close(); return false;
  }
  // logfile.Write("strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx
  if (strcmp(strRecvBuffer,"ok") != 0) { TcpClient.Close(); return false; }
  return true;
}

/////////////////////////////////////////////////////3.實現文件發送的功能
bool _tcpputfiles()
{
  // 把clientpath目錄下的文件加載到vlistfile容器中
  if (LoadListFile()==false)
  {
    logfile.Write("LoadListFile() failed.\n"); return false;
  }

  if (starg.ptype==1)
  {
    // 加載okfilename文件中的內容到容器vokfilename中
    LoadOKFileName();

    // 把vlistfile容器中的文件與vokfilename容器中文件對比,得到兩個容器
    // 一、在vlistfile中存在,並已經採集成功的文件vokfilename1
    // 二、在vlistfile中存在,新文件或需要重新採集的文件vlistfile1
    CompVector();

    // 把vokfilename1容器中的內容先寫入okfilename文件中,覆蓋之前的舊okfilename文件
    WriteToOKFileName();
   
    // 把vlistfile1容器中的內容複製到vlistfile容器中
    vlistfile.clear(); vlistfile.swap(vlistfile1);
  }

  // 把客戶端的新文件或已改動過後的文件發送給服務端
  for (int ii=0;ii<vlistfile.size();ii++)
  {
    logfile.Write("put %s ...",vlistfile[ii].filename);

    // 把文件發送給服務端
    if (SendFile(&logfile,TcpClient.m_sockfd,&vlistfile[ii])==false) 
    {
      logfile.Write("RecvFile() failed.\n"); TcpClient.Close(); return false;
    }

    logfile.WriteEx("ok.\n");

    // 刪除文件
    if (starg.ptype==2) REMOVE(vlistfile[ii].filename);

    // 轉存到備份目錄
    if (starg.ptype==3)
    {
      char strfilenamebak[301];
      memset(strfilenamebak,0,sizeof(strfilenamebak));
      strcpy(strfilenamebak,vlistfile[ii].filename);
//logfile.Write("1%s\n",strfilenamebak);
      UpdateStr(strfilenamebak,starg.clientpath,starg.clientpathbak,false);  // 要小心第三個參數
//logfile.Write("2%s\n",strfilenamebak);
      if (RENAME(vlistfile[ii].filename,strfilenamebak)==false)
      {
        logfile.Write("RENAME %s to %s failed.\n",vlistfile[ii].filename,strfilenamebak); return false;
      }
    }

    // 如果ptype==1,把採集成功的文件記錄追加到okfilename文件中
    if (starg.ptype==1) AppendToOKFileName(&vlistfile[ii]);
  }
  return true;
}

////////////////////////////////////////4.把clientpath目錄下的文件加載到vlistfile容器中
bool LoadListFile()
{
  vlistfile.clear();
  CDir Dir; //dir列出本地文件目錄再加載進容器
  // 注意,如果目錄下的總文件數超過50000,增量發送文件功能將有問題
  if (Dir.OpenDir(starg.clientpath,starg.matchname,50000,starg.andchild,false)==false)
  {
    logfile.Write("Dir.OpenDir(%s) 失敗。\n",starg.clientpath); return false;
  }

  struct st_fileinfo stfileinfo;
  while (true)
  {
    memset(&stfileinfo,0,sizeof(struct st_fileinfo));
    if (Dir.ReadDir()==false) break;
    strcpy(stfileinfo.filename,Dir.m_FullFileName);
    strcpy(stfileinfo.mtime,Dir.m_ModifyTime);
    stfileinfo.filesize=Dir.m_FileSize;    
    vlistfile.push_back(stfileinfo);
    // logfile.Write("vlistfile filename=%s,mtime=%s\n",stfileinfo.filename,stfileinfo.mtime);
  }
  return true;
}

/////////////////////////////////////////5.把okfilename文件內容加載到vokfilename容器中
bool LoadOKFileName()
{
  vokfilename.clear();
  CFile File;
  // 注意:如果程序是第一次採集,okfilename是不存在的,並不是錯誤,所以也返回true。
  if (File.Open(starg.okfilename,"r") == false) return true;
  struct st_fileinfo stfileinfo;
  char strbuffer[301];
  while (true)
  {
    memset(&stfileinfo,0,sizeof(struct st_fileinfo));
    if (File.Fgets(strbuffer,300,true)==false) break;
    GetXMLBuffer(strbuffer,"filename",stfileinfo.filename,300);
    GetXMLBuffer(strbuffer,"mtime",stfileinfo.mtime,20);
    vokfilename.push_back(stfileinfo);
    // logfile.Write("vokfilename filename=%s,mtime=%s\n",stfileinfo.filename,stfileinfo.mtime);
  }
  return true;
}

////////////////////////////////////6.把vlistfile容器中的文件與vokfilename容器中文件對比,得到兩個容器
// 一、在vlistfile中存在,並已經採集成功的文件vokfilename1
// 二、在vlistfile中存在,新文件或需要重新採集的文件vlistfile1
bool CompVector()
{
  vokfilename1.clear();  vlistfile1.clear();
  for (int ii=0;ii<vlistfile.size();ii++)
  {
    int jj=0;
    for (jj=0;jj<vokfilename.size();jj++)
    {
      if ( (strcmp(vlistfile[ii].filename,vokfilename[jj].filename)==0) &&
           (strcmp(vlistfile[ii].mtime,vokfilename[jj].mtime)==0) )
      {
        vokfilename1.push_back(vlistfile[ii]); break;
      }
    }
    if (jj==vokfilename.size())
    {
      vlistfile1.push_back(vlistfile[ii]);
    }
  }
  return true;
}

/////////////////////////////////7.把vokfilename1容器中的內容先寫入okfilename文件中,覆蓋之前的舊okfilename文件
bool WriteToOKFileName()
{
  CFile File;
  if (File.Open(starg.okfilename,"w") == false)
  {
    logfile.Write("File.Open(%s) failed.\n",starg.okfilename); return false;
  }
  for (int ii=0;ii<vokfilename1.size();ii++)
  {
    File.Fprintf("<filename>%s</filename><mtime>%s</mtime>\n",vokfilename1[ii].filename,vokfilename1[ii].mtime);
  }
  return true;
}

////////////////////////////////8.如果ptype==1,把採集成功的文件記錄追加到okfilename文件中
bool AppendToOKFileName(struct st_fileinfo *stfileinfo)
{
  CFile File;
  if (File.Open(starg.okfilename,"a") == false)
  {
    logfile.Write("File.Open(%s) failed.\n",starg.okfilename); return false;
  }
  File.Fprintf("<filename>%s</filename><mtime>%s</mtime>\n",stfileinfo->filename,stfileinfo->mtime);
  return true;
}
//這是一個通用的功能模塊,採用TCP協議實現文件傳輸的服務端,tcpfileserver.cpp多進程。
#include "_public.h"
struct st_arg
{
  char ip[31];              // 服務器端的IP地址。
  int  port;                // 服務器端的端口。
  int  ptype;               // 文件發送成功後文件的處理方式:1-保留文件;2-移動到備份目錄;3-刪除文件。
  char clientpath[301];     // 本地文件存放的根目錄。
  char clientpathbak[301];  // 文件成功發送後,本地文件備份的根目錄,當ptype==2時有效。
  char srvpath[301];        // 服務端文件存放的根目錄。
  char srvpathbak[301];     // 文件成功接收後,服務端文件備份的根目錄,當ptype==2時有效。
  bool andchild;            // 是否發送clientpath目錄下各級子目錄的文件,true-是;false-否。
  char matchname[301];      // 待發送文件名的匹配方式,如"*.TXT,*.XML",注意用大寫。
  char okfilename[301];     // 已發送成功文件名清單。
  int  timetvl;             // 掃描本地目錄文件的時間間隔,單位:秒。
} starg;
// 把xml解析到參數starg結構中
bool _xmltoarg(char *strxmlbuffer);
CTcpServer TcpServer;
CLogFile logfile;
char strRecvBuffer[TCPBUFLEN+10]; // 接收報文的緩衝區
char strSendBuffer[TCPBUFLEN+10]; // 發送報文的緩衝區
int clienttype=0;
// 等待登錄
bool ClientLogin();
// 列出srvpath目錄下文件的清單,返回給客戶端。
bool ListFile();
// 程序退出時調用的函數
void FathEXIT(int sig);
void ChldEXIT(int sig);
// 接收文件主函數
void RecvFilesMain();
// 發送文件主函數
void SendFilesMain();

int main(int argc,char *argv[])
{
  if (argc != 3)
  {
    printf("\n");
    printf("Using:/htidc/public/bin/tcpfileserver logfilename port\n");

    printf("Example:/htidc/public/bin/tcpfileserver /log/shqx/tcpfileserver.log 5010\n\n");
    printf("本程序是一個公共功能模塊,採用TCP/IP傳輸文件的服務端。\n");
    printf("logfilename 日誌文件名。\n");
    printf("port 用於傳輸文件的TCP端口。\n");
    return -1;
  }
  // 關閉全部的信號和輸入輸出
  // 設置信號,在shell狀態下可用 "kill + 進程號" 正常終止些進程
  // 但請不要用 "kill -9 +進程號" 強行終止
  CloseIOAndSignal(); signal(SIGINT,FathEXIT); signal(SIGTERM,FathEXIT);
  // 打開程序運行日誌,這是一個多進程程序共享日誌文件,日誌不能自動切換第三個參數填false
  if (logfile.Open(argv[1],"a+",false) == false)
  {
    printf("logfile.Open(%s) failed.\n",argv[1]); return -1;
  }
  logfile.Write("fileserver started(%s).\n",argv[2]);

  if (TcpServer.InitServer(atoi(argv[2])) == false)
  {
    logfile.Write("TcpServer.InitServer(%s) failed.\n",argv[2]); exit(-1);
  }
  while (true)
  {
    // 等待客戶端的連接
    if (TcpServer.Accept() == false)
    {
      logfile.Write("TcpServer.Accept() failed.\n"); continue;
    }
    // 新的客戶端連接
    if (fork() > 0)
    {
      // 父進程關閉剛建立起來的sock連接,並回到Accept繼續監聽
      TcpServer.CloseClient(); continue;
    }
    // 下面進入子進程的流程
    signal(SIGINT,ChldEXIT); signal(SIGTERM,ChldEXIT);
    // 子進程需要關掉監聽的sock
    TcpServer.CloseListen(); 
    // 等待客戶端的登錄。現在的登錄只是打個招呼,客戶端加更多參數如分配用戶名和密碼,服務端可判斷用戶名和密碼和客戶端ip
    if (ClientLogin() == false) ChldEXIT(0); 
    // 接收文件主函數 // 1爲客戶端發送,服務端接收
    if (clienttype==1) RecvFilesMain(); 
    // 發送文件主函數
    if (clienttype==2) SendFilesMain();
    ChldEXIT(0);
  }
  return 0;
}
// 父進程退出時調用的函數
void FathEXIT(int sig)
{
  if (sig > 0)
  {
    signal(sig,SIG_IGN); logfile.Write("catching the signal(%d).\n",sig);
  }
  TcpServer.CloseListen();
  kill(0,15); //向子進程發送15的信號通知退出
  logfile.Write("fileserver EXIT.\n");
  exit(0);
}
// 子進程退出時調用的函數
void ChldEXIT(int sig)
{
  if (sig > 0) signal(sig,SIG_IGN);
  TcpServer.CloseClient(); // 子進程收到15的信號就關客戶端連接的socket
  exit(0);  // 退出
}

///////////////////////////////////////////////////////////////////1.等待登錄
bool ClientLogin()
{
  memset(strRecvBuffer,0,sizeof(strRecvBuffer));
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  if (TcpServer.Read(strRecvBuffer,20) == false)
  {
    logfile.Write("1 TcpServer.Read() failed.\n"); return false;
  }
  // logfile.Write("1 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx
  GetXMLBuffer(strRecvBuffer,"clienttype",&clienttype);

  if ( (clienttype==1) || (clienttype==2) )
    strcpy(strSendBuffer,"ok");
  else
    strcpy(strSendBuffer,"failed");

  // logfile.Write("1 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
  if (TcpServer.Write(strSendBuffer) == false)
  {
    logfile.Write("1 TcpServer.Write() failed.\n"); return false;
  }
  logfile.Write("%s login %s.\n",TcpServer.GetIP(),strSendBuffer);
  if (strcmp(strSendBuffer,"failed") == 0) return false;
  
  // 把參數解析出來
  _xmltoarg(strRecvBuffer);
  return true;
}

/////////////////////////////////////////////////////////////2.接收文件主函數
void RecvFilesMain()
{
  while (true)
  {
    memset(strRecvBuffer,0,sizeof(strRecvBuffer));
    memset(strSendBuffer,0,sizeof(strSendBuffer));
    if (TcpServer.Read(strRecvBuffer,80) == false)
    {
      logfile.Write("2 TcpServer.Read() failed.\n"); ChldEXIT(-1);
    }
    // logfile.Write("2 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx
    // 處理心跳報文
    if (strstr(strRecvBuffer,"activetest")!=0)
    {
      strcpy(strSendBuffer,"ok");
      // logfile.Write("2 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
      if (TcpServer.Write(strSendBuffer) == false)
      {
        logfile.Write("2 TcpServer.Write() failed.\n"); ChldEXIT(-1);
      }
      continue;
    }

    struct st_fileinfo stfileinfo;
    memset(&stfileinfo,0,sizeof(struct st_fileinfo));

    // 獲取待接收的文件的時間和大小
    GetXMLBuffer(strRecvBuffer,"filename",stfileinfo.filename);
    GetXMLBuffer(strRecvBuffer,"filesize",&stfileinfo.filesize);
    GetXMLBuffer(strRecvBuffer,"mtime",stfileinfo.mtime);
	
    // 把文件名中的clientpath替換成srvpath,要小心第三個參數
    UpdateStr(stfileinfo.filename,starg.clientpath,starg.srvpath,false);
    // 接收文件的內容
    if (RecvFile(&logfile,TcpServer.m_connfd,&stfileinfo)== false)
    {
      logfile.Write("RecvFile() failed.\n"); ChldEXIT(-1);
    }
    logfile.Write("recv %s ok.\n",stfileinfo.filename);
  }
}

////////////////////////////////////////////////////////////////3.發送文件主函數
void SendFilesMain()
{
  while (true)
  {
    memset(strRecvBuffer,0,sizeof(strRecvBuffer));
    if (TcpServer.Read(strRecvBuffer,80) == false)
    {
      logfile.Write("3 TcpServer.Read() failed.\n"); ChldEXIT(-1);
    }
    // logfile.Write("3 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx

    // 處理心跳報文
    if (strstr(strRecvBuffer,"activetest")!=0)
    {
      memset(strSendBuffer,0,sizeof(strSendBuffer));
      strcpy(strSendBuffer,"ok");
      // logfile.Write("3 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
      if (TcpServer.Write(strSendBuffer) == false)
      {
        logfile.Write("3 TcpServer.Write() failed.\n"); ChldEXIT(-1);
      }
      continue;
    }
    // 處理獲取文件列表報文
    if (strcmp(strRecvBuffer,"<list>")==0)
    {
      if (ListFile()==false)
      {
        logfile.Write("ListFile() failed.\n"); ChldEXIT(-1);
      }
      continue;
    }
    // 取文件報文
    if (strncmp(strRecvBuffer,"<filename>",10)==0)
    {
      // 獲取待接收的文件的時間和大小
      struct st_fileinfo stfileinfo;
      memset(&stfileinfo,0,sizeof(struct st_fileinfo));
      GetXMLBuffer(strRecvBuffer,"filename",stfileinfo.filename);
      GetXMLBuffer(strRecvBuffer,"filesize",&stfileinfo.filesize);
      GetXMLBuffer(strRecvBuffer,"mtime",stfileinfo.mtime);
      // 把文件發送給客戶端
      if (SendFile(&logfile,TcpServer.m_connfd,&stfileinfo)==false) ChldEXIT(-1);
      logfile.Write("put %s ...ok.\n",stfileinfo.filename);
      // 刪除服務端的文件
      if (starg.ptype==2) REMOVE(stfileinfo.filename);
      // 備份服務端的文件
      if (starg.ptype==3) 
      {
        char strfilenamebak[301];
        memset(strfilenamebak,0,sizeof(strfilenamebak));
        strcpy(strfilenamebak,stfileinfo.filename);
        UpdateStr(strfilenamebak,starg.srvpath,starg.srvpathbak,false);  // 要小心第三個參數
        if (RENAME(stfileinfo.filename,strfilenamebak)==false)
        {
          logfile.Write("RENAME %s to %s failed.\n",stfileinfo.filename,strfilenamebak); ChldEXIT(-1);
        }
      }
    }
  }
}

/////////////////////////////////////////////////4.把xml解析到參數starg結構中
bool _xmltoarg(char *strxmlbuffer)
{
  memset(&starg,0,sizeof(struct st_arg));
  GetXMLBuffer(strxmlbuffer,"ip",starg.ip);
  GetXMLBuffer(strxmlbuffer,"port",&starg.port);
  GetXMLBuffer(strxmlbuffer,"ptype",&starg.ptype);
  GetXMLBuffer(strxmlbuffer,"clientpath",starg.clientpath);
  GetXMLBuffer(strxmlbuffer,"clientpathbak",starg.clientpathbak);
  GetXMLBuffer(strxmlbuffer,"srvpath",starg.srvpath);
  GetXMLBuffer(strxmlbuffer,"srvpathbak",starg.srvpathbak);
  GetXMLBuffer(strxmlbuffer,"andchild",&starg.andchild);
  GetXMLBuffer(strxmlbuffer,"matchname",starg.matchname);
  GetXMLBuffer(strxmlbuffer,"okfilename",starg.okfilename);
  GetXMLBuffer(strxmlbuffer,"timetvl",&starg.timetvl);
  return true;
}

//////////////////////////////////////////5.列出srvpath目錄下文件的清單,返回給客戶端
bool ListFile()
{
  CDir Dir;
  // 注意,如果目錄下的總文件數超過50000,增量發送文件功能將有問題
  if (Dir.OpenDir(starg.srvpath,starg.matchname,50000,starg.andchild,false)==false)
  {
    logfile.Write("Dir.OpenDir(%s) 失敗。\n",starg.srvpath); return false;
  }
  // 先把文件總數返回給客戶端
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  sprintf(strSendBuffer,"<totalfile>%d</totalfile>",Dir.m_vFileName.size());
  // logfile.Write("4 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
  if (TcpServer.Write(strSendBuffer) == false)
  {
    logfile.Write("4 TcpServer.Write() failed.\n"); return false;
  }
  // 把文件信息一條條的返回給客戶端
  while (true)
  {
    if (Dir.ReadDir()==false) break;
    memset(strSendBuffer,0,sizeof(strSendBuffer));
    sprintf(strSendBuffer,"<filename>%s</filename><mtime>%s</mtime><filesize>%d</filesize>",Dir.m_FullFileName,Dir.m_ModifyTime,Dir.m_FileSize);
    // logfile.Write("5 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
    if (TcpServer.Write(strSendBuffer) == false)
    {
      logfile.Write("5 TcpServer.Write() failed.\n"); return false;
    }
  }
  return true;
}

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
客戶端重連機制的實現即在通信鏈路斷開時不要退出要自動重連(ClientLogin()中),多進程gdb調試不方便所以寫日誌
在這裏插入圖片描述
如下會出現段錯誤,UpdataStr一般用於兩個空格替換爲一個空格
在這裏插入圖片描述
下面測試,tmp.sh中存放啓動客戶端腳本
在這裏插入圖片描述
如下put開始傳文件,.txt.tmp文件不被傳輸
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
對客戶端進行身份認證:我這的接入服務器和外部其他系統間也是採用文件交換傳輸數據方式,實際就是把服務端tcpfileserver.cpp改下加認證信息。客戶端tcpputfile.cpp也是我們提供,但也把客戶端改一下加一些認證信息,登錄報文時加上用戶名和密碼,客戶端認證信息存入下面數據庫表裏
在這裏插入圖片描述
如下在服務端中連數據庫查出,再和客戶端的信息比對,此方法不好
在這裏插入圖片描述
如下方法不用連數據庫,穩定性高,加載xml文件在內存裏實現數據查找和判斷,比表更快
在這裏插入圖片描述

4.文件下載模塊:多線程

在這裏插入圖片描述
文件下載可以將tcpputfile.cpp,tcpfileserver.cpp反過來,雖然全雙工但會出現連不上服務器被上網行爲審計系統攔截
在這裏插入圖片描述
如下記錄即服務端返回的報文直接存放在vlistfile容器中,listfilename就不需要了
在這裏插入圖片描述

//這是一個通用的功能模塊,採用TCP協議獲取文件的客戶端tcpgetfile.cpp
#include "_public.h"
struct st_arg
{
  char ip[31];              // 服務器端的IP地址。
  int  port;                // 服務器端的端口。
  int  ptype;               // 文件獲取成功後文件的處理方式:1-保留文件;2-刪除文件;3-移動到備份目錄。
  char clientpath[301];     // 本地文件存放的根目錄。
  char srvpath[301];        // 服務端文件存放的根目錄。
  char srvpathbak[301];     // 文件成功獲取後,服務端文件備份的根目錄,當ptype==3時有效。
  bool andchild;            // 是否獲取srvpath目錄下各級子目錄的文件,true-是;false-否。
  char matchname[301];      // 待獲取文件名的匹配方式,如"*.TXT,*.XML",注意用大寫。
  char okfilename[301];     // 已獲取成功文件名清單。listfilename不需要了,服務端返回的報文直接放容器裏了
  int  timetvl;             // 掃描本地目錄文件的時間間隔,單位:秒。
} starg;

char strRecvBuffer[TCPBUFLEN+10]; // 接收報文的緩衝區
char strSendBuffer[TCPBUFLEN+10]; // 發送報文的緩衝區
vector<struct st_fileinfo> vlistfile,vlistfile1;
vector<struct st_fileinfo> vokfilename,vokfilename1;
// 把服務端srvpath目錄下的文件加載到vlistfile容器中
bool LoadListFile();
// 把okfilename文件內容加載到vokfilename容器中
bool LoadOKFileName();
// 把vlistfile容器中的文件與vokfilename容器中文件對比,得到兩個容器
// 一、在vlistfile中存在,並已經採集成功的文件vokfilename1
// 二、在vlistfile中存在,新文件或需要重新採集的文件vlistfile1
bool CompVector();
// 把vokfilename1容器中的內容先寫入okfilename文件中,覆蓋之前的舊okfilename文件
bool WriteToOKFileName();
// 如果ptype==1,把採集成功的文件記錄追加到okfilename文件中
bool AppendToOKFileName(struct st_fileinfo *stfileinfo);
CTcpClient TcpClient;
CLogFile logfile;
// 本程序的業務流程主函數
bool _tcpgetfiles();
void EXIT(int sig);
// 顯示程序的幫助
void _help(char *argv[]);  
// 把xml解析到參數starg結構中
bool _xmltoarg(char *strxmlbuffer);
// 登錄服務器
bool ClientLogin(const char *argv);
// 向服務端發送心跳報文
bool ActiveTest();
// 實現文件獲取的功能
bool _tcpgetfiles();

int main(int argc,char *argv[])
{
  if (argc!=3) { _help(argv); return -1; }
  // 關閉全部的信號和輸入輸出
  CloseIOAndSignal();
  // 處理程序退出的信號
  signal(SIGINT,EXIT); signal(SIGTERM,EXIT);
  if (logfile.Open(argv[1],"a+")==false)
  {
    printf("打開日誌文件失敗(%s)。\n",argv[1]); return -1;
  }
  // 把xml解析到參數starg結構中
  if (_xmltoarg(argv[2])==false) return -1;
  while (true)
  {
    // 向服務器發起連接並登錄
    ClientLogin(argv[2]);
    // 實現文件獲取的功能,_tcpgetfiles()出現通訊故障沒有關socket,_tcpgetfiles函數返回後vlistfile容器是不空的
    //循環到了ClientLogin這裏判斷登錄,ClientLogin裏不判斷socket有沒有問題不會去重新登錄,又到_tcpgetfiles死循環
    _tcpgetfiles();
    if (vlistfile.size()==0)
    {
      // 向服務端發送心跳報文
      ActiveTest();     
      sleep(starg.timetvl);
    }
  }
  return 0;
}
void EXIT(int sig)
{
  logfile.Write("程序退出,sig=%d\n\n",sig);
  TcpClient.Close();
  exit(0);
}
// 顯示程序的幫助
void _help(char *argv[])
{
  printf("\n");
  printf("Using:/htidc/public/bin/tcpgetfiles logfilename xmlbuffer\n\n");

  printf("Sample:/htidc/public/bin/tcpgetfiles /log/shqx/tcpgetfiles_surfdata.log \"<ip>172.16.0.15</ip><port>5010</port><ptype>1</ptype><clientpath>/data/shqx/sdata/surfdata</clientpath><srvpath>/data/shqx/tcp/surfdata</srvpath><srvpathbak>/data/shqx/tcp/surfdatabak</srvpathbak><andchild>true</andchild><matchname>SURF_*.TXT,*.DAT</matchname><okfilename>/data/shqx/tcplist/tcpgetfiles_surfdata.xml</okfilename><timetvl>10</timetvl>\"\n\n\n");

  printf("這是一個通用的功能模塊,採用TCP協議獲取文件的客戶端。\n");
  printf("logfilename   本程序運行的日誌文件。\n");
  printf("xmlbuffer     本程序運行的參數,如下:\n");
  printf("ip            服務器端的IP地址。\n");
  printf("port          服務器端的端口。\n");
  printf("clientpath    客戶端文件存放的根目錄。\n");
  printf("srvpath       服務端文件存放的根目錄。\n");
  printf("ptype         文件獲取成功後服務端文件的處理方式:1-保留文件;2-刪除文件;3-移動到備份目錄。\n");
  printf("srvpathbak    文件成功獲取後,服務端文件備份的根目錄,當ptype==3時有效,缺省爲空。\n");
  printf("andchild      是否獲取srvpath目錄下各級子目錄的文件,true-是;false-否,缺省爲false。\n");
  printf("matchname     待獲取文件名的匹配方式,如\"*.TXT,*.XML\",注意用大寫。\n");
  printf("okfilename    已獲取成功文件名清單,缺省爲空。\n");
  printf("timetvl       掃描本地目錄文件的時間間隔,單位:秒,取值在1-50之間。\n\n\n");
}

// 把xml解析到參數starg結構中
bool _xmltoarg(char *strxmlbuffer)
{
  memset(&starg,0,sizeof(struct st_arg));
  GetXMLBuffer(strxmlbuffer,"ip",starg.ip);
  if (strlen(starg.ip)==0) { logfile.Write("ip is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"port",&starg.port);
  if ( starg.port==0) { logfile.Write("port is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"ptype",&starg.ptype);
  if ((starg.ptype!=1)&&(starg.ptype!=2)&&(starg.ptype!=3) ) { logfile.Write("ptype not in (1,2,3).\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"clientpath",starg.clientpath);
  if (strlen(starg.clientpath)==0) { logfile.Write("clientpath is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"srvpathbak",starg.srvpathbak);
  if ((starg.ptype==3)&&(strlen(starg.srvpathbak)==0)) { logfile.Write("srvpathbak is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"srvpath",starg.srvpath);
  if (strlen(starg.srvpath)==0) { logfile.Write("srvpath is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"andchild",&starg.andchild);

  GetXMLBuffer(strxmlbuffer,"matchname",starg.matchname);
  if (strlen(starg.matchname)==0) { logfile.Write("matchname is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"okfilename",starg.okfilename);
  if ((starg.ptype==1)&&(strlen(starg.okfilename)==0)) { logfile.Write("okfilename is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"timetvl",&starg.timetvl);
  if (starg.timetvl==0) { logfile.Write("timetvl is null.\n"); return false; }

  if (starg.timetvl>50) starg.timetvl=50;
  return true;
}

////////////////////////////////////////////////////1.登錄服務器
bool ClientLogin(const char *argv)
{
  if (TcpClient.m_sockfd>0) return true;
  int ii=0;
  while (true)
  {
    if (ii++>0) sleep(20);    // 第一次進入循環不休眠
    // 向服務器發起連接
    if (TcpClient.ConnectToServer(starg.ip,starg.port) == false)
    {
      logfile.Write("TcpClient.ConnectToServer(%s,%d) failed.\n",starg.ip,starg.port); continue;
    }

    memset(strRecvBuffer,0,sizeof(strRecvBuffer));
    memset(strSendBuffer,0,sizeof(strSendBuffer));
    strcpy(strSendBuffer,argv); strcat(strSendBuffer,"<clienttype>2</clienttype>");
    // logfile.Write("1 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
    if (TcpClient.Write(strSendBuffer) == false)
    {
      logfile.Write("1 TcpClient.Write() failed.\n"); continue;
    }

    if (TcpClient.Read(strRecvBuffer,20) == false)
    {
      logfile.Write("1 TcpClient.Read() failed.\n"); continue;
    }
    // logfile.Write("1 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx
    break;
  }
  logfile.Write("login(%s,%d) ok.\n",starg.ip,starg.port);
  return true;
}

//////////////////////////////////////////////2.向服務端發送心跳報文
bool ActiveTest()
{
  memset(strRecvBuffer,0,sizeof(strRecvBuffer));
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  strcpy(strSendBuffer,"<activetest>ok</activetest>");

  // logfile.Write("2 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
  if (TcpClient.Write(strSendBuffer) == false)
  {
    logfile.Write("2 TcpClient.Write() failed.\n"); TcpClient.Close(); return false;
  }

  if (TcpClient.Read(strRecvBuffer,20) == false)
  {
    logfile.Write("2 TcpClient.Read() failed.\n"); TcpClient.Close(); return false;
  }
  // logfile.Write("2 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx

  if (strcmp(strRecvBuffer,"ok") != 0) { TcpClient.Close(); return false; }
  return true;
}

////////////////////////////////////////////3.實現文件獲取的功能
bool _tcpgetfiles()
{
  // 把服務端srvpath目錄下的文件加載到vlistfile容器中
  if (LoadListFile()==false)
  {
    logfile.Write("LoadListFile() failed.\n"); TcpClient.Close(); return false;
  }
  if (starg.ptype==1)
  {
    // 加載okfilename文件中的內容到容器vokfilename中
    LoadOKFileName();
    // 把vlistfile容器中的文件與vokfilename容器中文件對比,得到兩個容器
    // 一、在vlistfile中存在,並已經採集成功的文件vokfilename1
    // 二、在vlistfile中存在,新文件或需要重新採集的文件vlistfile1
    CompVector();
    // 把vokfilename1容器中的內容先寫入okfilename文件中,覆蓋之前的舊okfilename文件
    WriteToOKFileName();   
    // 把vlistfile1容器中的內容複製到vlistfile容器中
    vlistfile.clear(); vlistfile.swap(vlistfile1);
  }
  // 從服務端逐個獲取新文件或已改動過的文件
  for (int ii=0;ii<vlistfile.size();ii++)
  {
    // 向服務端發送將獲取(下載)的文件信息
    memset(strSendBuffer,0,sizeof(strSendBuffer));
    sprintf(strSendBuffer,"<filename>%s</filename><filesize>%d</filesize><mtime>%s</mtime>",vlistfile[ii].filename,vlistfile[ii].filesize,vlistfile[ii].mtime);
    // logfile.Write("3 strSendBuffer=%s\n",strSendBuffer);     // xxxxxx  
    if (TcpClient.Write(strSendBuffer) == false)
    {
      logfile.Write("3 TcpClient.Write() failed.\n"); TcpClient.Close(); return false;
    }

    // 文件信息已知道,此報文有些多餘,但是爲了兼容SendFile和RecvFile函數,對性能不會有影響。
    if (TcpClient.Read(strRecvBuffer) == false)
    {
      logfile.Write("3 TcpClient.Read() failed.\n"); TcpClient.Close(); return false;
    }
    // logfile.Write("3 strRecvBuffer=%s\n",strRecvBuffer);     // xxxxxx  
    
    // 把文件名中的clientpath替換成srvpath,要小心第三個參數
    struct st_fileinfo stfileinfo;
    memset(&stfileinfo,0,sizeof(struct st_fileinfo));
    strcpy(stfileinfo.filename,vlistfile[ii].filename);
    strcpy(stfileinfo.mtime,vlistfile[ii].mtime);
    stfileinfo.filesize=vlistfile[ii].filesize;
    UpdateStr(stfileinfo.filename,starg.srvpath,starg.clientpath);
    logfile.Write("get %s ...",stfileinfo.filename);
    // ptype=1是增量傳輸,對服務端來說什麼都不幹,保留oklistfile是客戶端的事
    // 接收文件的內容
    if (RecvFile(&logfile,TcpClient.m_sockfd,&stfileinfo)== false)
    {
      logfile.Write("RecvFile() failed.\n"); TcpClient.Close(); return false;
    }
    logfile.WriteEx("ok.\n");
    // 如果ptype==1,把採集成功的文件記錄追加到okfilename文件中
    if (starg.ptype==1) AppendToOKFileName(&vlistfile[ii]);
  }
  return true;
}

///////////////////////////////////4.把服務端srvpath目錄下的文件加載到vlistfile容器中
bool LoadListFile()
{
  vlistfile.clear();
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  strcpy(strSendBuffer,"<list>"); //向服務端發<list>,就像向ftp服務端發nlist命令一樣
  // logfile.Write("4 strSendBuffer=%s\n",strSendBuffer);     // xxxxxx  
  if (TcpClient.Write(strSendBuffer) == false)
  {
    logfile.Write("4 TcpClient.Write() failed.\n"); return false;
  }

  memset(strRecvBuffer,0,sizeof(strRecvBuffer));
  if (TcpClient.Read(strRecvBuffer,20) == false)
  {
    logfile.Write("4 TcpClient.Read() failed.\n"); return false;
  }
  // logfile.Write("4 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx
  // Read到的報文就是文件總數
  int totalfile=0; 
  GetXMLBuffer(strRecvBuffer,"totalfile",&totalfile);
  struct st_fileinfo stfileinfo;

  for (int ii=0;ii<totalfile;ii++) //利用循環接收文件清單報文,解析出來放入vlistfile容器裏
  {
    memset(&stfileinfo,0,sizeof(struct st_fileinfo));

    memset(strRecvBuffer,0,sizeof(strRecvBuffer));
    if (TcpClient.Read(strRecvBuffer,20) == false)
    {
      logfile.Write("5 TcpClient.Read() failed.\n"); return false;
    }
    // logfile.Write("5 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx

    GetXMLBuffer(strRecvBuffer,"filename",stfileinfo.filename);
    GetXMLBuffer(strRecvBuffer,"filesize",&stfileinfo.filesize);
    GetXMLBuffer(strRecvBuffer,"mtime",stfileinfo.mtime);    
    vlistfile.push_back(stfileinfo);
    // logfile.Write("vlistfile filename=%s,mtime=%s\n",stfileinfo.filename,stfileinfo.mtime);
  }
  return true;
}

//////////////////////////////////////////5.把okfilename文件內容加載到vokfilename容器中
bool LoadOKFileName()
{
  vokfilename.clear();
  CFile File;
  // 注意:如果程序是第一次採集,okfilename是不存在的,並不是錯誤,所以也返回true。
  if (File.Open(starg.okfilename,"r") == false) return true;
  struct st_fileinfo stfileinfo;
  char strbuffer[301];
  while (true)
  {
    memset(&stfileinfo,0,sizeof(struct st_fileinfo));
    if (File.Fgets(strbuffer,300,true)==false) break;
    GetXMLBuffer(strbuffer,"filename",stfileinfo.filename,300);
    GetXMLBuffer(strbuffer,"mtime",stfileinfo.mtime,20);
    vokfilename.push_back(stfileinfo);
    // logfile.Write("vokfilename filename=%s,mtime=%s\n",stfileinfo.filename,stfileinfo.mtime);
  }
  return true;
}

////////////////////////////6.把vlistfile容器中的文件與vokfilename容器中文件對比,得到兩個容器
// 一、在vlistfile中存在,並已經採集成功的文件vokfilename1
// 二、在vlistfile中存在,新文件或需要重新採集的文件vlistfile1
bool CompVector()
{
  vokfilename1.clear();  vlistfile1.clear();
  for (int ii=0;ii<vlistfile.size();ii++)
  {
    int jj=0;
    for (jj=0;jj<vokfilename.size();jj++)
    {
      if ( (strcmp(vlistfile[ii].filename,vokfilename[jj].filename)==0) &&
           (strcmp(vlistfile[ii].mtime,vokfilename[jj].mtime)==0) )
      {
        vokfilename1.push_back(vlistfile[ii]); break;
      }
    }

    if (jj==vokfilename.size())
    {
      vlistfile1.push_back(vlistfile[ii]);
    }
  }

  /*
  for (int ii=0;ii<vokfilename1.size();ii++)
  {
    logfile.Write("vokfilename1 filename=%s,mtime=%s\n",vokfilename1[ii].filename,vokfilename1[ii].mtime);
  }

  for (int ii=0;ii<vlistfile1.size();ii++)
  {
    logfile.Write("vlistfile1 filename=%s,mtime=%s\n",vlistfile1[ii].filename,vlistfile1[ii].mtime);
  }
  */
  return true;
}

///////////////////7.把vokfilename1容器中的內容先寫入okfilename文件中,覆蓋之前的舊okfilename文件
bool WriteToOKFileName()
{
  CFile File;
  // 注意,打開文件不要採用緩衝機制
  if (File.Open(starg.okfilename,"w",false) == false)
  {
    logfile.Write("File.Open(%s) failed.\n",starg.okfilename); return false;
  }

  for (int ii=0;ii<vokfilename1.size();ii++)
  {
    File.Fprintf("<filename>%s</filename><mtime>%s</mtime>\n",vokfilename1[ii].filename,vokfilename1[ii].mtime);
  }

  return true;
}

////////////////////8.如果ptype==1,把採集成功的文件記錄追加到okfilename文件中
bool AppendToOKFileName(struct st_fileinfo *stfileinfo)
{
  CFile File;
  // 注意,打開文件不要採用緩衝機制
  if (File.Open(starg.okfilename,"a",false) == false)
  {
    logfile.Write("File.Open(%s) failed.\n",starg.okfilename); return false;
  }
  File.Fprintf("<filename>%s</filename><mtime>%s</mtime>\n",stfileinfo->filename,stfileinfo->mtime);
  return true;
}

在這裏插入圖片描述
tcpputfile.cpp和tcpgetfile.cpp都爲單進程,可合爲一個單進程程序,xml參數裏添加一個clienttype參數,clienttype=1調用_tcpputfile(),=2調用_tcpgetfile()。也可改爲多線程支持多任務(一個任務從服務器a目錄下取,另一個任務從服務器b目錄下取),其實在get.sh裏配置兩行腳本(一個a目錄,一個b目錄)就可以了,沒必要整成多進程多線程
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
測試tcpfileserver.cpp性能(tcpfileserver.cpp運行在短信平臺中,外面接入的系統大概有100多個登錄,tcpfileserver.cpp多進程程序會啓動200多個進程【有些系統文件需要的進程】。服務器1G內存,只能接入五十個客戶端)。如下先複製10行將01個位數替換,再將10行整個再複製替換十位數
在這裏插入圖片描述
在這裏插入圖片描述
windows平臺下的客戶端(java或qt做的):1.註冊爲系統服務程序,自啓動減少維護工作量。2.客戶端是一個多線程程序,可配置多個上傳和下載任務。3.客戶端程序運行會產生日誌,這些日誌要自己清理
在這裏插入圖片描述
把文件傳輸服務端改爲多線程,多進程全局對象是不共享,fork一進程複製一份(這一份和別人那份沒關係)。多線程全局對象是共享,共享的不只是數據還有對象:定義全局的不只有變量還有socket連接在一個對象裏(CTcpServer TcpServer),還比如數據庫連接池connection對象,以後不稱全局變量(範圍小)稱爲全局對象
客戶端參數st_arg用全局變量那每一個客戶端線程上來都會變,許多線程共用一個strsendbuffer就不允許單線程初始化爲0。多線程中logfile可以保留爲全局變量,像定義的bool RecvFilesMain這些全局函數,定義全局的變量在這些函數裏直接用,改爲多線程的話這些變量只能用參數傳遞給函數。如下變量需要傳個指針不一定給個指針,指針只是個容器,所有變量都是容器概念,容器裏放水放磚頭都一樣但拿出來方式各自對應
在這裏插入圖片描述
在這裏插入圖片描述
如下如果(long)中改爲(int)則報錯int*到int損失精度
在這裏插入圖片描述
每當有一個新的客戶端連上來後,TcpServer.m_connfd這個值就會改變,比如第一個客戶端連上來後m_connfd=10,第二個客戶端連上來後m_connfd=11,=10這個參數已傳給線程pth_main了,對於main主程序10沒有保留是不知道的,只有線程知道,main主程序11知道。現在有個問題:線程退出時必須關閉自己socket如10,不關的話主程序根本不知道要去關。如果線程自己退出的話可關socket,但如果給整個程序發一個信號,線程是收不到信號的,一個exit全部退了,這些socket都沒關。所以採用一個辦法:所有客戶端socket連接都放入一容器裏vector< int > vclientfd,有新的來就push進去AddClient()。線程退出時需要把socket從容器裏刪掉再關閉socket即RemoveClient(),EXIT()程序退出時關閉容器裏socket。文件傳輸服務端多進程合適但有些資源需共享用多線程

//這是一個通用的功能模塊,採用TCP協議實現文件傳輸的服務端,tcpfileserver1.cpp多線程。
#include "_public.h"
struct st_arg
{
  int clienttype;
  char ip[31];              // 服務器端的IP地址。
  int  port;                // 服務器端的端口。
  int  ptype;               // 文件發送成功後文件的處理方式:1-保留文件;2-移動到備份目錄;3-刪除文件。
  char clientpath[301];     // 本地文件存放的根目錄。
  char clientpathbak[301];  // 文件成功發送後,本地文件備份的根目錄,當ptype==2時有效。
  char srvpath[301];        // 服務端文件存放的根目錄。
  char srvpathbak[301];     // 文件成功接收後,服務端文件備份的根目錄,當ptype==2時有效。
  bool andchild;            // 是否發送clientpath目錄下各級子目錄的文件,true-是;false-否。
  char matchname[301];      // 待發送文件名的匹配方式,如"*.TXT,*.XML",注意用大寫。
  char okfilename[301];     // 已發送成功文件名清單。
  int  timetvl;             // 掃描本地目錄文件的時間間隔,單位:秒。
};
// 把xml解析到參數starg結構中
bool _xmltoarg(char *strxmlbuffer,struct st_arg *starg);
CLogFile logfile;
// 等待登錄
bool ClientLogin(int clientfd,struct st_arg *starg);
// 列出srvpath目錄下文件的清單,返回給客戶端。
bool ListFile(int clientfd,struct st_arg *starg);
// 程序退出時調用的函數
void EXIT(int sig);
// 與客戶端通信線程的主函數
void *pth_main(void *arg);
// 接收文件主函數
bool RecvFilesMain(int clientfd,struct st_arg *starg);
// 發送文件主函數
bool SendFilesMain(int clientfd,struct st_arg *starg);
// 存放客戶端已連接的socket的容器
vector<int> vclientfd;
void AddClient(int clientfd);      // 把客戶端新的socket加入vclientfd容器中
void RemoveClient(int clientfd);   // 關閉客戶端的socket並從vclientfd容器中刪除,

int main(int argc,char *argv[])
{
  if (argc != 3)
  {
    printf("\n");
    printf("Using:/htidc/public/bin/tcpfileserver1 logfilename port\n");

    printf("Example:/htidc/public/bin/tcpfileserver1 /log/shqx/tcpfileserver1.log 5010\n\n");
    printf("本程序是一個公共功能模塊,採用TCP/IP傳輸文件的服務端。\n");
    printf("本程序採用的是多線程的服務端,多進程的服務端程序是tcpfileserver.cpp。\n");
    printf("logfilename 日誌文件名。\n");
    printf("port 用於傳輸文件的TCP端口。\n");
    return -1;
  }

  // 關閉全部的信號和輸入輸出,只在主函數即主線程中設置就可以了
  // 設置信號,在shell狀態下可用 "kill + 進程號" 正常終止些進程
  // 但請不要用 "kill -9 +進程號" 強行終止
  CloseIOAndSignal(); signal(SIGINT,EXIT); signal(SIGTERM,EXIT);
  // 打開程序運行日誌,這是一個多進程程序,日誌不能自動切換
  if (logfile.Open(argv[1],"a+",false) == false)
  {
    printf("logfile.Open(%s) failed.\n",argv[1]); return -1;
  }
  logfile.Write("fileserver started(%s).\n",argv[2]);
  CTcpServer TcpServer; //定義爲局部變量
  if (TcpServer.InitServer(atoi(argv[2])) == false)
  {
    logfile.Write("TcpServer.InitServer(%s) failed.\n",argv[2]); return -1;
  }

  // 保存服務端的listenfd到vclientfd
  AddClient(TcpServer.m_listenfd);
  while (true)
  {
    // 等待客戶端的連接
    if (TcpServer.Accept() == false)
    {
      logfile.Write("TcpServer.Accept() failed.\n"); continue;
    }
    pthread_t pthid;   // 創建一線程,將socket參數傳進去,與新連接上來的客戶端通信
    // int4字節,long8字節,*指針8字節,TcpServer.m_connfd定義的是整數int
    if (pthread_create(&pthid,NULL,pth_main,(void*)(long)TcpServer.m_connfd)!=0)
    { 
      logfile.Write("創建線程失敗,程序退出。n"); close(TcpServer.m_connfd); EXIT(-1);
    }
    logfile.Write("%s is connected.\n",TcpServer.GetIP());
    // 保存每個客戶端的socket到vclientfd
    AddClient(TcpServer.m_connfd);
  }
  return 0;
}
// 退出時調用的函數
void EXIT(int sig)
{
  signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
  if (sig>0) signal(sig,SIG_IGN);
  logfile.Write("tcpfileserver1 exit,sig=%d...\n",sig);
  // 關閉vclientfd容器中全部的socket
  for (int ii=0;ii<vclientfd.size();ii++)
  {
    close(vclientfd[ii]);
  }
  exit(0);
}

////////////////////////////////////////////////////////1.等待登錄
bool ClientLogin(int clientfd,struct st_arg *starg)
{
  int  ibuflen=0;
  char strRecvBuffer[TCPBUFLEN+10]; // 接收報文的緩衝區
  char strSendBuffer[TCPBUFLEN+10]; // 發送報文的緩衝區

  memset(strRecvBuffer,0,sizeof(strRecvBuffer));
  memset(strSendBuffer,0,sizeof(strSendBuffer));

  if (TcpRead(clientfd,strRecvBuffer,&ibuflen,20) == false)
  {
    logfile.Write("1 TcpRead() failed.\n"); return false;
  }
  // logfile.Write("1 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx

  GetXMLBuffer(strRecvBuffer,"clienttype",&starg->clienttype);

  if ( (starg->clienttype==1) || (starg->clienttype==2) )
    strcpy(strSendBuffer,"ok");
  else
    strcpy(strSendBuffer,"failed");

  // logfile.Write("1 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
  if (TcpWrite(clientfd,strSendBuffer) == false)
  {
    logfile.Write("1 TcpWrite() failed.\n"); return false;
  }

  logfile.Write("login %s(clienttype=%d).\n",strSendBuffer,starg->clienttype);
  if (strcmp(strSendBuffer,"failed") == 0) return false;
  // 把參數解析出來
  _xmltoarg(strRecvBuffer,starg);
  return true;
}

//////////////////////////////////////////////////////2.接收文件主函數
bool RecvFilesMain(int clientfd,struct st_arg *starg)
{
  int  ibuflen=0;
  char strRecvBuffer[TCPBUFLEN+10]; // 接收報文的緩衝區
  char strSendBuffer[TCPBUFLEN+10]; // 發送報文的緩衝區
  while (true)
  {
    memset(strRecvBuffer,0,sizeof(strRecvBuffer));
    memset(strSendBuffer,0,sizeof(strSendBuffer));
    if (TcpRead(clientfd,strRecvBuffer,&ibuflen,80) == false)
    {
      logfile.Write("TcpRead() failed.\n"); return false;
    }
    // logfile.Write("2 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx
    // 處理心跳報文
    if (strstr(strRecvBuffer,"activetest")!=0)
    {
      strcpy(strSendBuffer,"ok");
      // logfile.Write("2 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
      if (TcpWrite(clientfd,strSendBuffer) == false)
      {
        logfile.Write("2 TcpWrite() failed.\n"); return false;
      }
      continue;
    }

    struct st_fileinfo stfileinfo;
    memset(&stfileinfo,0,sizeof(struct st_fileinfo));

    // 獲取待接收的文件的時間和大小
    GetXMLBuffer(strRecvBuffer,"filename",stfileinfo.filename);
    GetXMLBuffer(strRecvBuffer,"filesize",&stfileinfo.filesize);
    GetXMLBuffer(strRecvBuffer,"mtime",stfileinfo.mtime);
    // 把文件名中的clientpath替換成srvpath,要小心第三個參數
    UpdateStr(stfileinfo.filename,starg->clientpath,starg->srvpath,false);
    // 接收文件的內容
    if (RecvFile(&logfile,clientfd,&stfileinfo)== false)
    {
      logfile.Write("RecvFile() failed.\n"); return false;
    }
    logfile.Write("recv %s ok.\n",stfileinfo.filename);
  }
  return true;
}

//////////////////////////////////////////////////////3.發送文件主函數
bool SendFilesMain(int clientfd,struct st_arg *starg)
{
  int  ibuflen=0;
  char strRecvBuffer[TCPBUFLEN+10]; // 接收報文的緩衝區
  char strSendBuffer[TCPBUFLEN+10]; // 發送報文的緩衝區
  while (true)
  {
    memset(strRecvBuffer,0,sizeof(strRecvBuffer));
    if (TcpRead(clientfd,strRecvBuffer,&ibuflen,80) == false)
    {
      logfile.Write("TcpRead() failed.\n"); return false;
    }
    // logfile.Write("3 strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx

    // 處理心跳報文
    if (strstr(strRecvBuffer,"activetest")!=0)
    {
      memset(strSendBuffer,0,sizeof(strSendBuffer));
      strcpy(strSendBuffer,"ok");
      // logfile.Write("3 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
      if (TcpWrite(clientfd,strSendBuffer) == false)
      {
        logfile.Write("3 TcpWrite() failed.\n"); return false;
      }
      continue;
    }

    // 處理獲取文件列表報文
    if (strcmp(strRecvBuffer,"<list>")==0)
    {
      if (ListFile(clientfd,starg)==false)
      {
        logfile.Write("ListFile() failed.\n"); return false;
      }
      continue;
    }
    // 取文件報文
    if (strncmp(strRecvBuffer,"<filename>",10)==0)
    {
      // 獲取待接收的文件的時間和大小
      struct st_fileinfo stfileinfo;
      memset(&stfileinfo,0,sizeof(struct st_fileinfo));
      GetXMLBuffer(strRecvBuffer,"filename",stfileinfo.filename);
      GetXMLBuffer(strRecvBuffer,"filesize",&stfileinfo.filesize);
      GetXMLBuffer(strRecvBuffer,"mtime",stfileinfo.mtime);
      // 把文件發送給客戶端
      if (SendFile(&logfile,clientfd,&stfileinfo)==false) return false;
      logfile.Write("put %s ...ok.\n",stfileinfo.filename);
      // 刪除服務端的文件
      if (starg->ptype==2) REMOVE(stfileinfo.filename);
      // 備份服務端的文件
      if (starg->ptype==3) 
      {
        char strfilenamebak[301];
        memset(strfilenamebak,0,sizeof(strfilenamebak));
        strcpy(strfilenamebak,stfileinfo.filename);
        UpdateStr(strfilenamebak,starg->srvpath,starg->srvpathbak,false);  // 要小心第三個參數
        if (RENAME(stfileinfo.filename,strfilenamebak)==false)
        {
          logfile.Write("RENAME %s to %s failed.\n",stfileinfo.filename,strfilenamebak); return false;
        }
      }
    }
  }
  return true;
}

// 把xml解析到參數starg結構中
bool _xmltoarg(char *strxmlbuffer,struct st_arg *starg)
{
  GetXMLBuffer(strxmlbuffer,"ip",starg->ip);
  GetXMLBuffer(strxmlbuffer,"port",&starg->port);
  GetXMLBuffer(strxmlbuffer,"ptype",&starg->ptype);
  GetXMLBuffer(strxmlbuffer,"clientpath",starg->clientpath);
  GetXMLBuffer(strxmlbuffer,"clientpathbak",starg->clientpathbak);
  GetXMLBuffer(strxmlbuffer,"srvpath",starg->srvpath);
  GetXMLBuffer(strxmlbuffer,"srvpathbak",starg->srvpathbak);
  GetXMLBuffer(strxmlbuffer,"andchild",&starg->andchild);
  GetXMLBuffer(strxmlbuffer,"matchname",starg->matchname);
  GetXMLBuffer(strxmlbuffer,"okfilename",starg->okfilename);
  GetXMLBuffer(strxmlbuffer,"timetvl",&starg->timetvl);
  return true;
}

////////////////////////////////////4.列出srvpath目錄下文件的清單,返回給客戶端。
bool ListFile(int clientfd,struct st_arg *starg)
{
  int  ibuflen=0;
  char strRecvBuffer[TCPBUFLEN+10]; // 接收報文的緩衝區
  char strSendBuffer[TCPBUFLEN+10]; // 發送報文的緩衝區
  CDir Dir;
  // 注意,如果目錄下的總文件數超過50000,增量發送文件功能將有問題
  if (Dir.OpenDir(starg->srvpath,starg->matchname,50000,starg->andchild,false)==false)
  {
    logfile.Write("Dir.OpenDir(%s) 失敗。\n",starg->srvpath); return false;
  }
  // 先把文件總數返回給客戶端
  memset(strSendBuffer,0,sizeof(strSendBuffer));
  sprintf(strSendBuffer,"<totalfile>%d</totalfile>",Dir.m_vFileName.size());
  // logfile.Write("4 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
  if (TcpWrite(clientfd,strSendBuffer) == false)
  {
    logfile.Write("4 TcpWrite() failed.\n"); return false;
  }
  // 把文件信息一條條的返回給客戶端
  while (true)
  {
    if (Dir.ReadDir()==false) break;
    memset(strSendBuffer,0,sizeof(strSendBuffer));
    sprintf(strSendBuffer,"<filename>%s</filename><mtime>%s</mtime><filesize>%d</filesize>",Dir.m_FullFileName,Dir.m_ModifyTime,Dir.m_FileSize);
    // logfile.Write("5 strSendBuffer=%s\n",strSendBuffer);  // xxxxxx
    if (TcpWrite(clientfd,strSendBuffer) == false)
    {
      logfile.Write("5 TcpWrite() failed.\n"); return false;
    }
  }
  return true;
}

//////////////////////////////////5.與客戶端通信線程的主函數
void *pth_main(void *arg)
{
  int clientfd=(long) arg; // arg參數爲新客戶端的socket。
  pthread_detach(pthread_self());
  struct st_arg starg;
  memset(&starg,0,sizeof(struct st_arg));
  // 等待客戶端的登錄
  if (ClientLogin(clientfd,&starg) == false) {  RemoveClient(clientfd); pthread_exit(0); }
  // 接收文件主函數
  if (starg.clienttype==1) 
  {
    if (RecvFilesMain(clientfd,&starg) == false) { RemoveClient(clientfd); pthread_exit(0); }
  }
  // 發送文件主函數
  if (starg.clienttype==2) 
  {
    if (SendFilesMain(clientfd,&starg) == false) { RemoveClient(clientfd); pthread_exit(0); }
  }
  RemoveClient(clientfd); 
  pthread_exit(0);
}

// 把客戶端新的socket加入vclientfd容器中
void AddClient(int clientfd)
{
  vclientfd.push_back(clientfd);
}
// 關閉客戶端的socket並從vclientfd容器中刪除,
void RemoveClient(int clientfd)  
{
  for (int ii=0;ii<vclientfd.size();ii++)
  {
    if (vclientfd[ii]==clientfd) { close(clientfd); vclientfd.erase(vclientfd.begin()+ii); return; }
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章