本文分析了飞鸽传输核心传送过程。
- DWORD WINAPITMainWin::SendFileThread( void *_sendFileObj)
- {
- SendFileObj*obj=(SendFileObj*)_sendFileObj;
- fd_setfds;
- fd_set*rfds=NULL,*wfds=&fds;
- timevaltv;
- int sock_ret;
- BOOL ret=FALSE,completeWait=FALSE;
- //这里SendFileFunc根据command类型自动选择两种函数:sendfileorsenddirectory
- BOOL (TMainWin::*SendFileFunc)(SendFileObj*obj)=
- obj->command==IPMSG_GETDIRFILES?TMainWin::SendDirFile:TMainWin::SendFile;
- FD_ZERO(&fds);
- FD_SET(obj->conInfo->sd,&fds);
- //这里for条件引入了一个简单的超时机制
- //正常情况下,只要文件未传送完,循环不会退出
- for ( int waitCnt=0;waitCnt<180&&obj->hThread!=NULL;waitCnt++)
- {
- tv.tv_sec=1,tv.tv_usec=0;
- //这里select有什么用途呢?对于select功能我还不是完全明白
- //根据我的分析,这里主要是利用了select函数的等待功能
- //如果sd描述符没有就绪,则在select中最久等待1秒
- //如此反复等待最多180次,也就是3分钟,超过三分钟后,for循环结束
- if ((sock_ret=::select(obj->conInfo->sd+1,rfds,wfds,NULL,&tv))>0)
- {
- //套接字可用,清除等待
- waitCnt=0;
- //下面的代码是一个有限状态机
- /*
- *控制变量completeWait,obj->status
- *状态机迁移过程(一般状态下):
- *(1)if((mainWin->*SendFileFunc)(obj)!=TRUE)
- *(2)if(obj->status==FS_COMPLETE)
- *(3)if(completeWait)
- *
- *1首先被反复执行,直到文件发送完毕。
- *在没有错误的情况下,1总是等价于if(false)
- *于是其后的2每次都会被执行,判断是否完成
- *一旦完成,completeWait被设置为True,
- *在下一次循环里,进入3
- *在3的语句体内执行recv函数,等待对方应答
- *
- */
- if (completeWait)
- {
- //本分支在文件发送完后执行
- if (::recv(obj->conInfo->sd,( char *)&ret, sizeof (ret),0)>=0)
- ret=TRUE;
- break ;
- }
- else if ((mainWin->*SendFileFunc)(obj)!=TRUE)
- {
- //本分支仅在发送出错时进行
- break ;
- }
- else if (obj->status==FS_COMPLETE)
- {
- //本分支在发送完成后执行
- completeWait=TRUE,rfds=&fds,wfds=NULL;
- if (obj->fileSize==0){ret=TRUE; break ;}
- }
- }
- else if (sock_ret==0){
- //select超时,重置fds
- FD_ZERO(&fds);
- FD_SET(obj->conInfo->sd,&fds);
- }
- else if (sock_ret==SOCKET_ERROR){
- //select错误,算了,离去吧~
- break ;
- }
- }
- //如果发送的是文件夹,还需要擦一下屁股
- if (obj->isDir)
- {
- mainWin->CloseSendFile(obj);
- while (--obj->dirCnt>=0)
- ::FindClose(obj->hDir[obj->dirCnt]);
- }
- //ret是对方发回的返回值,告知发送方是否完成接收
- obj->status=ret?FS_COMPLETE:FS_ERROR;
- //发送TCPEVENT消息,关闭句柄
- //消息处理流程:EventUser->TcpEvent->EndSendFile
- mainWin->PostMessage(WM_TCPEVENT,obj->conInfo->sd,FD_CLOSE);
- //退出发送线程
- ::ExitThread(0);
- return 0;
- }
上面传送数据最重要的一句是:
else if ((mainWin->*SendFileFunc)(obj)!=TRUE)
SendFileFunc的实际内容是什么呢?由函数开始赋值的指针知道:
- BOOL TMainWin::SendFile(SendFileObj*obj)
- {
- if (obj==NULL||obj->hFile==INVALID_HANDLE_VALUE) //判断文件句柄是否合法
- return FALSE;
- int size=0;
- _int64remain=obj->fileSize-obj->offset; //取得还需要传递的总字节数
- //传数据
- if (remain>0&&(size=::send(obj->conInfo->sd,obj->mapAddr+(obj->offset%cfg->ViewMax),remain>cfg->TransMax?cfg->TransMax:( int )remain,0))<0)
- return FALSE;
- //根据本次成功发送的数据量,调整offset
- obj->offset+=size;
- //如果offset等于文件大小了,那么设置obj状态为完成
- //由于存在传文件夹模式和传文件模式,所以状态分情况设置
- if (obj->offset==obj->fileSize)
- obj->status=obj->command==IPMSG_GETDIRFILES?FS_ENDFILE:FS_COMPLETE;
- else if ((obj->offset%cfg->ViewMax)==0) //没有完成,但是已经传送完成了本部分数据映射,需要调整映射窗口
- {
- ::UnmapViewOfFile(obj->mapAddr); //删除旧映射
- remain=obj->fileSize-obj->offset; //计算新的剩余量
- //映射下一块,一次8M,如果只剩下最后一点了,则少于8M(remain)
- obj->mapAddr=( char *)::MapViewOfFile(obj->hMap,FILE_MAP_READ,( int )(obj->offset>>32),( int )obj->offset,( int )(remain>cfg->ViewMax?cfg->ViewMax:remain));
- }
- //更新总消耗时间
- obj->conInfo->lastTick=::GetTickCount();
- return TRUE;
- }
很多朋友向我要飞鸽带注释的源码,实在很抱歉,我只注释了这么多,其余的也没有深入地看。如果你对带注释的源码感兴趣,不妨来这里看看:
http://code.google.com/p/ipigeon/ 这是我在GoogleCode上开的一个项目,大家一起来注释飞鸽源码吧!