IPMsg(飞鸽传书)文件发送源码分析

本文分析了飞鸽传输核心传送过程。
  1. DWORD  WINAPI TMainWin::SendFileThread( void  *_sendFileObj)
  2. {
  3.     SendFileObj *obj = (SendFileObj *)_sendFileObj;
  4.     fd_set      fds;
  5.     fd_set      *rfds = NULL, *wfds = &fds;
  6.     timeval     tv;
  7.      int          sock_ret;
  8.      BOOL         ret = FALSE, completeWait = FALSE;
  9.      // 这里SendFileFunc根据command类型自动选择两种函数 : send file or send directory
  10.      BOOL         (TMainWin::*SendFileFunc)(SendFileObj *obj) =
  11.                 obj->command == IPMSG_GETDIRFILES ? TMainWin::SendDirFile : TMainWin::SendFile;
  12.     FD_ZERO(&fds);
  13.     FD_SET(obj->conInfo->sd, &fds);
  14.      // 这里for条件引入了一个简单的超时机制
  15.      // 正常情况下,只要文件未传送完,循环不会退出
  16.      for  ( int  waitCnt=0; waitCnt < 180 && obj->hThread != NULL; waitCnt++)
  17.     {
  18.         tv.tv_sec = 1, tv.tv_usec = 0;
  19.          // 这里select有什么用途呢? 对于select功能我还不是完全明白
  20.          // 根据我的分析,这里主要是利用了select函数的等待功能
  21.          // 如果sd描述符没有就绪,则在select中最久等待1秒
  22.          // 如此反复等待最多180次,也就是3分钟,超过三分钟后,for循环结束
  23.          if  ((sock_ret = ::select(obj->conInfo->sd + 1, rfds, wfds, NULL, &tv)) > 0)
  24.         {
  25.              // 套接字可用,清除等待
  26.             waitCnt = 0;
  27.              //下面的代码是一个有限状态机
  28.              /*
  29.             * 控制变量 completeWait, obj->status
  30.             * 状态机迁移过程(一般状态下):
  31.             * (1)  if ((mainWin->*SendFileFunc)(obj) != TRUE)
  32.             * (2)  if (obj->status == FS_COMPLETE)
  33.             * (3)  if (completeWait)
  34.             *
  35.             * 1首先被反复执行,直到文件发送完毕。
  36.             * 在没有错误的情况下,1总是等价于if(false)
  37.             * 于是其后的2每次都会被执行,判断是否完成
  38.             * 一旦完成,completeWait被设置为True,
  39.             * 在下一次循环里,进入3
  40.             * 在3的语句体内执行recv函数,等待对方应答
  41.             * 
  42.             */
  43.              if  (completeWait)
  44.             {
  45.                  // 本分支在文件发送完后执行
  46.                  if  (::recv(obj->conInfo->sd, ( char  *)&ret,  sizeof (ret), 0) >= 0)
  47.                     ret = TRUE;
  48.                  break ;
  49.             }
  50.              else   if  ((mainWin->*SendFileFunc)(obj) != TRUE)
  51.             {
  52.                  //本分支仅在发送出错时进行
  53.                  break ;  
  54.             }
  55.              else   if  (obj->status == FS_COMPLETE)
  56.             {
  57.                  // 本分支在发送完成后执行
  58.                 completeWait = TRUE, rfds = &fds, wfds = NULL;
  59.                  if  (obj->fileSize == 0) { ret = TRUE;  break ; }
  60.             }
  61.         }
  62.          else   if  (sock_ret == 0) {
  63.              // select超时,重置fds
  64.             FD_ZERO(&fds);
  65.             FD_SET(obj->conInfo->sd, &fds);
  66.         }
  67.          else   if  (sock_ret == SOCKET_ERROR) {
  68.              // select错误,算了,离去吧~
  69.              break ;
  70.         }
  71.     }
  72.      // 如果发送的是文件夹,还需要擦一下屁股
  73.      if  (obj->isDir)
  74.     {
  75.         mainWin->CloseSendFile(obj);
  76.          while  (--obj->dirCnt >= 0)
  77.             ::FindClose(obj->hDir[obj->dirCnt]);
  78.     }
  79.      // ret是对方发回的返回值,告知发送方是否完成接收
  80.     obj->status = ret ? FS_COMPLETE : FS_ERROR;
  81.      // 发送TCPEVENT消息,关闭句柄
  82.      // 消息处理流程: EventUser->TcpEvent->EndSendFile
  83.     mainWin->PostMessage(WM_TCPEVENT, obj->conInfo->sd, FD_CLOSE);
  84.      // 退出发送线程
  85.     ::ExitThread(0);
  86.      return   0;
  87. }

上面传送数据最重要的一句是:

             else   if  ((mainWin->*SendFileFunc)(obj) != TRUE)

SendFileFunc的实际内容是什么呢?由函数开始赋值的指针知道:

  1. BOOL  TMainWin::SendFile(SendFileObj *obj)
  2. {
  3.      if  (obj == NULL || obj->hFile == INVALID_HANDLE_VALUE)   //判断文件句柄是否合法
  4.          return   FALSE;
  5.      int      size = 0;
  6.     _int64  remain = obj->fileSize - obj->offset;    //取得还需要传递的总字节数
  7.      //传数据
  8.      if  (remain > 0 && (size = ::send(obj->conInfo->sd, obj->mapAddr + (obj->offset % cfg->ViewMax), remain > cfg->TransMax ? cfg->TransMax : ( int )remain, 0)) < 0)
  9.          return   FALSE;
  10.      // 根据本次成功发送的数据量,调整offset
  11.     obj->offset += size;
  12.      // 如果offset等于文件大小了,那么设置obj状态为完成
  13.      // 由于存在传文件夹模式和传文件模式,所以状态分情况设置
  14.      if  (obj->offset == obj->fileSize)
  15.         obj->status = obj->command == IPMSG_GETDIRFILES ? FS_ENDFILE : FS_COMPLETE;
  16.      else   if  ((obj->offset % cfg->ViewMax) == 0) //没有完成,但是已经传送完成了本部分数据映射,需要调整映射窗口
  17.     {
  18.         ::UnmapViewOfFile(obj->mapAddr);     // 删除旧映射
  19.         remain = obj->fileSize - obj->offset;    // 计算新的剩余量
  20.          // 映射下一块,一次8M ,如果只剩下最后一点了,则少于8M (remain)
  21.         obj->mapAddr = ( char  *)::MapViewOfFile(obj->hMap, FILE_MAP_READ, ( int )(obj->offset >> 32), ( int )obj->offset, ( int )(remain > cfg->ViewMax ? cfg->ViewMax : remain));
  22.     }
  23.      // 更新总消耗时间
  24.     obj->conInfo->lastTick = ::GetTickCount();
  25.      return   TRUE;
  26. }

很多朋友向我要飞鸽带注释的源码,实在很抱歉,我只注释了这么多,其余的也没有深入地看。如果你对带注释的源码感兴趣,不妨来这里看看: http://code.google.com/p/ipigeon/ 这是我在GoogleCode上开的一个项目,大家一起来注释飞鸽源码吧!


你可能感兴趣的:(socket,command,File,null)