TeamTalk源碼分析之file_server

一、連接狀況介紹

fileserver開始並不是和客戶端連接的,客戶端是按需連接file_server的。但是file_server與msg_server卻是長連接。先啓動file_server,再啓動msg_server。msg_server初始化的時候,會去嘗試連接file_server的8601端口。連接成功以後,會給file_server發送一個發包詢問file_server偵聽客戶端連接的ip和端口號信息:

[cpp] view plain copy
  1. void CFileServConn::OnConfirm()  
  2. {  
  3.     log("connect to file server success ");  
  4.     m_bOpen = true;  
  5.     m_connect_time = get_tick_count();  
  6.     g_file_server_list[m_serv_idx].reconnect_cnt = MIN_RECONNECT_CNT / 2;  
  7.       
  8.     //連上file_server以後,給file_server發送獲取ip地址的數據包  
  9.     IM::Server::IMFileServerIPReq msg;  
  10.     CImPdu pdu;  
  11.     pdu.SetPBMsg(&msg);  
  12.     pdu.SetServiceId(SID_OTHER);  
  13.     pdu.SetCommandId(CID_OTHER_FILE_SERVER_IP_REQ);  
  14.     SendPdu(&pdu);  
  15. }  

file_server收到該數據包後,將自己的偵聽客戶端連接的ip地址和端口號發包告訴msg_server:

[cpp] view plain copy
  1. void FileMsgServerConn::_HandleGetServerAddressReq(CImPdu* pPdu) {  
  2.     IM::Server::IMFileServerIPRsp msg;  
  3.       
  4.     const std::list<IM::BaseDefine::IpAddr>& addrs = ConfigUtil::GetInstance()->GetAddressList();  
  5.       
  6.     for (std::list<IM::BaseDefine::IpAddr>::const_iterator it=addrs.begin(); it!=addrs.end(); ++it) {  
  7.         IM::BaseDefine::IpAddr* addr = msg.add_ip_addr_list();  
  8.         *addr = *it;  
  9.         log("Upload file_client_conn addr info, ip=%s, port=%d", addr->ip().c_str(), addr->port());  
  10.     }  
  11.       
  12.     SendMessageLite(this, SID_OTHER, CID_OTHER_FILE_SERVER_IP_RSP, pPdu->GetSeqNum(), &msg);  
  13. }  

得到的信息是file_server偵聽的ip地址和端口號,默認配置的端口號是8600。也就是說file_server的8600用於客戶端連接,8601端口用於msg_server連接。這樣,客戶端需要傳輸文件(注意:不是聊天圖片,聊天圖片使用另外一個服務msfs進行傳輸),會先告訴msg_server它需要進行文件傳輸,msg_server收到消息後告訴客戶端,你連file_server來傳輸文件吧,並把file_server的地址和端口號告訴客戶端。客戶端這個時候連接file_server進行文件傳輸。我們來具體看一看這個流程的細節信息:

1. 客戶端發包給msg_server說要進行文件發送


然後選擇一個文件:




pc客戶端發送文件邏輯:

[cpp] view plain copy
  1. //pc客戶端代碼(Modules工程SessionLayout.cpp)  
  2. void SessionLayout::Notify(TNotifyUI& msg)  
  3. {  
  4.     ...  
  5.     //省略無關代碼  
  6.     else if (msg.pSender == m_pBtnsendfile) //文件傳輸  
  7.     {  
  8.         module::UserInfoEntity userInfo;  
  9.         if (!module::getUserListModule()->getUserInfoBySId(m_sId, userInfo))  
  10.         {  
  11.             LOG__(ERR, _T("SendFile can't find the sid"));  
  12.             return;  
  13.         }  
  14.   
  15.         CFileDialog fileDlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST  
  16.             , _T("文件|*.*||"), AfxGetMainWnd());  
  17.         fileDlg.m_ofn.Flags |= OFN_NOCHANGEDIR;  
  18.         fileDlg.DoModal();  
  19.   
  20.         CString sPathName;  
  21.         POSITION nPos = fileDlg.GetStartPosition();  
  22.         if (nPos != NULL)  
  23.         {  
  24.             sPathName = fileDlg.GetNextPathName(nPos);  
  25.         }  
  26.         if (sPathName.IsEmpty())  
  27.             return;  
  28.   
  29.         module::getFileTransferModule()->sendFile(sPathName, m_sId, userInfo.isOnlne());  
  30.     }  
  31.     ...  
  32.     //省略無關代碼  
  33. }  


sPathName是文件的全飾路徑;m_sId是收取文件的用戶id,userInfo.isOnlne()判斷m_sId代表的用戶是否在線,以此來決定這次文件傳輸是在線文件還是離線文件模式。
 
[cpp] view plain copy
  1. BOOL FileTransferModule_Impl::sendFile(IN const CString& sFilePath, IN const std::string& sSendToSID,IN BOOL bOnlineMode)  
  2. {  
  3.     if (TransferFileEntityManager::getInstance()->checkIfIsSending(sFilePath))  
  4.     {  
  5.         return FALSE;  
  6.     }  
  7.     TransferFileEntity fileEntity;  
  8.       
  9.     //獲取文件大小  
  10.     struct __stat64 buffer;  
  11.     _wstat64(sFilePath, &buffer);  
  12.     fileEntity.nFileSize = (UInt32)buffer.st_size;  
  13.     if (0 != fileEntity.nFileSize)  
  14.     {  
  15.         CString strFileName = sFilePath;  
  16.         strFileName.Replace(_T("\\"), _T("/"));//mac上對於路徑字符“\”需要做特殊處理,windows上則可以識別  
  17.         fileEntity.sFileName = util::cStringToString(strFileName);  
  18.         fileEntity.sFromID = module::getSysConfigModule()->userID();  
  19.         fileEntity.sToID = sSendToSID;  
  20.         uint32_t transMode = 0;  
  21.         transMode = bOnlineMode ? IM::BaseDefine::TransferFileType::FILE_TYPE_ONLINE : IM::BaseDefine::TransferFileType::FILE_TYPE_OFFLINE;  
  22.           
  23.         LOG__(DEBG,_T("FileTransferSevice_Impl::sendFile sTaskID = %s"), util::stringToCString(fileEntity.sTaskID));  
  24.   
  25.         imcore::IMLibCoreStartOperationWithLambda(  
  26.             [=]()  
  27.         {  
  28.             IM::File::IMFileReq imFileReq;  
  29.             LOG__(APP, _T("imFileReq,name=%s,size=%d,toId=%s"),util::stringToCString(fileEntity.sFileName)  
  30.                 ,fileEntity.nFileSize,util::stringToCString(fileEntity.sToID));  
  31.             imFileReq.set_from_user_id(util::stringToInt32(fileEntity.sFromID));  
  32.             imFileReq.set_to_user_id(util::stringToInt32(fileEntity.sToID));  
  33.             imFileReq.set_file_name(fileEntity.sFileName);  
  34.             imFileReq.set_file_size(fileEntity.nFileSize);  
  35.             imFileReq.set_trans_mode(static_cast<IM::BaseDefine::TransferFileType>(transMode));  
  36.   
  37.             module::getTcpClientModule()->sendPacket(IM::BaseDefine::ServiceID::SID_FILE  
  38.                 , IM::BaseDefine::FileCmdID::CID_FILE_REQUEST  
  39.                 , &imFileReq);  
  40.         });  
  41.           
  42.         return TRUE;  
  43.     }  
  44.     LOG__(ERR, _T("fileEntity FileSize error,size = %d"), fileEntity.nFileSize);  
  45.     return FALSE;  
  46. }  

上面代碼中組裝的包信息中含有要傳輸的文件路徑、文件大小、發送人id、接收方id、文件傳輸模式(在線還是離線),包的命令號是IM::BaseDefine::FileCmdID::CID_FILE_REQUEST。這個包發給msg_server以後,msg_server應答:

[cpp] view plain copy
  1. void CMsgConn::HandlePdu(CImPdu* pPdu)  
  2. {  
  3. ...  
  4. //省略無關代碼  
  5. case CID_FILE_REQUEST:  
  6.             s_file_handler->HandleClientFileRequest(this, pPdu);  
  7.             break;  
  8. ...  
  9. //省略無關代碼  
  10. }  

[cpp] view plain copy
  1. void CFileHandler::HandleClientFileRequest(CMsgConn* pMsgConn, CImPdu* pPdu)  
  2. {  
  3.     IM::File::IMFileReq msg;  
  4.     CHECK_PB_PARSE_MSG(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()));  
  5.       
  6.     uint32_t from_id = pMsgConn->GetUserId();  
  7.     uint32_t to_id = msg.to_user_id();  
  8.     string file_name = msg.file_name();  
  9.     uint32_t file_size = msg.file_size();  
  10.     uint32_t trans_mode = msg.trans_mode();  
  11.     log("HandleClientFileRequest, %u->%u, fileName: %s, trans_mode: %u.", from_id, to_id, file_name.c_str(), trans_mode);  
  12.       
  13.     CDbAttachData attach(ATTACH_TYPE_HANDLE, pMsgConn->GetHandle());  
  14.     CFileServConn* pFileConn = get_random_file_serv_conn();  
  15.     if (pFileConn)  
  16.     {  
  17.         IM::Server::IMFileTransferReq msg2;  
  18.         msg2.set_from_user_id(from_id);  
  19.         msg2.set_to_user_id(to_id);  
  20.         msg2.set_file_name(file_name);  
  21.         msg2.set_file_size(file_size);  
  22.         msg2.set_trans_mode((IM::BaseDefine::TransferFileType)trans_mode);  
  23.         msg2.set_attach_data(attach.GetBuffer(), attach.GetLength());  
  24.         CImPdu pdu;  
  25.         pdu.SetPBMsg(&msg2);  
  26.         pdu.SetServiceId(SID_OTHER);  
  27.         pdu.SetCommandId(CID_OTHER_FILE_TRANSFER_REQ);  
  28.         pdu.SetSeqNum(pPdu->GetSeqNum());  
  29.           
  30.         if (IM::BaseDefine::FILE_TYPE_OFFLINE == trans_mode)  
  31.         {  
  32.             pFileConn->SendPdu(&pdu);  
  33.         }  
  34.         else //IM::BaseDefine::FILE_TYPE_ONLINE  
  35.         {  
  36.             CImUser* pUser = CImUserManager::GetInstance()->GetImUserById(to_id);  
  37.             if (pUser && pUser->GetPCLoginStatus())//已有對應的賬號pc登錄狀態  
  38.             {  
  39.                 pFileConn->SendPdu(&pdu);  
  40.             }  
  41.             else//無對應用戶的pc登錄狀態,向route_server查詢狀態  
  42.             {  
  43.                 //no pc_client in this msg_server, check it from route_server  
  44.                 CPduAttachData attach_data(ATTACH_TYPE_HANDLE_AND_PDU_FOR_FILE, pMsgConn->GetHandle(), pdu.GetBodyLength(), pdu.GetBodyData());  
  45.                 IM::Buddy::IMUsersStatReq msg3;  
  46.                 msg3.set_user_id(from_id);  
  47.                 msg3.add_user_id_list(to_id);  
  48.                 msg3.set_attach_data(attach_data.GetBuffer(), attach_data.GetLength());  
  49.                 CImPdu pdu2;  
  50.                 pdu2.SetPBMsg(&msg3);  
  51.                 pdu2.SetServiceId(SID_BUDDY_LIST);  
  52.                 pdu2.SetCommandId(CID_BUDDY_LIST_USERS_STATUS_REQUEST);  
  53.                 pdu2.SetSeqNum(pPdu->GetSeqNum());  
  54.                 CRouteServConn* route_conn = get_route_serv_conn();  
  55.                 if (route_conn)  
  56.                 {  
  57.                     route_conn->SendPdu(&pdu2);  
  58.                 }  
  59.             }  
  60.         }  
  61.     }  
  62.     else  
  63.     {  
  64.         log("HandleClientFileRequest, no file server.   ");  
  65.         IM::File::IMFileRsp msg2;  
  66.         msg2.set_result_code(1);  
  67.         msg2.set_from_user_id(from_id);  
  68.         msg2.set_to_user_id(to_id);  
  69.         msg2.set_file_name(file_name);  
  70.         msg2.set_task_id("");  
  71.         msg2.set_trans_mode((IM::BaseDefine::TransferFileType)trans_mode);  
  72.         CImPdu pdu;  
  73.         pdu.SetPBMsg(&msg2);  
  74.         pdu.SetServiceId(SID_FILE);  
  75.         pdu.SetCommandId(CID_FILE_RESPONSE);  
  76.         pdu.SetSeqNum(pPdu->GetSeqNum());  
  77.         pMsgConn->SendPdu(&pdu);  
  78.     }  
  79. }  

這段代碼,很有講究,msg_server會檢測file_server是否已經啓動,如果沒有啓動,則直接發包告訴客戶端,file_server不存在。另外,如果該文件傳輸模式是在線文件,會判斷接收文件的用戶是否和發送用戶在同一臺msg_server上。不在的話,則給route_server發送消息,查找該用戶所在的msg_server(這個不具體介紹了,後面分析route_server會專門介紹的)。否則,會將文件發送請求轉發給file_server,包的命令號是CID_OTHER_FILE_TRANSFER_REQ。file_server收到該請求後,處理如下:

[cpp] view plain copy
  1. void FileMsgServerConn::HandlePdu(CImPdu* pdu) {  
  2.       
  3.      ...  
  4.     //省略無關代碼        
  5.     case CID_OTHER_FILE_TRANSFER_REQ:  
  6.         _HandleMsgFileTransferReq(pdu);  
  7.         break ;  
  8.     ...  
  9.     //省略無關代碼          
  10. }  

[cpp] view plain copy
  1. void FileMsgServerConn::_HandleMsgFileTransferReq(CImPdu* pdu) {  
  2.     IM::Server::IMFileTransferReq transfer_req;  
  3.     CHECK_PB_PARSE_MSG(transfer_req.ParseFromArray(pdu->GetBodyData(), pdu->GetBodyLength()));  
  4.       
  5.       
  6.     uint32_t from_id = transfer_req.from_user_id();  
  7.     uint32_t to_id = transfer_req.to_user_id();  
  8.       
  9.     IM::Server::IMFileTransferRsp transfer_rsp;  
  10.     transfer_rsp.set_result_code(1);  
  11.     transfer_rsp.set_from_user_id(from_id);  
  12.     transfer_rsp.set_to_user_id(to_id);  
  13.     transfer_rsp.set_file_name(transfer_req.file_name());  
  14.     transfer_rsp.set_file_size(transfer_req.file_size());  
  15.     transfer_rsp.set_task_id("");  
  16.     transfer_rsp.set_trans_mode(transfer_req.trans_mode());  
  17.     transfer_rsp.set_attach_data(transfer_req.attach_data());  
  18.   
  19.       
  20.     bool rv = false;  
  21.     do {  
  22.         std::string task_id = GenerateUUID();  
  23.         if (task_id.empty()) {  
  24.             log("Create task id failed");  
  25.             break;  
  26.         }  
  27.         log("trams_mode=%d, task_id=%s, from_id=%d, to_id=%d, file_name=%s, file_size=%d", transfer_req.trans_mode(), task_id.c_str(), from_id, to_id, transfer_req.file_name().c_str(), transfer_req.file_size());  
  28.           
  29.         BaseTransferTask* transfer_task = TransferTaskManager::GetInstance()->NewTransferTask(  
  30.                                                                                               transfer_req.trans_mode(),  
  31.                                                                                               task_id,  
  32.                                                                                               from_id,  
  33.                                                                                               to_id,  
  34.                                                                                               transfer_req.file_name(),  
  35.                                                                                               transfer_req.file_size());  
  36.           
  37.         if (transfer_task == NULL) {  
  38.             // 創建未成功  
  39.             // close connection with msg svr  
  40.             // need_close = true;  
  41.             log("Create task failed");  
  42.             break;  
  43.         }  
  44.           
  45.         transfer_rsp.set_result_code(0);  
  46.         transfer_rsp.set_task_id(task_id);  
  47.         rv = true;  
  48.         // need_seq_no = false;  
  49.           
  50.         log("Create task succeed, task id %s, task type %d, from user %d, to user %d", task_id.c_str(), transfer_req.trans_mode(), from_id, to_id);  
  51.     } while (0);  
  52.       
  53.     ::SendMessageLite(this, SID_OTHER, CID_OTHER_FILE_TRANSFER_RSP, pdu->GetSeqNum(), &transfer_rsp);  
  54.       
  55.     if (!rv) {  
  56.         // 未創建成功,關閉連接  
  57.         Close();  
  58.     }  
  59. }  

