【C/C++】项目_9_文件传输系统(tcpput/getfile.cpp,tcpfileserver.cpp)

@TOC


1.TCP粘包/超时:全双工,拆粘包,select

采用ftp协议进行文件传输性能不够【FTP协议是TCP/IP协议(五层,拆包)的一部分,严格意义上来说是应用层协议,TCP通信两大瓶颈:带宽,交互次数过多(获取对方服务器时间,文件列表,改名等)】。windows平台ftp安装服务端麻烦,不同ftp服务器在使用时略有区别,兼容性不好【比如ftp.list里*号圆点都可以,有的不行】。系统内部不用ftp,外部用ftp(因为不能把自己程序部署到别人服务器上,别人服务器上也只能装个ftp服务端)。如下比如B站作者将视频文件发到视频上传服务器进行审核,审核完后通过文件传输系统发给视频播放服务器,一个播放服务器是不可能响应那么多播放请求的,肯定是服务器集群。

在这里插入图片描述

下面是短信系统,主要服务器3台,业务系统有发送短信需求的话把文件传输给接入服务器
在这里插入图片描述

为什么要传文件而不是tcp报文(有些数据就是文件方式组织的,比如视频),因为传文件更快。比如一条短信是一个tcp报文,tcp报文传1000条/秒。若以文件传,一个文件存100000条短信记录(1条短信不到1k,两三百字节)
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

发的报文大小大于tcp的一个报文,tcp就帮我们拆包,为了达到高效率,不让tcp拆大的包。发数据时也要考虑tcp协议包大小,一次就发几个字节也让tcp一个包也会影响效率。如下解决粘包拆包:cmpp移动的短信协议报文就是固定长度,定长消息
在这里插入图片描述

在这里插入图片描述

如下是第一种方法,粘包还是会出现,但可以区分开
在这里插入图片描述

如下是第三种方法,自定义分隔符
在这里插入图片描述

如下tcp报文有报文格式,tcp底层把报文做拆分
在这里插入图片描述

在这里插入图片描述

如下在_public.h客户端
在这里插入图片描述

在这里插入图片描述

如下在_public.h服务端,InitServer函数中没指定ip,说明哪任何ip都能连
在这里插入图片描述

在这里插入图片描述

如下是_public.h定义的几个方法,TcpRead函数调用recv函数,客户端连上启动一进程消耗资源,客户端因网络或其他原因断开,但服务端recv函数不知道。如下TcpRead方法最后调用recv函数,recv会阻塞一直等待,实际开发不会等,加上一个超时机制:比如一分钟还没给我发信息的话,我会断开你释放资源进程退出。ReadnWriten是对recv和send扩展。
TcpWrite函数中我们可写入C语言格式字符串(以空字符结束),也可以写二进制流数据。buflen为空表示写入字符串,buflen不为空写入二进制流数据,写100个字节过去就写100,不管里面什么字符,0也好非0也好,类似于文件操作的read和write,比如gets函数读取一行,read读取指定字节数,不管是不是一行
在这里插入图片描述

如下在_public.h定义解决粘包问题,TCPBUFLEN是自定义的宏,自定义TCP报文格式
在这里插入图片描述

如下在_public.cpp中TcpRead(),TcpWrite(),Readn(),Writen()4个函数实现,5个字节,/0是字符串结尾符。如下有人做法是先发报文头,再发报文体,两次发有交互确认耗时效率低,假设总的报文加起来小于TCP报文长度(默认1448字节)一次就可以。大于TCP报文长度,tcp底层会拆分。如下定义两个临时变量strBufLen和strTBuffer,最后调用Writen发送出去。
报文头为什么发送一个字符串(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读不完整缺点
在这里插入图片描述

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)打印出是否超时
在这里插入图片描述

在这里插入图片描述

服务端分不清客户端是网络原因还是程序自身出问题断开,只知道TCP通道异常


在这里插入图片描述

下面是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,"%s%s%lu",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发送给服务端,并没有接收服务端回应,没必要,减少tcp交互次数
  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; //一次读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.接收ok后,向客户端返回响应内容
  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 vlistfile,vlistfile1;
vector 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 \"172.16.0.1550101/data/shqx/sdata/surfdata/data/shqx/sdata/surfdatabak/data/shqx/tcp/surfdatatrueSURF_*.TXT,*.DAT/data/shqx/tcplist/tcpputfiles_surfdata.xml10\"\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,"1");

    // 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,"ok");
  // 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%s%s\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("%s%s\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()
{ // 可以加更多参数,判断客户端用户名,密码,ip
  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,"")==0)
    {
      if (ListFile()==false)
      {
        logfile.Write("ListFile() failed.\n"); ChldEXIT(-1);
      }
      continue;
    }
    // 取文件报文
    if (strncmp(strRecvBuffer,"",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,"%d",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,"%s%s%d",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 vlistfile,vlistfile1;
vector 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 \"172.16.0.1550101/data/shqx/sdata/surfdata/data/shqx/tcp/surfdata/data/shqx/tcp/surfdatabaktrueSURF_*.TXT,*.DAT/data/shqx/tcplist/tcpgetfiles_surfdata.xml10\"\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,"2");
    // 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,"ok");

  // 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%s%d%s",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,""); //向服务端发,就像向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%s%s\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("%s%s\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知道。现在有个问题:线程pth_main()退出时必须关闭自己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 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;iiclienttype);

  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,"")==0)
    {
      if (ListFile(clientfd,starg)==false)
      {
        logfile.Write("ListFile() failed.\n"); return false;
      }
      continue;
    }
    // 取文件报文
    if (strncmp(strRecvBuffer,"",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,"%d",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,"%s%s%d",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

你可能感兴趣的:(【C/C++】项目_9_文件传输系统(tcpput/getfile.cpp,tcpfileserver.cpp))