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

本文分析了飞鸽传输核心传送过程。
  1. DWORD WINAPITMainWin::SendFileThread( void *_sendFileObj)
  2. {
  3. SendFileObj*obj=(SendFileObj*)_sendFileObj;
  4. fd_setfds;
  5. fd_set*rfds=NULL,*wfds=&fds;
  6. timevaltv;
  7. int sock_ret;
  8. BOOL ret=FALSE,completeWait=FALSE;
  9. //这里SendFileFunc根据command类型自动选择两种函数:sendfileorsenddirectory
  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. _int64remain=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上开的一个项目,大家一起来注释飞鸽源码吧!


你可能感兴趣的:(源码分析)