上述代碼會爲本次傳輸任務創建一個唯一的標識uuid作爲taskid,然後根據離線文件還是在線文件創建離線文件傳輸任務OfflineTransferTask或者在線文件傳輸任務OnlineTransferTask,並加入一個一個成員變量transfer_tasks_中進行管理:

[cpp] view plain copy
  1. BaseTransferTask* TransferTaskManager::NewTransferTask(uint32_t trans_mode, const std::string& task_id, uint32_t from_user_id, uint32_t to_user_id, const std::string& file_name, uint32_t file_size) {  
  2.     BaseTransferTask* transfer_task = NULL;  
  3.       
  4.     TransferTaskMap::iterator it = transfer_tasks_.find(task_id);  
  5.     if (it==transfer_tasks_.end()) {  
  6.         if (trans_mode == IM::BaseDefine::FILE_TYPE_ONLINE) {  
  7.             transfer_task = new OnlineTransferTask(task_id, from_user_id, to_user_id, file_name, file_size);  
  8.         } else if (trans_mode == IM::BaseDefine::FILE_TYPE_OFFLINE) {  
  9.             transfer_task = new OfflineTransferTask(task_id, from_user_id, to_user_id, file_name, file_size);  
  10.         } else {  
  11.             log("Invalid trans_mode = %d", trans_mode);  
  12.         }  
  13.           
  14.         if (transfer_task) {  
  15.             transfer_tasks_.insert(std::make_pair(task_id, transfer_task));  
  16.         }  
  17.     } else {  
  18.         log("Task existed by task_id=%s, why?????", task_id.c_str());  
  19.     }  
  20.       
  21.     return transfer_task;  
  22. }  

這個map transfer_tasks_是在定時器裏面進行定期處理的,處理的依據是當前任務的狀態,比如已經完成的任務就可以從map中移除了:

[cpp] view plain copy
  1. void TransferTaskManager::OnTimer(uint64_t tick) {  
  2.     for (TransferTaskMap::iterator it = transfer_tasks_.begin(); it != transfer_tasks_.end();) {  
  3.         BaseTransferTask* task = it->second;  
  4.         if (task == NULL) {  
  5.             transfer_tasks_.erase(it++);  
  6.             continue;  
  7.         }  
  8.           
  9.         if (task->state() != kTransferTaskStateWaitingUpload &&  
  10.             task->state() == kTransferTaskStateTransferDone) {  
  11.             long esp = time(NULL) - task->create_time();  
  12.             if (esp > ConfigUtil::GetInstance()->GetTaskTimeout()) {  
  13.                 if (task->GetFromConn()) {  
  14.                     FileClientConn* conn = reinterpret_cast<FileClientConn*>(task->GetFromConn());  
  15.                     conn->ClearTransferTask();  
  16.                 }  
  17.                 if (task->GetToConn()) {  
  18.                     FileClientConn* conn = reinterpret_cast<FileClientConn*>(task->GetToConn());  
  19.                     conn->ClearTransferTask();  
  20.                 }  
  21.                 delete task;  
  22.                 transfer_tasks_.erase(it++);  
  23.                 continue;  
  24.             }  
  25.         }  
  26.           
  27.         ++it;  
  28.     }  
  29. }  

完成這些工作以後,組裝的應答包命令號是CID_OTHER_FILE_TRANSFER_RSP,回覆給msg_server。msg_server收到該應答包後處理:

[cpp] view plain copy
  1. void CFileServConn::HandlePdu(CImPdu* pPdu)  
  2. {  
  3.     switch (pPdu->GetCommandId()) {  
  4.         ...  
  5.     //省略無關代碼  
  6.         case CID_OTHER_FILE_TRANSFER_RSP:  
  7.             _HandleFileMsgTransRsp(pPdu);  
  8.             break;  
  9.         ...  
  10.     //省略無關代碼   
  11.     }  
  12. }  

[cpp] view plain copy
  1. void CFileServConn::_HandleFileMsgTransRsp(CImPdu* pPdu)  
  2. {  
  3.     IM::Server::IMFileTransferRsp msg;  
  4.     CHECK_PB_PARSE_MSG(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()));  
  5.   
  6.     uint32_t result = msg.result_code();  
  7.     uint32_t from_id = msg.from_user_id();  
  8.     uint32_t to_id = msg.to_user_id();  
  9.     string file_name = msg.file_name();  
  10.     uint32_t file_size = msg.file_size();  
  11.     string task_id = msg.task_id();  
  12.     uint32_t trans_mode = msg.trans_mode();  
  13.     CDbAttachData attach((uchar_t*)msg.attach_data().c_str(), msg.attach_data().length());  
  14.     log("HandleFileMsgTransRsp, result: %u, from_user_id: %u, to_user_id: %u, file_name: %s, \  
  15.         task_id: %s, trans_mode: %u. ", result, from_id, to_id,  
  16.         file_name.c_str(), task_id.c_str(), trans_mode);  
  17.   
  18.     const list<IM::BaseDefine::IpAddr>* ip_addr_list = GetFileServerIPList();  
  19.   
  20.     IM::File::IMFileRsp msg2;  
  21.     msg2.set_result_code(result);  
  22.     msg2.set_from_user_id(from_id);  
  23.     msg2.set_to_user_id(to_id);  
  24.     msg2.set_file_name(file_name);  
  25.     msg2.set_task_id(task_id);  
  26.     msg2.set_trans_mode((IM::BaseDefine::TransferFileType)trans_mode);  
  27.     for (list<IM::BaseDefine::IpAddr>::const_iterator it = ip_addr_list->begin(); it != ip_addr_list->end(); it++)  
  28.     {  
  29.         IM::BaseDefine::IpAddr ip_addr_tmp = *it;  
  30.         IM::BaseDefine::IpAddr* ip_addr = msg2.add_ip_addr_list();  
  31.         ip_addr->set_ip(ip_addr_tmp.ip());  
  32.         ip_addr->set_port(ip_addr_tmp.port());  
  33.     }  
  34.     CImPdu pdu;  
  35.     pdu.SetPBMsg(&msg2);  
  36.     pdu.SetServiceId(SID_FILE);  
  37.     pdu.SetCommandId(CID_FILE_RESPONSE);  
  38.     pdu.SetSeqNum(pPdu->GetSeqNum());  
  39.     uint32_t handle = attach.GetHandle();  
  40.       
  41.     CMsgConn* pFromConn = CImUserManager::GetInstance()->GetMsgConnByHandle(from_id, handle);  
  42.     if (pFromConn)  
  43.     {  
  44.         pFromConn->SendPdu(&pdu);  
  45.     }  
  46.       
  47.     if (result == 0)  
  48.     {  
  49.         IM::File::IMFileNotify msg3;  
  50.         msg3.set_from_user_id(from_id);  
  51.         msg3.set_to_user_id(to_id);  
  52.         msg3.set_file_name(file_name);  
  53.         msg3.set_file_size(file_size);  
  54.         msg3.set_task_id(task_id);  
  55.         msg3.set_trans_mode((IM::BaseDefine::TransferFileType)trans_mode);  
  56.         msg3.set_offline_ready(0);  
  57.         for (list<IM::BaseDefine::IpAddr>::const_iterator it = ip_addr_list->begin(); it != ip_addr_list->end(); it++)  
  58.         {  
  59.             IM::BaseDefine::IpAddr ip_addr_tmp = *it;  
  60.             IM::BaseDefine::IpAddr* ip_addr = msg3.add_ip_addr_list();  
  61.             ip_addr->set_ip(ip_addr_tmp.ip());  
  62.             ip_addr->set_port(ip_addr_tmp.port());  
  63.         }  
  64.         CImPdu pdu2;  
  65.         pdu2.SetPBMsg(&msg3);  
  66.         pdu2.SetServiceId(SID_FILE);  
  67.         pdu2.SetCommandId(CID_FILE_NOTIFY);  
  68.           
  69.         //send notify to target user  
  70.         CImUser* pToUser = CImUserManager::GetInstance()->GetImUserById(to_id);  
  71.         if (pToUser)  
  72.         {  
  73.             pToUser->BroadcastPduWithOutMobile(&pdu2);  
  74.         }  
  75.           
  76.         //send to route server  
  77.         CRouteServConn* pRouteConn = get_route_serv_conn();  
  78.         if (pRouteConn) {  
  79.             pRouteConn->SendPdu(&pdu2);  
  80.         }  
  81.     }  
  82. }  

msg_server收到包後,首先裝包數據,並把file_server的ip地址和端口信息帶上,發給請求發文件的客戶端,命令號是CID_FILE_RESPONSE;接着查詢通知接收方有人給其發文件(通知方式也是一樣,如果接收方在該msg_server上,直接發給該用戶;不在的話,發給路由服務route_server)。當然接收到文件發送的端只有pc端,移動端會被過濾掉的,也就是說移動端不會收到發送文件的請求。

我們先看發送方pc客戶端收到應答的邏輯(命令號是CID_FILE_RESPONSE):

[cpp] view plain copy
  1. void FileTransferModule_Impl::onPacket(imcore::TTPBHeader& header, std::string& pbBody)  
  2. {  
  3.     switch (header.getCommandId())  
  4.     {  
  5.     case IM::BaseDefine::FileCmdID::CID_FILE_RESPONSE://發送“文件請求/離線文件”-返回  
  6.         _sendfileResponse(pbBody);  
  7.         break;    
  8.     }  
  9. }  

[cpp] view plain copy
  1. void FileTransferModule_Impl::_sendfileResponse(IN std::string& body)  
  2. {  
  3.     IM::File::IMFileRsp imFileRsp;  
  4.     if (!imFileRsp.ParseFromString(body))  
  5.     {  
  6.         LOG__(ERR, _T("parse failed,body:%s"), util::stringToCString(body));  
  7.         return;  
  8.     }  
  9.   
  10.     UInt32 nResult = imFileRsp.result_code();  
  11.     if (nResult != 0)  
  12.     {  
  13.         LOG__(ERR, _T("_sendfileResponse result != 0"));  
  14.         module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_UPLOAD_FAILED);  
  15.     }  
  16.   
  17.     TransferFileEntity fileEntity;  
  18.     fileEntity.sTaskID = imFileRsp.task_id();  
  19.     assert(!fileEntity.sTaskID.empty());  
  20.     fileEntity.sFromID = util::uint32ToString(imFileRsp.from_user_id());  
  21.     fileEntity.sToID = util::uint32ToString(imFileRsp.to_user_id());  
  22.     fileEntity.sFileName = imFileRsp.file_name();  
  23.     fileEntity.setSaveFilePath(util::stringToCString(fileEntity.sFileName));//發送方文件地址,就是保存地址  
  24.     fileEntity.time = static_cast<UInt32>(time(0));  
  25.     uint32_t transMode = imFileRsp.trans_mode();  
  26.     if (IM::BaseDefine::TransferFileType::FILE_TYPE_ONLINE == transMode)  
  27.     {  
  28.         fileEntity.nClientMode = IM::BaseDefine::ClientFileRole::CLIENT_REALTIME_SENDER;  
  29.     }  
  30.     else if (IM::BaseDefine::TransferFileType::FILE_TYPE_OFFLINE == transMode)  
  31.     {  
  32.         fileEntity.nClientMode = IM::BaseDefine::ClientFileRole::CLIENT_OFFLINE_UPLOAD;  
  33.     }  
  34.     fileEntity.pFileObject = new TransferFile(util::stringToCString(fileEntity.sFileName),FALSE);  
  35.     if (fileEntity.pFileObject)  
  36.     {  
  37.         fileEntity.nFileSize = fileEntity.pFileObject->length();  
  38.     }  
  39.       
  40.     UINT32 nIPCount = imFileRsp.ip_addr_list_size();  
  41.     if (nIPCount <= 0)  
  42.     {  
  43.         return;  
  44.     }  
  45.     const IM::BaseDefine::IpAddr& ipAdd = imFileRsp.ip_addr_list(0);  
  46.     fileEntity.sIP = ipAdd.ip();  
  47.     fileEntity.nPort = ipAdd.port();  
  48.   
  49.     if (!TransferFileEntityManager::getInstance()->pushTransferFileEntity(fileEntity))  
  50.         TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);  
  51.   
  52.     LOG__(DEBG, _T("FileTransferSevice_Impl::準備連接文件服務器 sTaskId = %s"), util::stringToCString(fileEntity.sTaskID));  
  53.     TransferFileEntityManager::getInstance()->openFileSocketByTaskId(fileEntity.sTaskID);  
  54. }  

客戶端在TransferFileEntityManager::getInstance()->openFileSocketByTaskId(fileEntity.sTaskID);裏面實際去連接file_server並嘗試發文件:

[cpp] view plain copy
  1. void TransferFileEntityManager::openFileSocketByTaskId(std::string& taskId)  
  2. {  
  3.     m_fileUIThread->openFileSocketByTaskId(taskId);  
  4. }  

[cpp] view plain copy
  1. void FileTransferUIThread::openFileSocketByTaskId(std::string& taskId)  
  2. {  
  3.     FileTransferSocket* pFileSocket = _findFileSocketByTaskId(taskId);  
  4.     if (!pFileSocket)  
  5.     {  
  6.         pFileSocket = new FileTransferSocket(taskId);  
  7.         m_lstFileTransSockets.push_back(pFileSocket);  
  8.         assert(m_hWnd);  
  9.         ::PostMessage(m_hWnd, WM_FILE_TRANSFER, 0, (LPARAM)pFileSocket);  
  10.     }  
  11. }  

[cpp] view plain copy
  1. LRESULT _stdcall FileTransferUIThread::_WndProc(HWND hWnd, UINT message, WPARAM wparam, LPARAM lparam)  
  2. {  
  3.     if (WM_FILE_TRANSFER == message)  
  4.     {  
  5.         FileTransferSocket* pFileSocket = (FileTransferSocket*)lparam;  
  6.         pFileSocket->startFileTransLink();  
  7.     }  
  8.   
  9.     return ::DefWindowProc(hWnd, message, wparam, lparam);  
  10. }  

[cpp] view plain copy
  1. BOOL FileTransferSocket::startFileTransLink()  
  2. {  
  3.     TransferFileEntity FileInfo;  
  4.     if (TransferFileEntityManager::getInstance()->getFileInfoByTaskId(m_sTaskId, FileInfo))  
  5.     {  
  6.         //大佛:使用msg server 傳過來的IP和端口  
  7.         LOG__(APP, _T("connect IP=%s,Port=%d"), util::stringToCString(FileInfo.sIP), FileInfo.nPort);  
  8.         connect(util::stringToCString(FileInfo.sIP), FileInfo.nPort);  
  9.         //connect(util::stringToCString(module::FILETRANSFER_IP), module::FILETRANSFER_PORT);  
  10.         return TRUE;  
  11.     }  
  12.     LOG__(ERR, _T("can't find the TaskId"));  
  13.     return FALSE;  
  14. }  

注意,這裏只是去連接file_server服務器,連接成功的情況下,會嘗試登錄文件服務器,登錄file_server的命令號是CID_FILE_LOGIN_REQ:

[cpp] view plain copy
  1. void FileTransferSocket::onConnectDone()  
  2. {  
  3.     LOG__(APP, _T("FileTransferSocket::onConnected()"));  
  4.     startHeartbeat();  
  5.   
  6.     TransferFileEntity info;  
  7.     if (!TransferFileEntityManager::getInstance()->getFileInfoByTaskId(m_sTaskId, info))  
  8.     {  
  9.         LOG__(APP, _T("Can't get the file info,task id:%s"),util::stringToCString(m_sTaskId));  
  10.         return;  
  11.     }  
  12.           
  13.     //拉模式文件傳輸,傳輸taskid、token、client_mode  
  14.     IM::File::IMFileLoginReq imFileLoginReq;  
  15.     imFileLoginReq.set_user_id(module::getSysConfigModule()->userId());  
  16.     imFileLoginReq.set_task_id(info.sTaskID);  
  17.     imFileLoginReq.set_file_role(static_cast<IM::BaseDefine::ClientFileRole>(info.nClientMode));  
  18.   
  19.     LOG__(APP, _T("IMFileLoginReq,sTaskID:%s,nClientMode:%d"), util::stringToCString(info.sTaskID), info.nClientMode);  
  20.     //send packet  
  21.     LOG__(APP, _T("IMFileLoginReq,taskId:%s"), util::stringToCString(info.sTaskID));  
  22.     sendPacket(IM::BaseDefine::ServiceID::SID_FILE, IM::BaseDefine::FileCmdID::CID_FILE_LOGIN_REQ, &imFileLoginReq);  
  23.   
  24.     //CImPduClientFileLoginReq pduFileLoginReq(module::getSysConfigModule()->userID().c_str()  
  25.     //  , "", info.sTaskID.c_str(), );  
  26.     //sendPacket(&pduFileLoginReq);  
  27. }  

