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; }
}
}