file_server收到該數據包處理如下:

[cpp] view plain copy
  1. void FileClientConn::HandlePdu(CImPdu* pdu) {  
  2.    
  3.     ...  
  4.     //省略無關代碼  
  5.         case CID_FILE_LOGIN_REQ:  
  6.             _HandleClientFileLoginReq(pdu);  
  7.             break;  
  8. }  

[cpp] view plain copy
  1. void FileClientConn::_HandleClientFileLoginReq(CImPdu* pdu) {  
  2.     IM::File::IMFileLoginReq login_req;  
  3.     CHECK_PB_PARSE_MSG(login_req.ParseFromArray(pdu->GetBodyData(), pdu->GetBodyLength()));  
  4.       
  5.     uint32_t user_id = login_req.user_id();  
  6.     string task_id = login_req.task_id();  
  7.     IM::BaseDefine::ClientFileRole mode = login_req.file_role();  
  8.       
  9.     log("Client login, user_id=%d, task_id=%s, file_role=%d", user_id, task_id.c_str(), mode);  
  10.       
  11.     BaseTransferTask* transfer_task = NULL;  
  12.       
  13.     bool rv = false;  
  14.     do {  
  15.         // 查找任務是否存在  
  16.         transfer_task = TransferTaskManager::GetInstance()->FindByTaskID(task_id);  
  17.           
  18.         if (transfer_task == NULL) {  
  19.             if (mode == CLIENT_OFFLINE_DOWNLOAD) {  
  20.                 // 文件不存在,檢查是否是離線下載,有可能是文件服務器重啓  
  21.                 // 嘗試從磁盤加載  
  22.                 transfer_task = TransferTaskManager::GetInstance()->NewTransferTask(task_id, user_id);  
  23.                 // 需要再次判斷是否加載成功  
  24.                 if (transfer_task == NULL) {  
  25.                     log("Find task id failed, user_id=%u, taks_id=%s, mode=%d", user_id, task_id.c_str(), mode);  
  26.                     break;  
  27.                 }  
  28.             } else {  
  29.                 log("Can't find task_id, user_id=%u, taks_id=%s, mode=%d", user_id, task_id.c_str(), mode);  
  30.                 break;  
  31.             }  
  32.         }  
  33.           
  34.         // 狀態轉換  
  35.         rv = transfer_task->ChangePullState(user_id, mode);  
  36.         if (!rv) {  
  37.             // log();  
  38.             break;  
  39.             //  
  40.         }  
  41.           
  42.         // Ok  
  43.         auth_ = true;  
  44.         transfer_task_ = transfer_task;  
  45.         user_id_ = user_id;  
  46.         // 設置conn  
  47.         transfer_task->SetConnByUserID(user_id, this);  
  48.         rv = true;  
  49.           
  50.     } while (0);  
  51.   
  52.     IM::File::IMFileLoginRsp login_rsp;  
  53.     login_rsp.set_result_code(rv?0:1);  
  54.     login_rsp.set_task_id(task_id);  
  55.   
  56.     ::SendMessageLite(this, SID_FILE, CID_FILE_LOGIN_RES, pdu->GetSeqNum(), &login_rsp);  
  57.   
  58.     if (rv) {  
  59.         if (transfer_task->GetTransMode() == FILE_TYPE_ONLINE) {  
  60.             if (transfer_task->state() == kTransferTaskStateWaitingTransfer) {  
  61.                 CImConn* conn = transfer_task_->GetToConn();  
  62.                 if (conn) {  
  63.                     _StatesNotify(CLIENT_FILE_PEER_READY, task_id, transfer_task_->from_user_id(), conn);  
  64.                 } else {  
  65.                     log("to_conn is close, close me!!!");  
  66.                     Close();  
  67.                 }  
  68.                 // _StatesNotify(CLIENT_FILE_PEER_READY, task_id, user_id, this);  
  69.                 // transfer_task->StatesNotify(CLIENT_FILE_PEER_READY, task_id, user_id_);  
  70.             }  
  71.         } else {  
  72.             if (transfer_task->state() == kTransferTaskStateWaitingUpload) {  
  73.                   
  74.                 OfflineTransferTask* offline = reinterpret_cast<OfflineTransferTask*>(transfer_task);  
  75.                   
  76.                 IM::File::IMFilePullDataReq pull_data_req;  
  77.                 pull_data_req.set_task_id(task_id);  
  78.                 pull_data_req.set_user_id(user_id);  
  79.                 pull_data_req.set_trans_mode(FILE_TYPE_OFFLINE);  
  80.                 pull_data_req.set_offset(0);  
  81.                 pull_data_req.set_data_size(offline->GetNextSegmentBlockSize());  
  82.                   
  83.                 ::SendMessageLite(this, SID_FILE, CID_FILE_PULL_DATA_REQ, &pull_data_req);  
  84.   
  85.                 log("Pull Data Req");  
  86.             }  
  87.         }  
  88.     } else {  
  89.         Close();  
  90.     }  
  91. }  


file_server應答客戶端的命令號是CID_FILE_LOGIN_RES,客戶端收到該包後處理如下:

[cpp] view plain copy
  1. void FileTransferSocket::onReceiveData(const char* data, int32_t size)  
  2. {  
  3.     std::string pbBody;  
  4.     imcore::TTPBHeader pbHeader;   
  5.     try  
  6.     {  
  7.         pbHeader.unSerialize((byte*)data, imcore::HEADER_LENGTH);  
  8.         pbBody.assign(data + imcore::HEADER_LENGTH, size - imcore::HEADER_LENGTH);  
  9.   
  10.         if (IM::BaseDefine::OtherCmdID::CID_OTHER_HEARTBEAT == pbHeader.getCommandId()  
  11.             && IM::BaseDefine::ServiceID::SID_OTHER == pbHeader.getModuleId())  
  12.             return;  
  13.     }  
  14.     catch (CPduException e)  
  15.     {  
  16.         LOG__(ERR, _T("onPacket CPduException serviceId:%d,commandId:%d,errCode:%d")  
  17.             , e.GetModuleId(), e.GetCommandId(), e.GetErrorCode());  
  18.         return;  
  19.     }  
  20.     catch (...)  
  21.     {  
  22.         LOG__(ERR, _T("FileTransferSocket onPacket unknown exception"));  
  23.         return;  
  24.     }  
  25.     UInt16 ncmdid = pbHeader.getCommandId();  
  26.     switch (ncmdid)  
  27.     {      
  28.     case IM::BaseDefine::FileCmdID::CID_FILE_LOGIN_RES:  
  29.         _fileLoginResponse(pbBody);  
  30.         break;  
  31.     //無關代碼省略  
  32.     }  
  33. }  
[cpp] view plain copy
  1. void FileTransferSocket::_fileLoginResponse(IN std::string& body)  
  2. {  
  3.     IM::File::IMFileLoginRsp imFileLoginRsp;  
  4.     if (!imFileLoginRsp.ParseFromString(body))  
  5.     {  
  6.         LOG__(ERR, _T("parse failed,body:%s"), util::stringToCString(body));  
  7.         return;  
  8.     }  
  9.     if (imFileLoginRsp.result_code() != 0)  
  10.     {  
  11.         LOG__(ERR, _T("file server login failed! "));  
  12.         return;  
  13.     }  
  14.     //打開文件  
  15.     std::string taskId = imFileLoginRsp.task_id();  
  16.     TransferFileEntity fileEntity;  
  17.     if (!TransferFileEntityManager::getInstance()->getFileInfoByTaskId(taskId, fileEntity))  
  18.     {  
  19.         LOG__(ERR, _T("file server login:can't find the fileInfo "));  
  20.         return;  
  21.     }  
  22.   
  23.     LOG__(APP, _T("IMFileLoginRsp, file server login succeed"));  
  24.     //提示界面,界面上插入該項  
  25.     if (IM::BaseDefine::ClientFileRole::CLIENT_REALTIME_SENDER == fileEntity.nClientMode  
  26.         || IM::BaseDefine::ClientFileRole::CLIENT_OFFLINE_UPLOAD == fileEntity.nClientMode)  
  27.     {  
  28.         module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILETRANSFER_SENDFILE, fileEntity.sTaskID);  
  29.     }  
  30.     else if (IM::BaseDefine::ClientFileRole::CLIENT_REALTIME_RECVER == fileEntity.nClientMode  
  31.         || IM::BaseDefine::ClientFileRole::CLIENT_OFFLINE_DOWNLOAD == fileEntity.nClientMode)  
  32.     {  
  33.         module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILETRANSFER_REQUEST, fileEntity.sTaskID);  
  34.     }  
  35. }  

至此,不管是離線文件還是在線文件發送,pc客戶端會顯示一個文件進度的對話框:




對於在線文件,需要對端同意接收文件的傳輸,客戶端纔會讀取文件,這個進度條纔會發生變化。而對於離線文件,應該會立馬讀取文件上傳文件數據到服務器。可是哪裏會觸發客戶端讀取文件併發送的邏輯呢?門道在於file_server在收到登錄請求CID_FILE_LOGIN_REQ後,不僅會給客戶端發送登錄應答數據包CID_FILE_LOGIN_RES。還會根據文件的傳輸模式,如果是離線文件則會給客戶端發送拉取文件的數據包CID_FILE_PULL_DATA_REQ,代碼我們已經在上面的FileClientConn::_HandleClientFileLoginReq(CImPdu* pdu)中貼過了,我們再貼一次:

[cpp] view plain copy
  1. void FileClientConn::_HandleClientFileLoginReq(CImPdu* pdu) {  
  2.     IM::File::IMFileLoginReq login_req;  
  3.     CHECK_PB_PARSE_MSG(login_req.ParseFromArray(pdu->GetBodyData(), pdu->GetBodyLength()));  
  4.       
  5.     uint32_t user_id = login_req.user_id();  
  6.     string task_id = login_req.task_id();  
  7.     IM::BaseDefine::ClientFileRole mode = login_req.file_role();  
  8.       
  9.     log("Client login, user_id=%d, task_id=%s, file_role=%d", user_id, task_id.c_str(), mode);  
  10.       
  11.     BaseTransferTask* transfer_task = NULL;  
  12.       
  13.     bool rv = false;  
  14.     do {  
  15.         // 查找任務是否存在  
  16.         transfer_task = TransferTaskManager::GetInstance()->FindByTaskID(task_id);  
  17.           
  18.         if (transfer_task == NULL) {  
  19.             if (mode == CLIENT_OFFLINE_DOWNLOAD) {  
  20.                 // 文件不存在,檢查是否是離線下載,有可能是文件服務器重啓  
  21.                 // 嘗試從磁盤加載  
  22.                 transfer_task = TransferTaskManager::GetInstance()->NewTransferTask(task_id, user_id);  
  23.                 // 需要再次判斷是否加載成功  
  24.                 if (transfer_task == NULL) {  
  25.                     log("Find task id failed, user_id=%u, taks_id=%s, mode=%d", user_id, task_id.c_str(), mode);  
  26.                     break;  
  27.                 }  
  28.             } else {  
  29.                 log("Can't find task_id, user_id=%u, taks_id=%s, mode=%d", user_id, task_id.c_str(), mode);  
  30.                 break;  
  31.             }  
  32.         }  
  33.           
  34.         // 狀態轉換  
  35.         rv = transfer_task->ChangePullState(user_id, mode);  
  36.         if (!rv) {  
  37.             // log();  
  38.             break;  
  39.             //  
  40.         }  
  41.           
  42.         // Ok  
  43.         auth_ = true;  
  44.         transfer_task_ = transfer_task;  
  45.         user_id_ = user_id;  
  46.         // 設置conn  
  47.         transfer_task->SetConnByUserID(user_id, this);  
  48.         rv = true;  
  49.           
  50.     } while (0);  
  51.   
  52.     IM::File::IMFileLoginRsp login_rsp;  
  53.     login_rsp.set_result_code(rv?0:1);  
  54.     login_rsp.set_task_id(task_id);  
  55.   
  56.     ::SendMessageLite(this, SID_FILE, CID_FILE_LOGIN_RES, pdu->GetSeqNum(), &login_rsp);  
  57.   
  58.     if (rv) {  
  59.         if (transfer_task->GetTransMode() == FILE_TYPE_ONLINE) {  
  60.             if (transfer_task->state() == kTransferTaskStateWaitingTransfer) {  
  61.                 CImConn* conn = transfer_task_->GetToConn();  
  62.                 if (conn) {  
  63.                     _StatesNotify(CLIENT_FILE_PEER_READY, task_id, transfer_task_->from_user_id(), conn);  
  64.                 } else {  
  65.                     log("to_conn is close, close me!!!");  
  66.                     Close();  
  67.                 }  
  68.                 // _StatesNotify(CLIENT_FILE_PEER_READY, task_id, user_id, this);  
  69.                 // transfer_task->StatesNotify(CLIENT_FILE_PEER_READY, task_id, user_id_);  
  70.             }  
  71.         } else {  
  72.             if (transfer_task->state() == kTransferTaskStateWaitingUpload) {  
  73.                   
  74.                 OfflineTransferTask* offline = reinterpret_cast<OfflineTransferTask*>(transfer_task);  
  75.                   
  76.                 IM::File::IMFilePullDataReq pull_data_req;  
  77.                 pull_data_req.set_task_id(task_id);  
  78.                 pull_data_req.set_user_id(user_id);  
  79.                 pull_data_req.set_trans_mode(FILE_TYPE_OFFLINE);  
  80.                 pull_data_req.set_offset(0);  
  81.                 pull_data_req.set_data_size(offline->GetNextSegmentBlockSize());  
  82.                   
  83.                 ::SendMessageLite(this, SID_FILE, CID_FILE_PULL_DATA_REQ, &pull_data_req);  
  84.   
  85.                 log("Pull Data Req");  
  86.             }  
  87.         }  
  88.     } else {  
  89.         Close();  
  90.     }  
  91. }  

pc端收到CID_FILE_PULL_DATA_REQ後,表示這是一個離線文件,就可以直接上傳文件數據了:

[cpp] view plain copy
  1. case IM::BaseDefine::FileCmdID::CID_FILE_PULL_DATA_REQ://發文件  
  2.         _filePullDataReqResponse(pbBody);  

[cpp] view plain copy
  1. void FileTransferSocket::_filePullDataReqResponse(IN std::string& body)//發  
  2. {  
  3.     IM::File::IMFilePullDataReq imFilePullDataReq;  
  4.     if (!imFilePullDataReq.ParseFromString(body))  
  5.     {  
  6.         LOG__(ERR, _T("parse failed,body:%s"), util::stringToCString(body));  
  7.         return;  
  8.     }  
  9.     UInt32 fileSize = imFilePullDataReq.data_size();  
  10.     UInt32 fileOffset = imFilePullDataReq.offset();  
  11.     std::string taskId = imFilePullDataReq.task_id();  
  12.       
  13.     TransferFileEntity fileEntity;  
  14.     if (!TransferFileEntityManager::getInstance()->getFileInfoByTaskId(taskId, fileEntity))  
  15.     {  
  16.         LOG__(ERR, _T("PullDataReqResponse: can't find the fileInfo"));  
  17.         return;  
  18.     }  
  19.     LOG__(DEBG, _T("send:taskId=%s,filesize=%d,name=%s,BolckSize=%d")  
  20.         ,util::stringToCString(fileEntity.sTaskID)  
  21.         ,fileEntity.nFileSize  
  22.         ,fileEntity.getRealFileName()  
  23.         ,fileSize);  
  24.     std::string buff;  
  25.     if (nullptr == fileEntity.pFileObject)  
  26.     {  
  27.         LOG__(ERR, _T("PullDataReqResponse: file boject Destoryed!"));  
  28.         return;  
  29.     }  
  30.     fileEntity.pFileObject->readBlock(fileOffset, fileSize, buff);//讀取本地文件的數據塊  
  31.     IM::File::IMFilePullDataRsp imFilePullDataRsp;//todo check  
  32.     imFilePullDataRsp.set_result_code(0);  
  33.     imFilePullDataRsp.set_task_id(taskId);  
  34.     imFilePullDataRsp.set_user_id(util::stringToInt32(fileEntity.sFromID));  
  35.     imFilePullDataRsp.set_offset(fileOffset);  
  36.     imFilePullDataRsp.set_file_data((void*)buff.data(), fileSize);  
  37.   
  38.     //send packet  
  39.     sendPacket(IM::BaseDefine::ServiceID::SID_FILE, IM::BaseDefine::FileCmdID::CID_FILE_PULL_DATA_RSP  
  40.         , &imFilePullDataRsp);  
  41.   
  42.     fileEntity.nProgress = fileOffset + fileSize;  
  43.     if (fileEntity.nProgress < fileEntity.nFileSize)  
  44.     {  
  45.         //更新進度條  
  46.         TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);//保存當前進度  
  47.         module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_UPDATA_PROGRESSBAR  
  48.             , fileEntity.sTaskID);  
  49.     }  
  50.     else//傳輸完成  
  51.     {  
  52.         if (fileEntity.pFileObject)  
  53.         {  
  54.             delete fileEntity.pFileObject;  
  55.             fileEntity.pFileObject = nullptr;  
  56.         }  
  57.         module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_PROGRESSBAR_FINISHED  
  58.             , fileEntity.sTaskID);  
  59.     }  
  60.     TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);  
  61. }  

當然,如果文件比較大,一次發不完也沒關係,在CID_FILE_PULL_DATA_REQ中有當前文件的偏移量,客戶端在讀取文件和應答服務器時也帶上這個偏移量fileOffset,應答給服務器的包是CID_FILE_PULL_DATA_RSP。file_server收到應答後處理:

[cpp] view plain copy
  1. void FileClientConn::HandlePdu(CImPdu* pdu) {  
  2.       
  3.             ...  
  4.     //省略無關代碼  
  5.         case CID_FILE_PULL_DATA_RSP:  
  6.             _HandleClientFilePullFileRsp( pdu);  
  7.             break ;  
  8.                 ...  
  9.     //省略無關代碼  
  10.   
  11. }  


[cpp] view plain copy
  1. void FileClientConn::_HandleClientFilePullFileRsp(CImPdu *pdu) {  
  2.     if (!auth_ || !transfer_task_) {  
  3.         log("auth is false");  
  4.         return;  
  5.     }  
  6.   
  7.     // 只有rsp  
  8.     IM::File::IMFilePullDataRsp pull_data_rsp;  
  9.     CHECK_PB_PARSE_MSG(pull_data_rsp.ParseFromArray(pdu->GetBodyData(), pdu->GetBodyLength()));  
  10.       
  11.     uint32_t user_id = pull_data_rsp.user_id();  
  12.     string task_id = pull_data_rsp.task_id();  
  13.     uint32_t offset = pull_data_rsp.offset();  
  14.     uint32_t data_size = static_cast<uint32_t>(pull_data_rsp.file_data().length());  
  15.     const char* data = pull_data_rsp.file_data().data();  
  16.   
  17.     // log("Recv FilePullFileRsp, user_id=%d, task_id=%s, file_role=%d, offset=%d, datasize=%d", user_id, task_id.c_str(), mode, offset, datasize);  
  18.     log("Recv FilePullFileRsp, task_id=%s, user_id=%u, offset=%u, data_size=%d", task_id.c_str(), user_id, offset, data_size);  
  19.   
  20.     int rv = -1;  
  21.     do {  
  22.         //  
  23.         // 檢查user_id  
  24.         if (user_id != user_id_) {  
  25.             log("Received user_id valid, recv_user_id = %d, transfer_task.user_id = %d, user_id_ = %d", user_id, transfer_task_->from_user_id(), user_id_);  
  26.             break;  
  27.         }  
  28.           
  29.         // 檢查task_id  
  30.         if (transfer_task_->task_id() != task_id) {  
  31.             log("Received task_id valid, recv_task_id = %s, this_task_id = %s", task_id.c_str(), transfer_task_->task_id().c_str());  
  32.             // Close();  
  33.             break;  
  34.         }  
  35.           
  36.         rv = transfer_task_->DoRecvData(user_id, offset, data, data_size);  
  37.         if (rv == -1) {  
  38.             break;  
  39.         }  
  40.           
  41.         if (transfer_task_->GetTransMode() == FILE_TYPE_ONLINE) {  
  42.             // 對於在線,直接轉發  
  43.             OnlineTransferTask* online = reinterpret_cast<OnlineTransferTask*>(transfer_task_);  
  44.             pdu->SetSeqNum(online->GetSeqNum());  
  45.             // online->SetSeqNum(pdu->GetSeqNum());  
  46.   
  47.             CImConn* conn = transfer_task_->GetToConn();  
  48.             if (conn) {  
  49.                 conn->SendPdu(pdu);  
  50.             }  
  51.         } else {  
  52.             // 離線  
  53.             // all packages recved  
  54.             if (rv == 1) {  
  55.                 _StatesNotify(CLIENT_FILE_DONE, task_id, user_id, this);  
  56.                 // Close();  
  57.             } else {  
  58.                 OfflineTransferTask* offline = reinterpret_cast<OfflineTransferTask*>(transfer_task_);  
  59.                   
  60.                 IM::File::IMFilePullDataReq pull_data_req;  
  61.                 pull_data_req.set_task_id(task_id);  
  62.                 pull_data_req.set_user_id(user_id);  
  63.                 pull_data_req.set_trans_mode(static_cast<IM::BaseDefine::TransferFileType>(offline->GetTransMode()));  
  64.                 pull_data_req.set_offset(offline->GetNextOffset());  
  65.                 pull_data_req.set_data_size(offline->GetNextSegmentBlockSize());  
  66.                   
  67.                 ::SendMessageLite(this, SID_FILE, CID_FILE_PULL_DATA_REQ, &pull_data_req);  
  68.                 // log("size not match");  
  69.             }  
  70.         }  
  71.           
  72.     } while (0);  
  73.       
  74.     if (rv!=0) {  
  75.         // -1,出錯關閉  
  76.         //  1, 離線上傳完成  
  77.         Close();  
  78.     }  
  79. }  

如果是在線文件,就直接轉發含有文件數據的包;如果是離線文件,則存入文件服務上,即寫入文件:

[cpp] view plain copy
  1. int OfflineTransferTask::DoRecvData(uint32_t user_id, uint32_t offset, const char* data, uint32_t data_size) {  
  2.     // 離線文件上傳  
  3.       
  4.     int rv = -1;  
  5.       
  6.     do {  
  7.         // 檢查是否發送者  
  8.         if (!CheckFromUserID(user_id)) {  
  9.             log("rsp user_id=%d, but sender_id is %d", user_id, from_user_id_);  
  10.             break;  
  11.         }  
  12.           
  13.         // 檢查狀態  
  14.         if (state_ != kTransferTaskStateWaitingUpload && state_ != kTransferTaskStateUploading) {  
  15.             log("state=%d error, need kTransferTaskStateWaitingUpload or kTransferTaskStateUploading", state_);  
  16.             break;  
  17.         }  
  18.           
  19.         // 檢查offset是否有效  
  20.         if (offset != transfered_idx_*SEGMENT_SIZE) {  
  21.             break;  
  22.         }  
  23.           
  24.         //if (data_size != GetNextSegmentBlockSize()) {  
  25.         //    break;  
  26.         //}  
  27.         // todo  
  28.         // 檢查文件大小  
  29.           
  30.         data_size = GetNextSegmentBlockSize();  
  31.         log("Ready recv data, offset=%d, data_size=%d, segment_size=%d", offset, data_size, sengment_size_);  
  32.           
  33.         if (state_ == kTransferTaskStateWaitingUpload) {  
  34.             if (fp_ == NULL) {  
  35.                 fp_ = OpenByWrite(task_id_, to_user_id_);  
  36.                 if (fp_ == NULL) {  
  37.                     break;  
  38.                 }  
  39.             }  
  40.   
  41.             // 寫文件頭  
  42.             OfflineFileHeader file_header;  
  43.             memset(&file_header, 0, sizeof(file_header));  
  44.             file_header.set_create_time(time(NULL));  
  45.             file_header.set_task_id(task_id_);  
  46.             file_header.set_from_user_id(from_user_id_);  
  47.             file_header.set_to_user_id(to_user_id_);  
  48.             file_header.set_file_name("");  
  49.             file_header.set_file_size(file_size_);  
  50.             fwrite(&file_header, 1, sizeof(file_header), fp_);  
  51.             fflush(fp_);  
  52.   
  53.             state_ = kTransferTaskStateUploading;  
  54.         }  
  55.           
  56.         // 存儲  
  57.         if (fp_ == NULL) {  
  58.             //  
  59.             break;  
  60.         }  
  61.           
  62.         fwrite(data, 1, data_size, fp_);  
  63.         fflush(fp_);  
  64.   
  65.         ++transfered_idx_;  
  66.         SetLastUpdateTime();  
  67.   
  68.         if (transfered_idx_ == sengment_size_) {  
  69.             state_ = kTransferTaskStateUploadEnd;  
  70.             fclose(fp_);  
  71.             fp_ = NULL;  
  72.             rv = 1;  
  73.         } else {  
  74.             rv = 0;  
  75.         }  
  76.     } while (0);  
  77.       
  78.     return rv;  
  79. }  

如此循環,直至文件傳輸完成。當然文件上傳完成後file_server也會斷開與客戶端的連接。

到這裏我們介紹了發送文件方的邏輯,下面我們看看接收方的邏輯,上文中介紹了接收方會收到接收文件的通知CID_FILE_NOTIFY,客戶端處理這個命令號:

[cpp] view plain copy
  1. case IM::BaseDefine::FileCmdID::CID_FILE_NOTIFY://收到“發送文件請求”  
  2. <span style="white-space:pre">  </span>_fileNotify(pbBody);  
[cpp] view plain copy
  1. void FileTransferModule_Impl::_fileNotify(IN std::string& body)  
  2. {  
  3.     IM::File::IMFileNotify imFileNotify;  
  4.     if (!imFileNotify.ParseFromString(body))  
  5.     {  
  6.         LOG__(ERR, _T("parse failed,body:%s"), util::stringToCString(body));  
  7.         return;  
  8.     }  
  9.     TransferFileEntity file;  
  10.     file.sFileName = imFileNotify.file_name();  
  11.     file.sFromID = util::uint32ToString(imFileNotify.from_user_id());  
  12.     file.sToID = util::uint32ToString(imFileNotify.to_user_id());  
  13.     file.sTaskID = imFileNotify.task_id();  
  14.     file.nFileSize = imFileNotify.file_size();  
  15.   
  16.     UINT32 nIPCount = imFileNotify.ip_addr_list_size();  
  17.     if (nIPCount <= 0)  
  18.     {  
  19.         return;  
  20.     }  
  21.     const IM::BaseDefine::IpAddr& ipAdd = imFileNotify.ip_addr_list(0);  
  22.     file.sIP = ipAdd.ip();  
  23.     file.nPort = ipAdd.port();  
  24.   
  25.     uint32_t transMode = imFileNotify.trans_mode();  
  26.     if (IM::BaseDefine::TransferFileType::FILE_TYPE_ONLINE == transMode)  
  27.     {  
  28.         file.nClientMode = IM::BaseDefine::ClientFileRole::CLIENT_REALTIME_RECVER;  
  29.     }  
  30.     else if (IM::BaseDefine::TransferFileType::FILE_TYPE_OFFLINE == transMode)  
  31.     {  
  32.         file.nClientMode = IM::BaseDefine::ClientFileRole::CLIENT_OFFLINE_DOWNLOAD;  
  33.     }  
  34.     file.time = static_cast<UInt32>(time(0));  
  35.     TransferFileEntityManager::getInstance()->pushTransferFileEntity(file);  
  36.     LOG__(DEBG, _T("FileTransferSevice_Impl::給你發文件 sFileID = %s"), util::stringToCString(file.sTaskID));  
  37.   
  38.     if (1 == imFileNotify.offline_ready())  
  39.     {  
  40.         //TODO離線文件傳輸結束  
  41.     }  
  42.   
  43.     //連接服務器  
  44.     TransferFileEntityManager::getInstance()->openFileSocketByTaskId(file.sTaskID);  
  45. }  

其實也就是接收方會去連接文件服務器。連接成功以後,在對應的回調函數裏面觸發顯示接收文件對話框。但是此時實際上還不能接收文件,因爲發送方可能還沒準備好。發送方要準備啥呢?前面我們已經介紹了,我們梳理一下上述流程:

1. 發送方先向msg_server請求發送文件,msg_server轉發給file_server;

2. file_server應答msg_server並告訴msg_server自己的地址和端口號;

3. msg_server收到file_server的應答後,先回復發送方,再轉發給接收方;

4. 發送方接着發送登錄請求給file_server,file_server收到請求決定是否給發送方發送拉取文件的數據包。如果是離線文件,則會立刻給發送方發送拉取文件的數據包;如果是在線文件,則需要等待接收方同意接收。


所以,必須過了步驟4,一直到file_server應答了發送方的登錄文件服務器請求後,發送方纔算準備好。此時,file_server知道發送方已經準備好了,給接收方發送數據包CID_FILE_STATE。接收方收到這個命令號後:

[cpp] view plain copy
  1. case IM::BaseDefine::FileCmdID::CID_FILE_STATE://  
  2.         _fileState(pbBody);  

[cpp] view plain copy
  1. void FileTransferSocket::_fileState(IN std::string& body)  
  2. {  
  3.     IM::File::IMFileState imFileState;  
  4.     if (!imFileState.ParseFromString(body))  
  5.     {  
  6.         LOG__(ERR, _T("parse failed,body:%s"), util::stringToCString(body));  
  7.         return;  
  8.     }  
  9.     UINT32 nfileState = imFileState.state();  
  10.   
  11.     std::string taskId = imFileState.task_id();  
  12.     TransferFileEntity fileEntity;  
  13.     if (!TransferFileEntityManager::getInstance()->getFileInfoByTaskId(taskId, fileEntity))  
  14.     {  
  15.         LOG__(ERR, _T("fileState:can't find the fileInfo "));  
  16.         return;  
  17.     }  
  18.   
  19.     switch (nfileState)  
  20.     {  
  21.     case IM::BaseDefine::ClientFileState::CLIENT_FILE_PEER_READY:  
  22.         LOG__(APP, _T("fileState--CLIENT_FILE_PEER_READY "));  
  23.         break;  
  24.     case IM::BaseDefine::ClientFileState::CLIENT_FILE_CANCEL ://取消的了文件傳輸  
  25.         LOG__(APP, _T("fileState--CLIENT_FILE_CANCEL "));  
  26.         {  
  27.             if (fileEntity.pFileObject)  
  28.             {  
  29.                 delete fileEntity.pFileObject;  
  30.                 fileEntity.pFileObject = nullptr;  
  31.             }  
  32.             TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);  
  33.             module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_UPLOAD_CANCEL, fileEntity.sTaskID);  
  34.         }  
  35.         break;  
  36.     case IM::BaseDefine::ClientFileState::CLIENT_FILE_REFUSE://拒絕了文件  
  37.         LOG__(APP, _T("fileState--CLIENT_FILE_REFUSE "));  
  38.         {  
  39.             if (fileEntity.pFileObject)  
  40.             {  
  41.                 delete fileEntity.pFileObject;  
  42.                 fileEntity.pFileObject = nullptr;  
  43.             }  
  44.             TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);  
  45.             module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_UPLOAD_REJECT, fileEntity.sTaskID);  
  46.         }  
  47.         break;  
  48.     case IM::BaseDefine::ClientFileState::CLIENT_FILE_DONE:  
  49.         LOG__(APP, _T("fileState--CLIENT_FILE_DONE "));  
  50.         if (fileEntity.pFileObject)  
  51.         {  
  52.             delete fileEntity.pFileObject;  
  53.             fileEntity.pFileObject = nullptr;  
  54.         }  
  55.         TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);  
  56.         module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_PROGRESSBAR_FINISHED, fileEntity.sTaskID);  
  57.         break;  
  58.     default:  
  59.         break;  
  60.     }  
  61. }  

同理,對於接收方,選擇接收還是拒絕文件的邏輯也是在這裏一起處理的,與此類似,這裏就不再重複敘述了。

接收方下載文件的邏輯和發送方上傳文件的邏輯類似。這裏也不在描述了。


最後說一點我的建議,teamtalk的file_server邏輯、以及與客戶端還有msg_server的邏輯流程加上各種細節寫的比較的細膩,代碼實現上也比較好。強烈建議好好地閱讀這部分的代碼。畢竟很多人在自己實現一個文件服務器時,還是存在不少問題的。


zhangyl 2017.05.20 

文中如果存在說的不對的地方,歡迎批評指正。

一、連接狀況介紹

fileserver開始並不是和客戶端連接的,客戶端是按需連接file_server的。但是file_server與msg_server卻是長連接。先啓動file_server,再啓動msg_server。msg_server初始化的時候,會去嘗試連接file_server的8601端口。連接成功以後,會給file_server發送一個發包詢問file_server偵聽客戶端連接的ip和端口號信息:

[cpp] view plain copy
  1. void CFileServConn::OnConfirm()  
  2. {  
  3.     log("connect to file server success ");  
  4.     m_bOpen = true;  
  5.     m_connect_time = get_tick_count();  
  6.     g_file_server_list[m_serv_idx].reconnect_cnt = MIN_RECONNECT_CNT / 2;  
  7.       
  8.     //連上file_server以後,給file_server發送獲取ip地址的數據包  
  9.     IM::Server::IMFileServerIPReq msg;  
  10.     CImPdu pdu;  
  11.     pdu.SetPBMsg(&msg);  
  12.     pdu.SetServiceId(SID_OTHER);  
  13.     pdu.SetCommandId(CID_OTHER_FILE_SERVER_IP_REQ);  
  14.     SendPdu(&pdu);  
  15. }  

file_server收到該數據包後,將自己的偵聽客戶端連接的ip地址和端口號發包告訴msg_server:

[cpp] view plain copy
  1. void FileMsgServerConn::_HandleGetServerAddressReq(CImPdu* pPdu) {  
  2.     IM::Server::IMFileServerIPRsp msg;  
  3.       
  4.     const std::list<IM::BaseDefine::IpAddr>& addrs = ConfigUtil::GetInstance()->GetAddressList();  
  5.       
  6.     for (std::list<IM::BaseDefine::IpAddr>::const_iterator it=addrs.begin(); it!=addrs.end(); ++it) {  
  7.         IM::BaseDefine::IpAddr* addr = msg.add_ip_addr_list();  
  8.         *addr = *it;  
  9.         log("Upload file_client_conn addr info, ip=%s, port=%d", addr->ip().c_str(), addr->port());  
  10.     }  
  11.       
  12.     SendMessageLite(this, SID_OTHER, CID_OTHER_FILE_SERVER_IP_RSP, pPdu->GetSeqNum(), &msg);  
  13. }  

得到的信息是file_server偵聽的ip地址和端口號,默認配置的端口號是8600。也就是說file_server的8600用於客戶端連接,8601端口用於msg_server連接。這樣,客戶端需要傳輸文件(注意:不是聊天圖片,聊天圖片使用另外一個服務msfs進行傳輸),會先告訴msg_server它需要進行文件傳輸,msg_server收到消息後告訴客戶端,你連file_server來傳輸文件吧,並把file_server的地址和端口號告訴客戶端。客戶端這個時候連接file_server進行文件傳輸。我們來具體看一看這個流程的細節信息:

1. 客戶端發包給msg_server說要進行文件發送


然後選擇一個文件:




pc客戶端發送文件邏輯:

[cpp] view plain copy
  1. //pc客戶端代碼(Modules工程SessionLayout.cpp)  
  2. void SessionLayout::Notify(TNotifyUI& msg)  
  3. {  
  4.     ...  
  5.     //省略無關代碼  
  6.     else if (msg.pSender == m_pBtnsendfile) //文件傳輸  
  7.     {  
  8.         module::UserInfoEntity userInfo;  
  9.         if (!module::getUserListModule()->getUserInfoBySId(m_sId, userInfo))  
  10.         {  
  11.             LOG__(ERR, _T("SendFile can't find the sid"));  
  12.             return;  
  13.         }  
  14.   
  15.         CFileDialog fileDlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST  
  16.             , _T("文件|*.*||"), AfxGetMainWnd());  
  17.         fileDlg.m_ofn.Flags |= OFN_NOCHANGEDIR;  
  18.         fileDlg.DoModal();  
  19.   
  20.         CString sPathName;  
  21.         POSITION nPos = fileDlg.GetStartPosition();  
  22.         if (nPos != NULL)  
  23.         {  
  24.             sPathName = fileDlg.GetNextPathName(nPos);  
  25.         }  
  26.         if (sPathName.IsEmpty())  
  27.             return;  
  28.   
  29.         module::getFileTransferModule()->sendFile(sPathName, m_sId, userInfo.isOnlne());  
  30.     }  
  31.     ...  
  32.     //省略無關代碼  
  33. }  


sPathName是文件的全飾路徑;m_sId是收取文件的用戶id,userInfo.isOnlne()判斷m_sId代表的用戶是否在線,以此來決定這次文件傳輸是在線文件還是離線文件模式。
 
[cpp] view plain copy
  1. BOOL FileTransferModule_Impl::sendFile(IN const CString& sFilePath, IN const std::string& sSendToSID,IN BOOL bOnlineMode)  
  2. {  
  3.     if (TransferFileEntityManager::getInstance()->checkIfIsSending(sFilePath))  
  4.     {  
  5.         return FALSE;  
  6.     }  
  7.     TransferFileEntity fileEntity;  
  8.       
  9.     //獲取文件大小  
  10.     struct __stat64 buffer;  
  11.     _wstat64(sFilePath, &buffer);  
  12.     fileEntity.nFileSize = (UInt32)buffer.st_size;  
  13.     if (0 != fileEntity.nFileSize)  
  14.     {  
  15.         CString strFileName = sFilePath;  
  16.         strFileName.Replace(_T("\\"), _T("/"));//mac上對於路徑字符“\”需要做特殊處理,windows上則可以識別  
  17.         fileEntity.sFileName = util::cStringToString(strFileName);  
  18.         fileEntity.sFromID = module::getSysConfigModule()->userID();  
  19.         fileEntity.sToID = sSendToSID;  
  20.         uint32_t transMode = 0;  
  21.         transMode = bOnlineMode ? IM::BaseDefine::TransferFileType::FILE_TYPE_ONLINE : IM::BaseDefine::TransferFileType::FILE_TYPE_OFFLINE;  
  22.           
  23.         LOG__(DEBG,_T("FileTransferSevice_Impl::sendFile sTaskID = %s"), util::stringToCString(fileEntity.sTaskID));  
  24.   
  25.         imcore::IMLibCoreStartOperationWithLambda(  
  26.             [=]()  
  27.         {  
  28.             IM::File::IMFileReq imFileReq;  
  29.             LOG__(APP, _T("imFileReq,name=%s,size=%d,toId=%s"),util::stringToCString(fileEntity.sFileName)  
  30.                 ,fileEntity.nFileSize,util::stringToCString(fileEntity.sToID));  
  31.             imFileReq.set_from_user_id(util::stringToInt32(fileEntity.sFromID));  
  32.             imFileReq.set_to_user_id(util::stringToInt32(fileEntity.sToID));  
  33.             imFileReq.set_file_name(fileEntity.sFileName);  
  34.             imFileReq.set_file_size(fileEntity.nFileSize);  
  35.             imFileReq.set_trans_mode(static_cast<IM::BaseDefine::TransferFileType>(transMode));  
  36.   
  37.             module::getTcpClientModule()->sendPacket(IM::BaseDefine::ServiceID::SID_FILE  
  38.                 , IM::BaseDefine::FileCmdID::CID_FILE_REQUEST  
  39.                 , &imFileReq);  
  40.         });  
  41.           
  42.         return TRUE;  
  43.     }  
  44.     LOG__(ERR, _T("fileEntity FileSize error,size = %d"), fileEntity.nFileSize);  
  45.     return FALSE;  
  46. }  

上面代碼中組裝的包信息中含有要傳輸的文件路徑、文件大小、發送人id、接收方id、文件傳輸模式(在線還是離線),包的命令號是IM::BaseDefine::FileCmdID::CID_FILE_REQUEST。這個包發給msg_server以後,msg_server應答:

[cpp] view plain copy
  1. void CMsgConn::HandlePdu(CImPdu* pPdu)  
  2. {  
  3. ...  
  4. //省略無關代碼  
  5. case CID_FILE_REQUEST:  
  6.             s_file_handler->HandleClientFileRequest(this, pPdu);  
  7.             break;  
  8. ...  
  9. //省略無關代碼  
  10. }  

[cpp] view plain copy
  1. void CFileHandler::HandleClientFileRequest(CMsgConn* pMsgConn, CImPdu* pPdu)  
  2. {  
  3.     IM::File::IMFileReq msg;  
  4.     CHECK_PB_PARSE_MSG(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()));  
  5.       
  6.     uint32_t from_id = pMsgConn->GetUserId();  
  7.     uint32_t to_id = msg.to_user_id();  
  8.     string file_name = msg.file_name();  
  9.     uint32_t file_size = msg.file_size();  
  10.     uint32_t trans_mode = msg.trans_mode();  
  11.     log("HandleClientFileRequest, %u->%u, fileName: %s, trans_mode: %u.", from_id, to_id, file_name.c_str(), trans_mode);  
  12.       
  13.     CDbAttachData attach(ATTACH_TYPE_HANDLE, pMsgConn->GetHandle());  
  14.     CFileServConn* pFileConn = get_random_file_serv_conn();  
  15.     if (pFileConn)  
  16.     {  
  17.         IM::Server::IMFileTransferReq msg2;  
  18.         msg2.set_from_user_id(from_id);  
  19.         msg2.set_to_user_id(to_id);  
  20.         msg2.set_file_name(file_name);  
  21.         msg2.set_file_size(file_size);  
  22.         msg2.set_trans_mode((IM::BaseDefine::TransferFileType)trans_mode);  
  23.         msg2.set_attach_data(attach.GetBuffer(), attach.GetLength());  
  24.         CImPdu pdu;  
  25.         pdu.SetPBMsg(&msg2);  
  26.         pdu.SetServiceId(SID_OTHER);  
  27.         pdu.SetCommandId(CID_OTHER_FILE_TRANSFER_REQ);  
  28.         pdu.SetSeqNum(pPdu->GetSeqNum());  
  29.           
  30.         if (IM::BaseDefine::FILE_TYPE_OFFLINE == trans_mode)  
  31.         {  
  32.             pFileConn->SendPdu(&pdu);  
  33.         }  
  34.         else //IM::BaseDefine::FILE_TYPE_ONLINE  
  35.         {  
  36.             CImUser* pUser = CImUserManager::GetInstance()->GetImUserById(to_id);  
  37.             if (pUser && pUser->GetPCLoginStatus())//已有對應的賬號pc登錄狀態  
  38.             {  
  39.                 pFileConn->SendPdu(&pdu);  
  40.             }  
  41.             else//無對應用戶的pc登錄狀態,向route_server查詢狀態  
  42.             {  
  43.                 //no pc_client in this msg_server, check it from route_server  
  44.                 CPduAttachData attach_data(ATTACH_TYPE_HANDLE_AND_PDU_FOR_FILE, pMsgConn->GetHandle(), pdu.GetBodyLength(), pdu.GetBodyData());  
  45.                 IM::Buddy::IMUsersStatReq msg3;  
  46.                 msg3.set_user_id(from_id);  
  47.                 msg3.add_user_id_list(to_id);  
  48.                 msg3.set_attach_data(attach_data.GetBuffer(), attach_data.GetLength());  
  49.                 CImPdu pdu2;  
  50.                 pdu2.SetPBMsg(&msg3);  
  51.                 pdu2.SetServiceId(SID_BUDDY_LIST);  
  52.                 pdu2.SetCommandId(CID_BUDDY_LIST_USERS_STATUS_REQUEST);  
  53.                 pdu2.SetSeqNum(pPdu->GetSeqNum());  
  54.                 CRouteServConn* route_conn = get_route_serv_conn();  
  55.                 if (route_conn)  
  56.                 {  
  57.                     route_conn->SendPdu(&pdu2);  
  58.                 }  
  59.             }  
  60.         }  
  61.     }  
  62.     else  
  63.     {  
  64.         log("HandleClientFileRequest, no file server.   ");  
  65.         IM::File::IMFileRsp msg2;  
  66.         msg2.set_result_code(1);  
  67.         msg2.set_from_user_id(from_id);  
  68.         msg2.set_to_user_id(to_id);  
  69.         msg2.set_file_name(file_name);  
  70.         msg2.set_task_id("");  
  71.         msg2.set_trans_mode((IM::BaseDefine::TransferFileType)trans_mode);  
  72.         CImPdu pdu;  
  73.         pdu.SetPBMsg(&msg2);  
  74.         pdu.SetServiceId(SID_FILE);  
  75.         pdu.SetCommandId(CID_FILE_RESPONSE);  
  76.         pdu.SetSeqNum(pPdu->GetSeqNum());  
  77.         pMsgConn->SendPdu(&pdu);  
  78.     }  
  79. }  

這段代碼,很有講究,msg_server會檢測file_server是否已經啓動,如果沒有啓動,則直接發包告訴客戶端,file_server不存在。另外,如果該文件傳輸模式是在線文件,會判斷接收文件的用戶是否和發送用戶在同一臺msg_server上。不在的話,則給route_server發送消息,查找該用戶所在的msg_server(這個不具體介紹了,後面分析route_server會專門介紹的)。否則,會將文件發送請求轉發給file_server,包的命令號是CID_OTHER_FILE_TRANSFER_REQ。file_server收到該請求後,處理如下:

[cpp] view plain copy
  1. void FileMsgServerConn::HandlePdu(CImPdu* pdu) {  
  2.       
  3.      ...  
  4.     //省略無關代碼        
  5.     case CID_OTHER_FILE_TRANSFER_REQ:  
  6.         _HandleMsgFileTransferReq(pdu);  
  7.         break ;  
  8.     ...  
  9.     //省略無關代碼          
  10. }  

[cpp] view plain copy
  1. void FileMsgServerConn::_HandleMsgFileTransferReq(CImPdu* pdu) {  
  2.     IM::Server::IMFileTransferReq transfer_req;  
  3.     CHECK_PB_PARSE_MSG(transfer_req.ParseFromArray(pdu->GetBodyData(), pdu->GetBodyLength()));  
  4.       
  5.       
  6.     uint32_t from_id = transfer_req.from_user_id();  
  7.     uint32_t to_id = transfer_req.to_user_id();  
  8.       
  9.     IM::Server::IMFileTransferRsp transfer_rsp;  
  10.     transfer_rsp.set_result_code(1);  
  11.     transfer_rsp.set_from_user_id(from_id);  
  12.     transfer_rsp.set_to_user_id(to_id);  
  13.     transfer_rsp.set_file_name(transfer_req.file_name());  
  14.     transfer_rsp.set_file_size(transfer_req.file_size());  
  15.     transfer_rsp.set_task_id("");  
  16.     transfer_rsp.set_trans_mode(transfer_req.trans_mode());  
  17.     transfer_rsp.set_attach_data(transfer_req.attach_data());  
  18.   
  19.       
  20.     bool rv = false;  
  21.     do {  
  22.         std::string task_id = GenerateUUID();  
  23.         if (task_id.empty()) {  
  24.             log("Create task id failed");  
  25.             break;  
  26.         }  
  27.         log("trams_mode=%d, task_id=%s, from_id=%d, to_id=%d, file_name=%s, file_size=%d", transfer_req.trans_mode(), task_id.c_str(), from_id, to_id, transfer_req.file_name().c_str(), transfer_req.file_size());  
  28.           
  29.         BaseTransferTask* transfer_task = TransferTaskManager::GetInstance()->NewTransferTask(  
  30.                                                                                               transfer_req.trans_mode(),  
  31.                                                                                               task_id,  
  32.                                                                                               from_id,  
  33.                                                                                               to_id,  
  34.                                                                                               transfer_req.file_name(),  
  35.                                                                                               transfer_req.file_size());  
  36.           
  37.         if (transfer_task == NULL) {  
  38.             // 創建未成功  
  39.             // close connection with msg svr  
  40.             // need_close = true;  
  41.             log("Create task failed");  
  42.             break;  
  43.         }  
  44.           
  45.         transfer_rsp.set_result_code(0);  
  46.         transfer_rsp.set_task_id(task_id);  
  47.         rv = true;  
  48.         // need_seq_no = false;  
  49.           
  50.         log("Create task succeed, task id %s, task type %d, from user %d, to user %d", task_id.c_str(), transfer_req.trans_mode(), from_id, to_id);  
  51.     } while (0);  
  52.       
  53.     ::SendMessageLite(this, SID_OTHER, CID_OTHER_FILE_TRANSFER_RSP, pdu->GetSeqNum(), &transfer_rsp);  
  54.       
  55.     if (!rv) {  
  56.         // 未創建成功,關閉連接  
  57.         Close();  
  58.     }  
  59. }  

上述代碼會爲本次傳輸任務創建一個唯一的標識uuid作爲taskid,然後根據離線文件還是在線文件創建離線文件傳輸任務OfflineTransferTask或者在線文件傳輸任務OnlineTransferTask,並加入一個一個成員變量transfer_tasks_中進行管理:

[cpp] view plain copy
  1. BaseTransferTask* TransferTaskManager::NewTransferTask(uint32_t trans_mode, const std::string& task_id, uint32_t from_user_id, uint32_t to_user_id, const std::string& file_name, uint32_t file_size) {  
  2.     BaseTransferTask* transfer_task = NULL;  
  3.       
  4.     TransferTaskMap::iterator it = transfer_tasks_.find(task_id);  
  5.     if (it==transfer_tasks_.end()) {  
  6.         if (trans_mode == IM::BaseDefine::FILE_TYPE_ONLINE) {  
  7.             transfer_task = new OnlineTransferTask(task_id, from_user_id, to_user_id, file_name, file_size);  
  8.         } else if (trans_mode == IM::BaseDefine::FILE_TYPE_OFFLINE) {  
  9.             transfer_task = new OfflineTransferTask(task_id, from_user_id, to_user_id, file_name, file_size);  
  10.         } else {  
  11.             log("Invalid trans_mode = %d", trans_mode);  
  12.         }  
  13.           
  14.         if (transfer_task) {  
  15.             transfer_tasks_.insert(std::make_pair(task_id, transfer_task));  
  16.         }  
  17.     } else {  
  18.         log("Task existed by task_id=%s, why?????", task_id.c_str());  
  19.     }  
  20.       
  21.     return transfer_task;  
  22. }  

這個map transfer_tasks_是在定時器裏面進行定期處理的,處理的依據是當前任務的狀態,比如已經完成的任務就可以從map中移除了:

[cpp] view plain copy
  1. void TransferTaskManager::OnTimer(uint64_t tick) {  
  2.     for (TransferTaskMap::iterator it = transfer_tasks_.begin(); it != transfer_tasks_.end();) {  
  3.         BaseTransferTask* task = it->second;  
  4.         if (task == NULL) {  
  5.             transfer_tasks_.erase(it++);  
  6.             continue;  
  7.         }  
  8.           
  9.         if (task->state() != kTransferTaskStateWaitingUpload &&  
  10.             task->state() == kTransferTaskStateTransferDone) {  
  11.             long esp = time(NULL) - task->create_time();  
  12.             if (esp > ConfigUtil::GetInstance()->GetTaskTimeout()) {  
  13.                 if (task->GetFromConn()) {  
  14.                     FileClientConn* conn = reinterpret_cast<FileClientConn*>(task->GetFromConn());  
  15.                     conn->ClearTransferTask();  
  16.                 }  
  17.                 if (task->GetToConn()) {  
  18.                     FileClientConn* conn = reinterpret_cast<FileClientConn*>(task->GetToConn());  
  19.                     conn->ClearTransferTask();  
  20.                 }  
  21.                 delete task;  
  22.                 transfer_tasks_.erase(it++);  
  23.                 continue;  
  24.             }  
  25.         }  
  26.           
  27.         ++it;  
  28.     }  
  29. }  

完成這些工作以後,組裝的應答包命令號是CID_OTHER_FILE_TRANSFER_RSP,回覆給msg_server。msg_server收到該應答包後處理:

[cpp] view plain copy
  1. void CFileServConn::HandlePdu(CImPdu* pPdu)  
  2. {  
  3.     switch (pPdu->GetCommandId()) {  
  4.         ...  
  5.     //省略無關代碼  
  6.         case CID_OTHER_FILE_TRANSFER_RSP:  
  7.             _HandleFileMsgTransRsp(pPdu);  
  8.             break;  
  9.         ...  
  10.     //省略無關代碼   
  11.     }  
  12. }  

[cpp] view plain copy
  1. void CFileServConn::_HandleFileMsgTransRsp(CImPdu* pPdu)  
  2. {  
  3.     IM::Server::IMFileTransferRsp msg;  
  4.     CHECK_PB_PARSE_MSG(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()));  
  5.   
  6.     uint32_t result = msg.result_code();  
  7.     uint32_t from_id = msg.from_user_id();  
  8.     uint32_t to_id = msg.to_user_id();  
  9.     string file_name = msg.file_name();  
  10.     uint32_t file_size = msg.file_size();  
  11.     string task_id = msg.task_id();  
  12.     uint32_t trans_mode = msg.trans_mode();  
  13.     CDbAttachData attach((uchar_t*)msg.attach_data().c_str(), msg.attach_data().length());  
  14.     log("HandleFileMsgTransRsp, result: %u, from_user_id: %u, to_user_id: %u, file_name: %s, \  
  15.         task_id: %s, trans_mode: %u. ", result, from_id, to_id,  
  16.         file_name.c_str(), task_id.c_str(), trans_mode);  
  17.   
  18.     const list<IM::BaseDefine::IpAddr>* ip_addr_list = GetFileServerIPList();  
  19.   
  20.     IM::File::IMFileRsp msg2;  
  21.     msg2.set_result_code(result);  
  22.     msg2.set_from_user_id(from_id);  
  23.     msg2.set_to_user_id(to_id);  
  24.     msg2.set_file_name(file_name);  
  25.     msg2.set_task_id(task_id);  
  26.     msg2.set_trans_mode((IM::BaseDefine::TransferFileType)trans_mode);  
  27.     for (list<IM::BaseDefine::IpAddr>::const_iterator it = ip_addr_list->begin(); it != ip_addr_list->end(); it++)  
  28.     {  
  29.         IM::BaseDefine::IpAddr ip_addr_tmp = *it;  
  30.         IM::BaseDefine::IpAddr* ip_addr = msg2.add_ip_addr_list();  
  31.         ip_addr->set_ip(ip_addr_tmp.ip());  
  32.         ip_addr->set_port(ip_addr_tmp.port());  
  33.     }  
  34.     CImPdu pdu;  
  35.     pdu.SetPBMsg(&msg2);  
  36.     pdu.SetServiceId(SID_FILE);  
  37.     pdu.SetCommandId(CID_FILE_RESPONSE);  
  38.     pdu.SetSeqNum(pPdu->GetSeqNum());  
  39.     uint32_t handle = attach.GetHandle();  
  40.       
  41.     CMsgConn* pFromConn = CImUserManager::GetInstance()->GetMsgConnByHandle(from_id, handle);  
  42.     if (pFromConn)  
  43.     {  
  44.         pFromConn->SendPdu(&pdu);  
  45.     }  
  46.       
  47.     if (result == 0)  
  48.     {  
  49.         IM::File::IMFileNotify msg3;  
  50.         msg3.set_from_user_id(from_id);  
  51.         msg3.set_to_user_id(to_id);  
  52.         msg3.set_file_name(file_name);  
  53.         msg3.set_file_size(file_size);  
  54.         msg3.set_task_id(task_id);  
  55.         msg3.set_trans_mode((IM::BaseDefine::TransferFileType)trans_mode);  
  56.         msg3.set_offline_ready(0);  
  57.         for (list<IM::BaseDefine::IpAddr>::const_iterator it = ip_addr_list->begin(); it != ip_addr_list->end(); it++)  
  58.         {  
  59.             IM::BaseDefine::IpAddr ip_addr_tmp = *it;  
  60.             IM::BaseDefine::IpAddr* ip_addr = msg3.add_ip_addr_list();  
  61.             ip_addr->set_ip(ip_addr_tmp.ip());  
  62.             ip_addr->set_port(ip_addr_tmp.port());  
  63.         }  
  64.         CImPdu pdu2;  
  65.         pdu2.SetPBMsg(&msg3);  
  66.         pdu2.SetServiceId(SID_FILE);  
  67.         pdu2.SetCommandId(CID_FILE_NOTIFY);  
  68.           
  69.         //send notify to target user  
  70.         CImUser* pToUser = CImUserManager::GetInstance()->GetImUserById(to_id);  
  71.         if (pToUser)  
  72.         {  
  73.             pToUser->BroadcastPduWithOutMobile(&pdu2);  
  74.         }  
  75.           
  76.         //send to route server  
  77.         CRouteServConn* pRouteConn = get_route_serv_conn();  
  78.         if (pRouteConn) {  
  79.             pRouteConn->SendPdu(&pdu2);  
  80.         }  
  81.     }  
  82. }  

msg_server收到包後,首先裝包數據,並把file_server的ip地址和端口信息帶上,發給請求發文件的客戶端,命令號是CID_FILE_RESPONSE;接着查詢通知接收方有人給其發文件(通知方式也是一樣,如果接收方在該msg_server上,直接發給該用戶;不在的話,發給路由服務route_server)。當然接收到文件發送的端只有pc端,移動端會被過濾掉的,也就是說移動端不會收到發送文件的請求。

我們先看發送方pc客戶端收到應答的邏輯(命令號是CID_FILE_RESPONSE):

[cpp] view plain copy
  1. void FileTransferModule_Impl::onPacket(imcore::TTPBHeader& header, std::string& pbBody)  
  2. {  
  3.     switch (header.getCommandId())  
  4.     {  
  5.     case IM::BaseDefine::FileCmdID::CID_FILE_RESPONSE://發送“文件請求/離線文件”-返回  
  6.         _sendfileResponse(pbBody);  
  7.         break;    
  8.     }  
  9. }  

[cpp] view plain copy
  1. void FileTransferModule_Impl::_sendfileResponse(IN std::string& body)  
  2. {  
  3.     IM::File::IMFileRsp imFileRsp;  
  4.     if (!imFileRsp.ParseFromString(body))  
  5.     {  
  6.         LOG__(ERR, _T("parse failed,body:%s"), util::stringToCString(body));  
  7.         return;  
  8.     }  
  9.   
  10.     UInt32 nResult = imFileRsp.result_code();  
  11.     if (nResult != 0)  
  12.     {  
  13.         LOG__(ERR, _T("_sendfileResponse result != 0"));  
  14.         module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_UPLOAD_FAILED);  
  15.     }  
  16.   
  17.     TransferFileEntity fileEntity;  
  18.     fileEntity.sTaskID = imFileRsp.task_id();  
  19.     assert(!fileEntity.sTaskID.empty());  
  20.     fileEntity.sFromID = util::uint32ToString(imFileRsp.from_user_id());  
  21.     fileEntity.sToID = util::uint32ToString(imFileRsp.to_user_id());  
  22.     fileEntity.sFileName = imFileRsp.file_name();  
  23.     fileEntity.setSaveFilePath(util::stringToCString(fileEntity.sFileName));//發送方文件地址,就是保存地址  
  24.     fileEntity.time = static_cast<UInt32>(time(0));  
  25.     uint32_t transMode = imFileRsp.trans_mode();  
  26.     if (IM::BaseDefine::TransferFileType::FILE_TYPE_ONLINE == transMode)  
  27.     {  
  28.         fileEntity.nClientMode = IM::BaseDefine::ClientFileRole::CLIENT_REALTIME_SENDER;  
  29.     }  
  30.     else if (IM::BaseDefine::TransferFileType::FILE_TYPE_OFFLINE == transMode)  
  31.     {  
  32.         fileEntity.nClientMode = IM::BaseDefine::ClientFileRole::CLIENT_OFFLINE_UPLOAD;  
  33.     }  
  34.     fileEntity.pFileObject = new TransferFile(util::stringToCString(fileEntity.sFileName),FALSE);  
  35.     if (fileEntity.pFileObject)  
  36.     {  
  37.         fileEntity.nFileSize = fileEntity.pFileObject->length();  
  38.     }  
  39.       
  40.     UINT32 nIPCount = imFileRsp.ip_addr_list_size();  
  41.     if (nIPCount <= 0)  
  42.     {  
  43.         return;  
  44.     }  
  45.     const IM::BaseDefine::IpAddr& ipAdd = imFileRsp.ip_addr_list(0);  
  46.     fileEntity.sIP = ipAdd.ip();  
  47.     fileEntity.nPort = ipAdd.port();  
  48.   
  49.     if (!TransferFileEntityManager::getInstance()->pushTransferFileEntity(fileEntity))  
  50.         TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);  
  51.   
  52.     LOG__(DEBG, _T("FileTransferSevice_Impl::準備連接文件服務器 sTaskId = %s"), util::stringToCString(fileEntity.sTaskID));  
  53.     TransferFileEntityManager::getInstance()->openFileSocketByTaskId(fileEntity.sTaskID);  
  54. }  

客戶端在TransferFileEntityManager::getInstance()->openFileSocketByTaskId(fileEntity.sTaskID);裏面實際去連接file_server並嘗試發文件:

[cpp] view plain copy
  1. void TransferFileEntityManager::openFileSocketByTaskId(std::string& taskId)  
  2. {  
  3.     m_fileUIThread->openFileSocketByTaskId(taskId);  
  4. }  

[cpp] view plain copy
  1. void FileTransferUIThread::openFileSocketByTaskId(std::string& taskId)  
  2. {  
  3.     FileTransferSocket* pFileSocket = _findFileSocketByTaskId(taskId);  
  4.     if (!pFileSocket)  
  5.     {  
  6.         pFileSocket = new FileTransferSocket(taskId);  
  7.         m_lstFileTransSockets.push_back(pFileSocket);  
  8.         assert(m_hWnd);  
  9.         ::PostMessage(m_hWnd, WM_FILE_TRANSFER, 0, (LPARAM)pFileSocket);  
  10.     }  
  11. }  

[cpp] view plain copy
  1. LRESULT _stdcall FileTransferUIThread::_WndProc(HWND hWnd, UINT message, WPARAM wparam, LPARAM lparam)  
  2. {  
  3.     if (WM_FILE_TRANSFER == message)  
  4.     {  
  5.         FileTransferSocket* pFileSocket = (FileTransferSocket*)lparam;  
  6.         pFileSocket->startFileTransLink();  
  7.     }  
  8.   
  9.     return ::DefWindowProc(hWnd, message, wparam, lparam);  
  10. }  

[cpp] view plain copy
  1. BOOL FileTransferSocket::startFileTransLink()  
  2. {  
  3.     TransferFileEntity FileInfo;  
  4.     if (TransferFileEntityManager::getInstance()->getFileInfoByTaskId(m_sTaskId, FileInfo))  
  5.     {  
  6.         //大佛:使用msg server 傳過來的IP和端口  
  7.         LOG__(APP, _T("connect IP=%s,Port=%d"), util::stringToCString(FileInfo.sIP), FileInfo.nPort);  
  8.         connect(util::stringToCString(FileInfo.sIP), FileInfo.nPort);  
  9.         //connect(util::stringToCString(module::FILETRANSFER_IP), module::FILETRANSFER_PORT);  
  10.         return TRUE;  
  11.     }  
  12.     LOG__(ERR, _T("can't find the TaskId"));  
  13.     return FALSE;  
  14. }  

注意,這裏只是去連接file_server服務器,連接成功的情況下,會嘗試登錄文件服務器,登錄file_server的命令號是CID_FILE_LOGIN_REQ:

[cpp] view plain copy
  1. void FileTransferSocket::onConnectDone()  
  2. {  
  3.     LOG__(APP, _T("FileTransferSocket::onConnected()"));  
  4.     startHeartbeat();  
  5.   
  6.     TransferFileEntity info;  
  7.     if (!TransferFileEntityManager::getInstance()->getFileInfoByTaskId(m_sTaskId, info))  
  8.     {  
  9.         LOG__(APP, _T("Can't get the file info,task id:%s"),util::stringToCString(m_sTaskId));  
  10.         return;  
  11.     }  
  12.           
  13.     //拉模式文件傳輸,傳輸taskid、token、client_mode  
  14.     IM::File::IMFileLoginReq imFileLoginReq;  
  15.     imFileLoginReq.set_user_id(module::getSysConfigModule()->userId());  
  16.     imFileLoginReq.set_task_id(info.sTaskID);  
  17.     imFileLoginReq.set_file_role(static_cast<IM::BaseDefine::ClientFileRole>(info.nClientMode));  
  18.   
  19.     LOG__(APP, _T("IMFileLoginReq,sTaskID:%s,nClientMode:%d"), util::stringToCString(info.sTaskID), info.nClientMode);  
  20.     //send packet  
  21.     LOG__(APP, _T("IMFileLoginReq,taskId:%s"), util::stringToCString(info.sTaskID));  
  22.     sendPacket(IM::BaseDefine::ServiceID::SID_FILE, IM::BaseDefine::FileCmdID::CID_FILE_LOGIN_REQ, &imFileLoginReq);  
  23.   
  24.     //CImPduClientFileLoginReq pduFileLoginReq(module::getSysConfigModule()->userID().c_str()  
  25.     //  , "", info.sTaskID.c_str(), );  
  26.     //sendPacket(&pduFileLoginReq);  
  27. }  

file_server收到該數據包處理如下:

[cpp] view plain copy
  1. void FileClientConn::HandlePdu(CImPdu* pdu) {  
  2.    
  3.     ...  
  4.     //省略無關代碼  
  5.         case CID_FILE_LOGIN_REQ:  
  6.             _HandleClientFileLoginReq(pdu);  
  7.             break;  
  8. }  

[cpp] view plain copy
  1. void FileClientConn::_HandleClientFileLoginReq(CImPdu* pdu) {  
  2.     IM::File::IMFileLoginReq login_req;  
  3.     CHECK_PB_PARSE_MSG(login_req.ParseFromArray(pdu->GetBodyData(), pdu->GetBodyLength()));  
  4.       
  5.     uint32_t user_id = login_req.user_id();  
  6.     string task_id = login_req.task_id();  
  7.     IM::BaseDefine::ClientFileRole mode = login_req.file_role();  
  8.       
  9.     log("Client login, user_id=%d, task_id=%s, file_role=%d", user_id, task_id.c_str(), mode);  
  10.       
  11.     BaseTransferTask* transfer_task = NULL;  
  12.       
  13.     bool rv = false;  
  14.     do {  
  15.         // 查找任務是否存在  
  16.         transfer_task = TransferTaskManager::GetInstance()->FindByTaskID(task_id);  
  17.           
  18.         if (transfer_task == NULL) {  
  19.             if (mode == CLIENT_OFFLINE_DOWNLOAD) {  
  20.                 // 文件不存在,檢查是否是離線下載,有可能是文件服務器重啓  
  21.                 // 嘗試從磁盤加載  
  22.                 transfer_task = TransferTaskManager::GetInstance()->NewTransferTask(task_id, user_id);  
  23.                 // 需要再次判斷是否加載成功  
  24.                 if (transfer_task == NULL) {  
  25.                     log("Find task id failed, user_id=%u, taks_id=%s, mode=%d", user_id, task_id.c_str(), mode);  
  26.                     break;  
  27.                 }  
  28.             } else {  
  29.                 log("Can't find task_id, user_id=%u, taks_id=%s, mode=%d", user_id, task_id.c_str(), mode);  
  30.                 break;  
  31.             }  
  32.         }  
  33.           
  34.         // 狀態轉換  
  35.         rv = transfer_task->ChangePullState(user_id, mode);  
  36.         if (!rv) {  
  37.             // log();  
  38.             break;  
  39.             //  
  40.         }  
  41.           
  42.         // Ok  
  43.         auth_ = true;  
  44.         transfer_task_ = transfer_task;  
  45.         user_id_ = user_id;  
  46.         // 設置conn  
  47.         transfer_task->SetConnByUserID(user_id, this);  
  48.         rv = true;  
  49.           
  50.     } while (0);  
  51.   
  52.     IM::File::IMFileLoginRsp login_rsp;  
  53.     login_rsp.set_result_code(rv?0:1);  
  54.     login_rsp.set_task_id(task_id);  
  55.   
  56.     ::SendMessageLite(this, SID_FILE, CID_FILE_LOGIN_RES, pdu->GetSeqNum(), &login_rsp);  
  57.   
  58.     if (rv) {  
  59.         if (transfer_task->GetTransMode() == FILE_TYPE_ONLINE) {  
  60.             if (transfer_task->state() == kTransferTaskStateWaitingTransfer) {  
  61.                 CImConn* conn = transfer_task_->GetToConn();  
  62.                 if (conn) {  
  63.                     _StatesNotify(CLIENT_FILE_PEER_READY, task_id, transfer_task_->from_user_id(), conn);  
  64.                 } else {  
  65.                     log("to_conn is close, close me!!!");  
  66.                     Close();  
  67.                 }  
  68.                 // _StatesNotify(CLIENT_FILE_PEER_READY, task_id, user_id, this);  
  69.                 // transfer_task->StatesNotify(CLIENT_FILE_PEER_READY, task_id, user_id_);  
  70.             }  
  71.         } else {  
  72.             if (transfer_task->state() == kTransferTaskStateWaitingUpload) {  
  73.                   
  74.                 OfflineTransferTask* offline = reinterpret_cast<OfflineTransferTask*>(transfer_task);  
  75.                   
  76.                 IM::File::IMFilePullDataReq pull_data_req;  
  77.                 pull_data_req.set_task_id(task_id);  
  78.                 pull_data_req.set_user_id(user_id);  
  79.                 pull_data_req.set_trans_mode(FILE_TYPE_OFFLINE);  
  80.                 pull_data_req.set_offset(0);  
  81.                 pull_data_req.set_data_size(offline->GetNextSegmentBlockSize());  
  82.                   
  83.                 ::SendMessageLite(this, SID_FILE, CID_FILE_PULL_DATA_REQ, &pull_data_req);  
  84.   
  85.                 log("Pull Data Req");  
  86.             }  
  87.         }  
  88.     } else {  
  89.         Close();  
  90.     }  
  91. }  


file_server應答客戶端的命令號是CID_FILE_LOGIN_RES,客戶端收到該包後處理如下:

[cpp] view plain copy
  1. void FileTransferSocket::onReceiveData(const char* data, int32_t size)  
  2. {  
  3.     std::string pbBody;  
  4.     imcore::TTPBHeader pbHeader;   
  5.     try  
  6.     {  
  7.         pbHeader.unSerialize((byte*)data, imcore::HEADER_LENGTH);  
  8.         pbBody.assign(data + imcore::HEADER_LENGTH, size - imcore::HEADER_LENGTH);  
  9.   
  10.         if (IM::BaseDefine::OtherCmdID::CID_OTHER_HEARTBEAT == pbHeader.getCommandId()  
  11.             && IM::BaseDefine::ServiceID::SID_OTHER == pbHeader.getModuleId())  
  12.             return;  
  13.     }  
  14.     catch (CPduException e)  
  15.     {  
  16.         LOG__(ERR, _T("onPacket CPduException serviceId:%d,commandId:%d,errCode:%d")  
  17.             , e.GetModuleId(), e.GetCommandId(), e.GetErrorCode());  
  18.         return;  
  19.     }  
  20.     catch (...)  
  21.     {  
  22.         LOG__(ERR, _T("FileTransferSocket onPacket unknown exception"));  
  23.         return;  
  24.     }  
  25.     UInt16 ncmdid = pbHeader.getCommandId();  
  26.     switch (ncmdid)  
  27.     {      
  28.     case IM::BaseDefine::FileCmdID::CID_FILE_LOGIN_RES:  
  29.         _fileLoginResponse(pbBody);  
  30.         break;  
  31.     //無關代碼省略  
  32.     }  
  33. }  
[cpp] view plain copy
  1. void FileTransferSocket::_fileLoginResponse(IN std::string& body)  
  2. {  
  3.     IM::File::IMFileLoginRsp imFileLoginRsp;  
  4.     if (!imFileLoginRsp.ParseFromString(body))  
  5.     {  
  6.         LOG__(ERR, _T("parse failed,body:%s"), util::stringToCString(body));  
  7.         return;  
  8.     }  
  9.     if (imFileLoginRsp.result_code() != 0)  
  10.     {  
  11.         LOG__(ERR, _T("file server login failed! "));  
  12.         return;  
  13.     }  
  14.     //打開文件  
  15.     std::string taskId = imFileLoginRsp.task_id();  
  16.     TransferFileEntity fileEntity;  
  17.     if (!TransferFileEntityManager::getInstance()->getFileInfoByTaskId(taskId, fileEntity))  
  18.     {  
  19.         LOG__(ERR, _T("file server login:can't find the fileInfo "));  
  20.         return;  
  21.     }  
  22.   
  23.     LOG__(APP, _T("IMFileLoginRsp, file server login succeed"));  
  24.     //提示界面,界面上插入該項  
  25.     if (IM::BaseDefine::ClientFileRole::CLIENT_REALTIME_SENDER == fileEntity.nClientMode  
  26.         || IM::BaseDefine::ClientFileRole::CLIENT_OFFLINE_UPLOAD == fileEntity.nClientMode)  
  27.     {  
  28.         module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILETRANSFER_SENDFILE, fileEntity.sTaskID);  
  29.     }  
  30.     else if (IM::BaseDefine::ClientFileRole::CLIENT_REALTIME_RECVER == fileEntity.nClientMode  
  31.         || IM::BaseDefine::ClientFileRole::CLIENT_OFFLINE_DOWNLOAD == fileEntity.nClientMode)  
  32.     {  
  33.         module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILETRANSFER_REQUEST, fileEntity.sTaskID);  
  34.     }  
  35. }  

至此,不管是離線文件還是在線文件發送,pc客戶端會顯示一個文件進度的對話框:




對於在線文件,需要對端同意接收文件的傳輸,客戶端纔會讀取文件,這個進度條纔會發生變化。而對於離線文件,應該會立馬讀取文件上傳文件數據到服務器。可是哪裏會觸發客戶端讀取文件併發送的邏輯呢?門道在於file_server在收到登錄請求CID_FILE_LOGIN_REQ後,不僅會給客戶端發送登錄應答數據包CID_FILE_LOGIN_RES。還會根據文件的傳輸模式,如果是離線文件則會給客戶端發送拉取文件的數據包CID_FILE_PULL_DATA_REQ,代碼我們已經在上面的FileClientConn::_HandleClientFileLoginReq(CImPdu* pdu)中貼過了,我們再貼一次:

[cpp] view plain copy
  1. void FileClientConn::_HandleClientFileLoginReq(CImPdu* pdu) {  
  2.     IM::File::IMFileLoginReq login_req;  
  3.     CHECK_PB_PARSE_MSG(login_req.ParseFromArray(pdu->GetBodyData(), pdu->GetBodyLength()));  
  4.       
  5.     uint32_t user_id = login_req.user_id();  
  6.     string task_id = login_req.task_id();  
  7.     IM::BaseDefine::ClientFileRole mode = login_req.file_role();  
  8.       
  9.     log("Client login, user_id=%d, task_id=%s, file_role=%d", user_id, task_id.c_str(), mode);  
  10.       
  11.     BaseTransferTask* transfer_task = NULL;  
  12.       
  13.     bool rv = false;  
  14.     do {  
  15.         // 查找任務是否存在  
  16.         transfer_task = TransferTaskManager::GetInstance()->FindByTaskID(task_id);  
  17.           
  18.         if (transfer_task == NULL) {  
  19.             if (mode == CLIENT_OFFLINE_DOWNLOAD) {  
  20.                 // 文件不存在,檢查是否是離線下載,有可能是文件服務器重啓  
  21.                 // 嘗試從磁盤加載  
  22.                 transfer_task = TransferTaskManager::GetInstance()->NewTransferTask(task_id, user_id);  
  23.                 // 需要再次判斷是否加載成功  
  24.                 if (transfer_task == NULL) {  
  25.                     log("Find task id failed, user_id=%u, taks_id=%s, mode=%d", user_id, task_id.c_str(), mode);  
  26.                     break;  
  27.                 }  
  28.             } else {  
  29.                 log("Can't find task_id, user_id=%u, taks_id=%s, mode=%d", user_id, task_id.c_str(), mode);  
  30.                 break;  
  31.             }  
  32.         }  
  33.           
  34.         // 狀態轉換  
  35.         rv = transfer_task->ChangePullState(user_id, mode);  
  36.         if (!rv) {  
  37.             // log();  
  38.             break;  
  39.             //  
  40.         }  
  41.           
  42.         // Ok  
  43.         auth_ = true;  
  44.         transfer_task_ = transfer_task;  
  45.         user_id_ = user_id;  
  46.         // 設置conn  
  47.         transfer_task->SetConnByUserID(user_id, this);  
  48.         rv = true;  
  49.           
  50.     } while (0);  
  51.   
  52.     IM::File::IMFileLoginRsp login_rsp;  
  53.     login_rsp.set_result_code(rv?0:1);  
  54.     login_rsp.set_task_id(task_id);  
  55.   
  56.     ::SendMessageLite(this, SID_FILE, CID_FILE_LOGIN_RES, pdu->GetSeqNum(), &login_rsp);  
  57.   
  58.     if (rv) {  
  59.         if (transfer_task->GetTransMode() == FILE_TYPE_ONLINE) {  
  60.             if (transfer_task->state() == kTransferTaskStateWaitingTransfer) {  
  61.                 CImConn* conn = transfer_task_->GetToConn();  
  62.                 if (conn) {  
  63.                     _StatesNotify(CLIENT_FILE_PEER_READY, task_id, transfer_task_->from_user_id(), conn);  
  64.                 } else {  
  65.                     log("to_conn is close, close me!!!");  
  66.                     Close();  
  67.                 }  
  68.                 // _StatesNotify(CLIENT_FILE_PEER_READY, task_id, user_id, this);  
  69.                 // transfer_task->StatesNotify(CLIENT_FILE_PEER_READY, task_id, user_id_);  
  70.             }  
  71.         } else {  
  72.             if (transfer_task->state() == kTransferTaskStateWaitingUpload) {  
  73.                   
  74.                 OfflineTransferTask* offline = reinterpret_cast<OfflineTransferTask*>(transfer_task);  
  75.                   
  76.                 IM::File::IMFilePullDataReq pull_data_req;  
  77.                 pull_data_req.set_task_id(task_id);  
  78.                 pull_data_req.set_user_id(user_id);  
  79.                 pull_data_req.set_trans_mode(FILE_TYPE_OFFLINE);  
  80.                 pull_data_req.set_offset(0);  
  81.                 pull_data_req.set_data_size(offline->GetNextSegmentBlockSize());  
  82.                   
  83.                 ::SendMessageLite(this, SID_FILE, CID_FILE_PULL_DATA_REQ, &pull_data_req);  
  84.   
  85.                 log("Pull Data Req");  
  86.             }  
  87.         }  
  88.     } else {  
  89.         Close();  
  90.     }  
  91. }  

pc端收到CID_FILE_PULL_DATA_REQ後,表示這是一個離線文件,就可以直接上傳文件數據了:

[cpp] view plain copy
  1. case IM::BaseDefine::FileCmdID::CID_FILE_PULL_DATA_REQ://發文件  
  2.         _filePullDataReqResponse(pbBody);  

[cpp] view plain copy
  1. void FileTransferSocket::_filePullDataReqResponse(IN std::string& body)//發  
  2. {  
  3.     IM::File::IMFilePullDataReq imFilePullDataReq;  
  4.     if (!imFilePullDataReq.ParseFromString(body))  
  5.     {  
  6.         LOG__(ERR, _T("parse failed,body:%s"), util::stringToCString(body));  
  7.         return;  
  8.     }  
  9.     UInt32 fileSize = imFilePullDataReq.data_size();  
  10.     UInt32 fileOffset = imFilePullDataReq.offset();  
  11.     std::string taskId = imFilePullDataReq.task_id();  
  12.       
  13.     TransferFileEntity fileEntity;  
  14.     if (!TransferFileEntityManager::getInstance()->getFileInfoByTaskId(taskId, fileEntity))  
  15.     {  
  16.         LOG__(ERR, _T("PullDataReqResponse: can't find the fileInfo"));  
  17.         return;  
  18.     }  
  19.     LOG__(DEBG, _T("send:taskId=%s,filesize=%d,name=%s,BolckSize=%d")  
  20.         ,util::stringToCString(fileEntity.sTaskID)  
  21.         ,fileEntity.nFileSize  
  22.         ,fileEntity.getRealFileName()  
  23.         ,fileSize);  
  24.     std::string buff;  
  25.     if (nullptr == fileEntity.pFileObject)  
  26.     {  
  27.         LOG__(ERR, _T("PullDataReqResponse: file boject Destoryed!"));  
  28.         return;  
  29.     }  
  30.     fileEntity.pFileObject->readBlock(fileOffset, fileSize, buff);//讀取本地文件的數據塊  
  31.     IM::File::IMFilePullDataRsp imFilePullDataRsp;//todo check  
  32.     imFilePullDataRsp.set_result_code(0);  
  33.     imFilePullDataRsp.set_task_id(taskId);  
  34.     imFilePullDataRsp.set_user_id(util::stringToInt32(fileEntity.sFromID));  
  35.     imFilePullDataRsp.set_offset(fileOffset);  
  36.     imFilePullDataRsp.set_file_data((void*)buff.data(), fileSize);  
  37.   
  38.     //send packet  
  39.     sendPacket(IM::BaseDefine::ServiceID::SID_FILE, IM::BaseDefine::FileCmdID::CID_FILE_PULL_DATA_RSP  
  40.         , &imFilePullDataRsp);  
  41.   
  42.     fileEntity.nProgress = fileOffset + fileSize;  
  43.     if (fileEntity.nProgress < fileEntity.nFileSize)  
  44.     {  
  45.         //更新進度條  
  46.         TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);//保存當前進度  
  47.         module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_UPDATA_PROGRESSBAR  
  48.             , fileEntity.sTaskID);  
  49.     }  
  50.     else//傳輸完成  
  51.     {  
  52.         if (fileEntity.pFileObject)  
  53.         {  
  54.             delete fileEntity.pFileObject;  
  55.             fileEntity.pFileObject = nullptr;  
  56.         }  
  57.         module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_PROGRESSBAR_FINISHED  
  58.             , fileEntity.sTaskID);  
  59.     }  
  60.     TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);  
  61. }  

當然,如果文件比較大,一次發不完也沒關係,在CID_FILE_PULL_DATA_REQ中有當前文件的偏移量,客戶端在讀取文件和應答服務器時也帶上這個偏移量fileOffset,應答給服務器的包是CID_FILE_PULL_DATA_RSP。file_server收到應答後處理:

[cpp] view plain copy
  1. void FileClientConn::HandlePdu(CImPdu* pdu) {  
  2.       
  3.             ...  
  4.     //省略無關代碼  
  5.         case CID_FILE_PULL_DATA_RSP:  
  6.             _HandleClientFilePullFileRsp( pdu);  
  7.             break ;  
  8.                 ...  
  9.     //省略無關代碼  
  10.   
  11. }  


[cpp] view plain copy
  1. void FileClientConn::_HandleClientFilePullFileRsp(CImPdu *pdu) {  
  2.     if (!auth_ || !transfer_task_) {  
  3.         log("auth is false");  
  4.         return;  
  5.     }  
  6.   
  7.     // 只有rsp  
  8.     IM::File::IMFilePullDataRsp pull_data_rsp;  
  9.     CHECK_PB_PARSE_MSG(pull_data_rsp.ParseFromArray(pdu->GetBodyData(), pdu->GetBodyLength()));  
  10.       
  11.     uint32_t user_id = pull_data_rsp.user_id();  
  12.     string task_id = pull_data_rsp.task_id();  
  13.     uint32_t offset = pull_data_rsp.offset();  
  14.     uint32_t data_size = static_cast<uint32_t>(pull_data_rsp.file_data().length());  
  15.     const char* data = pull_data_rsp.file_data().data();  
  16.   
  17.     // log("Recv FilePullFileRsp, user_id=%d, task_id=%s, file_role=%d, offset=%d, datasize=%d", user_id, task_id.c_str(), mode, offset, datasize);  
  18.     log("Recv FilePullFileRsp, task_id=%s, user_id=%u, offset=%u, data_size=%d", task_id.c_str(), user_id, offset, data_size);  
  19.   
  20.     int rv = -1;  
  21.     do {  
  22.         //  
  23.         // 檢查user_id  
  24.         if (user_id != user_id_) {  
  25.             log("Received user_id valid, recv_user_id = %d, transfer_task.user_id = %d, user_id_ = %d", user_id, transfer_task_->from_user_id(), user_id_);  
  26.             break;  
  27.         }  
  28.           
  29.         // 檢查task_id  
  30.         if (transfer_task_->task_id() != task_id) {  
  31.             log("Received task_id valid, recv_task_id = %s, this_task_id = %s", task_id.c_str(), transfer_task_->task_id().c_str());  
  32.             // Close();  
  33.             break;  
  34.         }  
  35.           
  36.         rv = transfer_task_->DoRecvData(user_id, offset, data, data_size);  
  37.         if (rv == -1) {  
  38.             break;  
  39.         }  
  40.           
  41.         if (transfer_task_->GetTransMode() == FILE_TYPE_ONLINE) {  
  42.             // 對於在線,直接轉發  
  43.             OnlineTransferTask* online = reinterpret_cast<OnlineTransferTask*>(transfer_task_);  
  44.             pdu->SetSeqNum(online->GetSeqNum());  
  45.             // online->SetSeqNum(pdu->GetSeqNum());  
  46.   
  47.             CImConn* conn = transfer_task_->GetToConn();  
  48.             if (conn) {  
  49.                 conn->SendPdu(pdu);  
  50.             }  
  51.         } else {  
  52.             // 離線  
  53.             // all packages recved  
  54.             if (rv == 1) {  
  55.                 _StatesNotify(CLIENT_FILE_DONE, task_id, user_id, this);  
  56.                 // Close();  
  57.             } else {  
  58.                 OfflineTransferTask* offline = reinterpret_cast<OfflineTransferTask*>(transfer_task_);  
  59.                   
  60.                 IM::File::IMFilePullDataReq pull_data_req;  
  61.                 pull_data_req.set_task_id(task_id);  
  62.                 pull_data_req.set_user_id(user_id);  
  63.                 pull_data_req.set_trans_mode(static_cast<IM::BaseDefine::TransferFileType>(offline->GetTransMode()));  
  64.                 pull_data_req.set_offset(offline->GetNextOffset());  
  65.                 pull_data_req.set_data_size(offline->GetNextSegmentBlockSize());  
  66.                   
  67.                 ::SendMessageLite(this, SID_FILE, CID_FILE_PULL_DATA_REQ, &pull_data_req);  
  68.                 // log("size not match");  
  69.             }  
  70.         }  
  71.           
  72.     } while (0);  
  73.       
  74.     if (rv!=0) {  
  75.         // -1,出錯關閉  
  76.         //  1, 離線上傳完成  
  77.         Close();  
  78.     }  
  79. }  

如果是在線文件,就直接轉發含有文件數據的包;如果是離線文件,則存入文件服務上,即寫入文件:

[cpp] view plain copy
  1. int OfflineTransferTask::DoRecvData(uint32_t user_id, uint32_t offset, const char* data, uint32_t data_size) {  
  2.     // 離線文件上傳  
  3.       
  4.     int rv = -1;  
  5.       
  6.     do {  
  7.         // 檢查是否發送者  
  8.         if (!CheckFromUserID(user_id)) {  
  9.             log("rsp user_id=%d, but sender_id is %d", user_id, from_user_id_);  
  10.             break;  
  11.         }  
  12.           
  13.         // 檢查狀態  
  14.         if (state_ != kTransferTaskStateWaitingUpload && state_ != kTransferTaskStateUploading) {  
  15.             log("state=%d error, need kTransferTaskStateWaitingUpload or kTransferTaskStateUploading", state_);  
  16.             break;  
  17.         }  
  18.           
  19.         // 檢查offset是否有效  
  20.         if (offset != transfered_idx_*SEGMENT_SIZE) {  
  21.             break;  
  22.         }  
  23.           
  24.         //if (data_size != GetNextSegmentBlockSize()) {  
  25.         //    break;  
  26.         //}  
  27.         // todo  
  28.         // 檢查文件大小  
  29.           
  30.         data_size = GetNextSegmentBlockSize();  
  31.         log("Ready recv data, offset=%d, data_size=%d, segment_size=%d", offset, data_size, sengment_size_);  
  32.           
  33.         if (state_ == kTransferTaskStateWaitingUpload) {  
  34.             if (fp_ == NULL) {  
  35.                 fp_ = OpenByWrite(task_id_, to_user_id_);  
  36.                 if (fp_ == NULL) {  
  37.                     break;  
  38.                 }  
  39.             }  
  40.   
  41.             // 寫文件頭  
  42.             OfflineFileHeader file_header;  
  43.             memset(&file_header, 0, sizeof(file_header));  
  44.             file_header.set_create_time(time(NULL));  
  45.             file_header.set_task_id(task_id_);  
  46.             file_header.set_from_user_id(from_user_id_);  
  47.             file_header.set_to_user_id(to_user_id_);  
  48.             file_header.set_file_name("");  
  49.             file_header.set_file_size(file_size_);  
  50.             fwrite(&file_header, 1, sizeof(file_header), fp_);  
  51.             fflush(fp_);  
  52.   
  53.             state_ = kTransferTaskStateUploading;  
  54.         }  
  55.           
  56.         // 存儲  
  57.         if (fp_ == NULL) {  
  58.             //  
  59.             break;  
  60.         }  
  61.           
  62.         fwrite(data, 1, data_size, fp_);  
  63.         fflush(fp_);  
  64.   
  65.         ++transfered_idx_;  
  66.         SetLastUpdateTime();  
  67.   
  68.         if (transfered_idx_ == sengment_size_) {  
  69.             state_ = kTransferTaskStateUploadEnd;  
  70.             fclose(fp_);  
  71.             fp_ = NULL;  
  72.             rv = 1;  
  73.         } else {  
  74.             rv = 0;  
  75.         }  
  76.     } while (0);  
  77.       
  78.     return rv;  
  79. }  

如此循環,直至文件傳輸完成。當然文件上傳完成後file_server也會斷開與客戶端的連接。

到這裏我們介紹了發送文件方的邏輯,下面我們看看接收方的邏輯,上文中介紹了接收方會收到接收文件的通知CID_FILE_NOTIFY,客戶端處理這個命令號:

[cpp] view plain copy
  1. case IM::BaseDefine::FileCmdID::CID_FILE_NOTIFY://收到“發送文件請求”  
  2. <span style="white-space:pre">  </span>_fileNotify(pbBody);  
[cpp] view plain copy
  1. void FileTransferModule_Impl::_fileNotify(IN std::string& body)  
  2. {  
  3.     IM::File::IMFileNotify imFileNotify;  
  4.     if (!imFileNotify.ParseFromString(body))  
  5.     {  
  6.         LOG__(ERR, _T("parse failed,body:%s"), util::stringToCString(body));  
  7.         return;  
  8.     }  
  9.     TransferFileEntity file;  
  10.     file.sFileName = imFileNotify.file_name();  
  11.     file.sFromID = util::uint32ToString(imFileNotify.from_user_id());  
  12.     file.sToID = util::uint32ToString(imFileNotify.to_user_id());  
  13.     file.sTaskID = imFileNotify.task_id();  
  14.     file.nFileSize = imFileNotify.file_size();  
  15.   
  16.     UINT32 nIPCount = imFileNotify.ip_addr_list_size();  
  17.     if (nIPCount <= 0)  
  18.     {  
  19.         return;  
  20.     }  
  21.     const IM::BaseDefine::IpAddr& ipAdd = imFileNotify.ip_addr_list(0);  
  22.     file.sIP = ipAdd.ip();  
  23.     file.nPort = ipAdd.port();  
  24.   
  25.     uint32_t transMode = imFileNotify.trans_mode();  
  26.     if (IM::BaseDefine::TransferFileType::FILE_TYPE_ONLINE == transMode)  
  27.     {  
  28.         file.nClientMode = IM::BaseDefine::ClientFileRole::CLIENT_REALTIME_RECVER;  
  29.     }  
  30.     else if (IM::BaseDefine::TransferFileType::FILE_TYPE_OFFLINE == transMode)  
  31.     {  
  32.         file.nClientMode = IM::BaseDefine::ClientFileRole::CLIENT_OFFLINE_DOWNLOAD;  
  33.     }  
  34.     file.time = static_cast<UInt32>(time(0));  
  35.     TransferFileEntityManager::getInstance()->pushTransferFileEntity(file);  
  36.     LOG__(DEBG, _T("FileTransferSevice_Impl::給你發文件 sFileID = %s"), util::stringToCString(file.sTaskID));  
  37.   
  38.     if (1 == imFileNotify.offline_ready())  
  39.     {  
  40.         //TODO離線文件傳輸結束  
  41.     }  
  42.   
  43.     //連接服務器  
  44.     TransferFileEntityManager::getInstance()->openFileSocketByTaskId(file.sTaskID);  
  45. }  

其實也就是接收方會去連接文件服務器。連接成功以後,在對應的回調函數裏面觸發顯示接收文件對話框。但是此時實際上還不能接收文件,因爲發送方可能還沒準備好。發送方要準備啥呢?前面我們已經介紹了,我們梳理一下上述流程:

1. 發送方先向msg_server請求發送文件,msg_server轉發給file_server;

2. file_server應答msg_server並告訴msg_server自己的地址和端口號;

3. msg_server收到file_server的應答後,先回復發送方,再轉發給接收方;

4. 發送方接着發送登錄請求給file_server,file_server收到請求決定是否給發送方發送拉取文件的數據包。如果是離線文件,則會立刻給發送方發送拉取文件的數據包;如果是在線文件,則需要等待接收方同意接收。


所以,必須過了步驟4,一直到file_server應答了發送方的登錄文件服務器請求後,發送方纔算準備好。此時,file_server知道發送方已經準備好了,給接收方發送數據包CID_FILE_STATE。接收方收到這個命令號後:

[cpp] view plain copy
  1. case IM::BaseDefine::FileCmdID::CID_FILE_STATE://  
  2.         _fileState(pbBody);  

[cpp] view plain copy
  1. void FileTransferSocket::_fileState(IN std::string& body)  
  2. {  
  3.     IM::File::IMFileState imFileState;  
  4.     if (!imFileState.ParseFromString(body))  
  5.     {  
  6.         LOG__(ERR, _T("parse failed,body:%s"), util::stringToCString(body));  
  7.         return;  
  8.     }  
  9.     UINT32 nfileState = imFileState.state();  
  10.   
  11.     std::string taskId = imFileState.task_id();  
  12.     TransferFileEntity fileEntity;  
  13.     if (!TransferFileEntityManager::getInstance()->getFileInfoByTaskId(taskId, fileEntity))  
  14.     {  
  15.         LOG__(ERR, _T("fileState:can't find the fileInfo "));  
  16.         return;  
  17.     }  
  18.   
  19.     switch (nfileState)  
  20.     {  
  21.     case IM::BaseDefine::ClientFileState::CLIENT_FILE_PEER_READY:  
  22.         LOG__(APP, _T("fileState--CLIENT_FILE_PEER_READY "));  
  23.         break;  
  24.     case IM::BaseDefine::ClientFileState::CLIENT_FILE_CANCEL ://取消的了文件傳輸  
  25.         LOG__(APP, _T("fileState--CLIENT_FILE_CANCEL "));  
  26.         {  
  27.             if (fileEntity.pFileObject)  
  28.             {  
  29.                 delete fileEntity.pFileObject;  
  30.                 fileEntity.pFileObject = nullptr;  
  31.             }  
  32.             TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);  
  33.             module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_UPLOAD_CANCEL, fileEntity.sTaskID);  
  34.         }  
  35.         break;  
  36.     case IM::BaseDefine::ClientFileState::CLIENT_FILE_REFUSE://拒絕了文件  
  37.         LOG__(APP, _T("fileState--CLIENT_FILE_REFUSE "));  
  38.         {  
  39.             if (fileEntity.pFileObject)  
  40.             {  
  41.                 delete fileEntity.pFileObject;  
  42.                 fileEntity.pFileObject = nullptr;  
  43.             }  
  44.             TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);  
  45.             module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_UPLOAD_REJECT, fileEntity.sTaskID);  
  46.         }  
  47.         break;  
  48.     case IM::BaseDefine::ClientFileState::CLIENT_FILE_DONE:  
  49.         LOG__(APP, _T("fileState--CLIENT_FILE_DONE "));  
  50.         if (fileEntity.pFileObject)  
  51.         {  
  52.             delete fileEntity.pFileObject;  
  53.             fileEntity.pFileObject = nullptr;  
  54.         }  
  55.         TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);  
  56.         module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_PROGRESSBAR_FINISHED, fileEntity.sTaskID);  
  57.         break;  
  58.     default:  
  59.         break;  
  60.     }  
  61. }  

同理,對於接收方,選擇接收還是拒絕文件的邏輯也是在這裏一起處理的,與此類似,這裏就不再重複敘述了。

接收方下載文件的邏輯和發送方上傳文件的邏輯類似。這裏也不在描述了。


最後說一點我的建議,teamtalk的file_server邏輯、以及與客戶端還有msg_server的邏輯流程加上各種細節寫的比較的細膩,代碼實現上也比較好。強烈建議好好地閱讀這部分的代碼。畢竟很多人在自己實現一個文件服務器時,還是存在不少問題的。


發佈了56 篇原創文章 · 獲贊 95 · 訪問量 51萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章