teamtalk文件服务器学习篇

背景

对于独立多媒体上传下载,比如文件数据传输量一般都比较大,如果也和普通文本消息收发的通道放在一条连接里,可能会导致消息收发通道出现阻塞,因此一般会开辟新的连接通道来传输二进制的文件流。这种优化方式除了能保护消息收发的核心通道,也能缩短上传下载的链路,提高媒体消息收发的性能。

提纲

  1. 在线发送和接受文件宏观流程(流程图)
  2. protobuf 信令解析
  3. 微观实现
  4. 思考
  5. 优化点

宏观流程

在线流程图,后续增加离线流程以及更加细致的分析。

请添加图片描述

文件传输前置准备

在线文件传输
client->msg_server
MsgConn.cpp处理业务:

void CMsgConn::HandlePdu(CImPdu* pPdu)
{
	// request authorization check
	if (pPdu->GetCommandId() != CID_LOGIN_REQ_USERLOGIN && !IsOpen() && IsKickOff()) {
        log("HandlePdu, wrong msg. ");
        throw CPduException(pPdu->GetServiceId(), pPdu->GetCommandId(), ERROR_CODE_WRONG_SERVICE_ID, "HandlePdu error, user not login. ");
		return;
    }
	switch (pPdu->GetCommandId()) {
        case CID_OTHER_HEARTBEAT:
            _HandleHeartBeat(pPdu);
            break;                    
        case CID_FILE_REQUEST:
            s_file_handler->HandleClientFileRequest(this, pPdu);
            break;
        case CID_FILE_HAS_OFFLINE_REQ:
            s_file_handler->HandleClientFileHasOfflineReq(this, pPdu);
            break;
        case CID_FILE_ADD_OFFLINE_REQ:
            s_file_handler->HandleClientFileAddOfflineReq(this, pPdu);
            break;
        case CID_FILE_DEL_OFFLINE_REQ:
            s_file_handler->HandleClientFileDelOfflineReq(this, pPdu);
            break;
        default:
            log("wrong msg, cmd id=%d, user id=%u. ", pPdu->GetCommandId(), GetUserId());
            break;
	}
}

msg_server 处理(== CID_FILE_REQUEST==) 信令,调用 HandleClientFileRequest 函数,转发给 file_server,信令为 (CID_OTHER_FILE_TRANSFER_REQ)

void CFileHandler::HandleClientFileRequest(CMsgConn* pMsgConn, CImPdu* pPdu)
{
    IM::File::IMFileReq msg;
    CHECK_PB_PARSE_MSG(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()));
    
    uint32_t from_id = pMsgConn->GetUserId(); // xxx 为什么不从msg中拿(跟下方写法一样)?
    uint32_t to_id = msg.to_user_id();
    string file_name = msg.file_name();
    uint32_t file_size = msg.file_size();
    uint32_t trans_mode = msg.trans_mode();
    log("HandleClientFileRequest, %u->%u, fileName: %s, trans_mode: %u.", from_id, to_id, file_name.c_str(), trans_mode);
    
    CDbAttachData attach(ATTACH_TYPE_HANDLE, pMsgConn->GetHandle()); // xxx 什么作用?
    CFileServConn* pFileConn = get_random_file_serv_conn();
    if (pFileConn)
    {
        IM::Server::IMFileTransferReq msg2;
        msg2.set_from_user_id(from_id);
        msg2.set_to_user_id(to_id);
        msg2.set_file_name(file_name);
        msg2.set_file_size(file_size);
        msg2.set_trans_mode((IM::BaseDefine::TransferFileType)trans_mode);
        msg2.set_attach_data(attach.GetBuffer(), attach.GetLength());
        CImPdu pdu;
        pdu.SetPBMsg(&msg2);
        pdu.SetServiceId(SID_OTHER);
        pdu.SetCommandId(CID_OTHER_FILE_TRANSFER_REQ);
        pdu.SetSeqNum(pPdu->GetSeqNum()); // stack
        
        if (IM::BaseDefine::FILE_TYPE_OFFLINE == trans_mode)
        {
            pFileConn->SendPdu(&pdu);
        }
        else //IM::BaseDefine::FILE_TYPE_ONLINE
        {
            CImUser* pUser = CImUserManager::GetInstance()->GetImUserById(to_id);
            if (pUser && pUser->GetPCLoginStatus())//已有对应的账号pc登录状态
            {
                pFileConn->SendPdu(&pdu);
            }
            else//无对应用户的pc登录状态,向route_server查询状态
            {
                //no pc_client in this msg_server, check it from route_server
                CPduAttachData attach_data(ATTACH_TYPE_HANDLE_AND_PDU_FOR_FILE, pMsgConn->GetHandle(), pdu.GetBodyLength(), pdu.GetBodyData());
                IM::Buddy::IMUsersStatReq msg3;
                msg3.set_user_id(from_id);
                msg3.add_user_id_list(to_id);
                msg3.set_attach_data(attach_data.GetBuffer(), attach_data.GetLength());
                CImPdu pdu2;
                pdu2.SetPBMsg(&msg3);
                pdu2.SetServiceId(SID_BUDDY_LIST);
                pdu2.SetCommandId(CID_BUDDY_LIST_USERS_STATUS_REQUEST);
                pdu2.SetSeqNum(pPdu->GetSeqNum());
                CRouteServConn* route_conn = get_route_serv_conn();
                if (route_conn)
                {
                    route_conn->SendPdu(&pdu2);
                }
            }
        }
    }
    else
    {
        log("HandleClientFileRequest, no file server.   ");
        IM::File::IMFileRsp msg2;
        msg2.set_result_code(1);
        msg2.set_from_user_id(from_id);
        msg2.set_to_user_id(to_id);
        msg2.set_file_name(file_name);
        msg2.set_task_id("");
        msg2.set_trans_mode((IM::BaseDefine::TransferFileType)trans_mode);
        CImPdu pdu;
        pdu.SetPBMsg(&msg2);
        pdu.SetServiceId(SID_FILE);
        pdu.SetCommandId(CID_FILE_RESPONSE);
        pdu.SetSeqNum(pPdu->GetSeqNum());
        pMsgConn->SendPdu(&pdu);
    }
}

在这里 msg_server 会检测 file_server 是否已经启动(通过pFileConn判断),如果没有启动,则直接发包告诉客户端,file_server不存在。

另外,如果该传输模式是在线文件传输,会判断接收文件的用户是否和发送用户在同一台msg_server上。不在的话,则给route_server发送消息,查找该用户所在的msg_server(这个不具体介绍了,后续写文章总结分析route_server)

最后会将文件发送请求转发给 file_server,包的命令号是CID_OTHER_FILE_TRANSFER_REQ。

void FileMsgServerConn::HandlePdu(CImPdu* pdu) {
    switch (pdu->GetCommandId()) {
        case CID_OTHER_HEARTBEAT:
            _HandleHeartBeat(pdu);
            break;
            
        case CID_OTHER_FILE_TRANSFER_REQ:
            _HandleMsgFileTransferReq(pdu);
            break ;
           //msg_server连接file_server成功以后发来查询file_server的ip地址的命令号
        case CID_OTHER_FILE_SERVER_IP_REQ:
            _HandleGetServerAddressReq(pdu);
            break;
            
        default:
            log("No such cmd id = %u", pdu->GetCommandId());
            break;
    }
}

file_server处理 (CID_OTHER_FILE_TRANSFER_REQ)信令,调用 _HandleMsgFileTransferReq函数。

void FileMsgServerConn::_HandleMsgFileTransferReq(CImPdu* pdu) {
    IM::Server::IMFileTransferReq transfer_req;
    CHECK_PB_PARSE_MSG(transfer_req.ParseFromArray(pdu->GetBodyData(), pdu->GetBodyLength()));
    
    
    uint32_t from_id = transfer_req.from_user_id();
    uint32_t to_id = transfer_req.to_user_id();
    
    IM::Server::IMFileTransferRsp transfer_rsp;
    transfer_rsp.set_result_code(1);
    transfer_rsp.set_from_user_id(from_id);
    transfer_rsp.set_to_user_id(to_id);
    transfer_rsp.set_file_name(transfer_req.file_name());
    transfer_rsp.set_file_size(transfer_req.file_size());
    transfer_rsp.set_task_id("");
    transfer_rsp.set_trans_mode(transfer_req.trans_mode());
    transfer_rsp.set_attach_data(transfer_req.attach_data());

    bool rv = false;
    do {
        std::string task_id = GenerateUUID(); // stack 看下原理
        if (task_id.empty()) {
            log("Create task id failed");
            break;
        }
        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());
        
        BaseTransferTask* transfer_task = TransferTaskManager::GetInstance()->NewTransferTask(
                                                                                           transfer_req.trans_mode(),                                                                                            task_id,                                                                                              from_id,                                                                                              to_id,                                                                                              transfer_req.file_name(),                                                                                              transfer_req.file_size());
        
        if (transfer_task == NULL) {
            // 创建未成功
            // close connection with msg svr
            // need_close = true;
            log("Create task failed");
            break;
        }
        
        transfer_rsp.set_result_code(0);
        transfer_rsp.set_task_id(task_id);
        rv = true;
        // need_seq_no = false;
        
        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);
    } while (0);
    
    ::SendMessageLite(this, SID_OTHER, CID_OTHER_FILE_TRANSFER_RSP, pdu->GetSeqNum(), &transfer_rsp);
    
    if (!rv) {
        // 未创建成功,关闭连接
        Close();
    }
}

该函数有4小步骤:

  1. 生成task_id
  2. 创建传输任务 transfer_task ,分在线和离线
  3. 设置初始状态为(kTransferTaskStateReady
  4. 封装 (IMFileTransferRsp

上述代码会为本次传输任务创建一个唯一的标识uuid作为taskid,然后离线模式创建 OfflineTransferTask 或者在线模式创建 OnlineTransferTask,并加入一个成员变量transfer_tasks_中进行管理。

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) {
    BaseTransferTask* transfer_task = NULL;
    
    TransferTaskMap::iterator it = transfer_tasks_.find(task_id);
    if (it==transfer_tasks_.end()) {
        if (trans_mode == IM::BaseDefine::FILE_TYPE_ONLINE) {
            transfer_task = new OnlineTransferTask(task_id, from_user_id, to_user_id, file_name, file_size);
        } else if (trans_mode == IM::BaseDefine::FILE_TYPE_OFFLINE) {
            transfer_task = new OfflineTransferTask(task_id, from_user_id, to_user_id, file_name, file_size);
        } else {
            log("Invalid trans_mode = %d", trans_mode);
        }
        
        if (transfer_task) {
            transfer_tasks_.insert(std::make_pair(task_id, transfer_task));
        }
    } else {
        log("Task existed by task_id=%s, why?????", task_id.c_str());
    }
    
    return transfer_task;
}

这个map transfer_tasks_是在定时器里面进行定期处理的,处理的依据是当前任务的状态,比如已经完成的任务就可以从map中移除了(定时处理逻辑还不懂,后续需要研究):

void TransferTaskManager::OnTimer(uint64_t tick) {
    for (TransferTaskMap::iterator it = transfer_tasks_.begin(); it != transfer_tasks_.end();) {
        BaseTransferTask* task = it->second;
        if (task == NULL) {
            transfer_tasks_.erase(it++);
            continue;
        }
        
        if (task->state() != kTransferTaskStateWaitingUpload &&
            task->state() == kTransferTaskStateTransferDone) {
            long esp = time(NULL) - task->create_time();
            if (esp > ConfigUtil::GetInstance()->GetTaskTimeout()) {
                if (task->GetFromConn()) {
                    FileClientConn* conn = reinterpret_cast<FileClientConn*>(task->GetFromConn());
                    conn->ClearTransferTask();
                }
                if (task->GetToConn()) {
                    FileClientConn* conn = reinterpret_cast<FileClientConn*>(task->GetToConn());
                    conn->ClearTransferTask();
                }
                delete task;
                transfer_tasks_.erase(it++);
                continue;
            }
        }
        
        ++it;
    }
}

完成这些工作以后,组装的应答包命令号是CID_OTHER_FILE_TRANSFER_RSP,回复给msg_server。

void CFileServConn::HandlePdu(CImPdu* pPdu)
{
	switch (pPdu->GetCommandId()) {
        case CID_OTHER_HEARTBEAT:
            break;
        case CID_OTHER_FILE_TRANSFER_RSP:
            _HandleFileMsgTransRsp(pPdu);
            break;
        case CID_OTHER_FILE_SERVER_IP_RSP:
            _HandleFileServerIPRsp(pPdu);
            break;
        default:
            log("unknown cmd id=%d ", pPdu->GetCommandId());
            break;
	}
}

msg_server 随后处理 (CID_OTHER_FILE_TRANSFER_RSP) 信令,调用 _HandleFileMsgTransRsp 函数。

void CFileServConn::_HandleFileMsgTransRsp(CImPdu* pPdu)
{
    IM::Server::IMFileTransferRsp msg;
    CHECK_PB_PARSE_MSG(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()));

    uint32_t result = msg.result_code();
    uint32_t from_id = msg.from_user_id();
    uint32_t to_id = msg.to_user_id();
    string file_name = msg.file_name();
    uint32_t file_size = msg.file_size();
    string task_id = msg.task_id();
    uint32_t trans_mode = msg.trans_mode();
    CDbAttachData attach((uchar_t*)msg.attach_data().c_str(), msg.attach_data().length());
    log("HandleFileMsgTransRsp, result: %u, from_user_id: %u, to_user_id: %u, file_name: %s, \
        task_id: %s, trans_mode: %u. ", result, from_id, to_id,
        file_name.c_str(), task_id.c_str(), trans_mode);

    const list<IM::BaseDefine::IpAddr>* ip_addr_list = GetFileServerIPList();

    IM::File::IMFileRsp msg2;
    msg2.set_result_code(result);
    msg2.set_from_user_id(from_id);
    msg2.set_to_user_id(to_id);
    msg2.set_file_name(file_name);
    msg2.set_task_id(task_id);
    msg2.set_trans_mode((IM::BaseDefine::TransferFileType)trans_mode);
    for (list<IM::BaseDefine::IpAddr>::const_iterator it = ip_addr_list->begin(); it != ip_addr_list->end(); it++)
    {
        IM::BaseDefine::IpAddr ip_addr_tmp = *it;
        IM::BaseDefine::IpAddr* ip_addr = msg2.add_ip_addr_list();
        ip_addr->set_ip(ip_addr_tmp.ip());
        ip_addr->set_port(ip_addr_tmp.port());
    }
    CImPdu pdu;
    pdu.SetPBMsg(&msg2);
    pdu.SetServiceId(SID_FILE);
    pdu.SetCommandId(CID_FILE_RESPONSE);
    pdu.SetSeqNum(pPdu->GetSeqNum());
    uint32_t handle = attach.GetHandle();
    
    CMsgConn* pFromConn = CImUserManager::GetInstance()->GetMsgConnByHandle(from_id, handle);
    if (pFromConn)
    {
        pFromConn->SendPdu(&pdu);
    }
    
    if (result == 0)
    {
        IM::File::IMFileNotify msg3;
        msg3.set_from_user_id(from_id);
        msg3.set_to_user_id(to_id);
        msg3.set_file_name(file_name);
        msg3.set_file_size(file_size);
        msg3.set_task_id(task_id);
        msg3.set_trans_mode((IM::BaseDefine::TransferFileType)trans_mode);
        msg3.set_offline_ready(0);
        for (list<IM::BaseDefine::IpAddr>::const_iterator it = ip_addr_list->begin(); it != ip_addr_list->end(); it++)
        {
            IM::BaseDefine::IpAddr ip_addr_tmp = *it;
            IM::BaseDefine::IpAddr* ip_addr = msg3.add_ip_addr_list();
            ip_addr->set_ip(ip_addr_tmp.ip());
            ip_addr->set_port(ip_addr_tmp.port());
        }
        CImPdu pdu2;
        pdu2.SetPBMsg(&msg3);
        pdu2.SetServiceId(SID_FILE);
        pdu2.SetCommandId(CID_FILE_NOTIFY);
        
        //send notify to target user
        CImUser* pToUser = CImUserManager::GetInstance()->GetImUserById(to_id);
        if (pToUser)
        {
            pToUser->BroadcastPduWithOutMobile(&pdu2);
        }
        
        //send to route server
        CRouteServConn* pRouteConn = get_route_serv_conn();
        if (pRouteConn) {
            pRouteConn->SendPdu(&pdu2);
        }
    }
}

发送方请求发送文件后,msg_server 先给其返回 (CID_FILE_RESPONSE 信令),此时附带了task_id 和 对应的file_server的ip和port地址。接下来发送方需要登录file_server。

在线文件传输时,msg_server 还会将请求在线文件传输发送给接收方(CID_FILE_NOTIFY 信令),里面包含了对应的file_server的ip和port地址,以及其他的必要信息,接受方收到后也是要登录file_server。

(通知方式也是一样,如果接收方在该msg_server上,直接发给该用户;不在的话,发给路由服务route_server)。当然接收到文件发送的端只有pc端,移动端会被过滤掉的,也就是说移动端不会收到发送文件的请求(why???为什么要这样设计???)。

文件正式传输

CID_FILE_LOGIN_REQ
真正开始传输文件时先要登录 file_server,使用user 和 task_id,并注明自己的角色。

message IMFileLoginReq{
	//cmd id:	0x0501
	required uint32 user_id = 1;
	required string task_id = 2;
	required IM.BaseDefine.ClientFileRole file_role= 3;
}

enum ClientFileRole{
    CLIENT_REALTIME_SENDER  = 0x01; // 在线发送者
    CLIENT_REALTIME_RECVER  = 0x02; // 在线接收者
    CLIENT_OFFLINE_UPLOAD   = 0x03; // 离线上传
    CLIENT_OFFLINE_DOWNLOAD = 0x04; // 离线下载
}
void FileTransferModule_Impl::onPacket(imcore::TTPBHeader& header, std::string& pbBody)
{
	switch (header.getCommandId())
	{
	case IM::BaseDefine::FileCmdID::CID_FILE_RESPONSE://发送“文件请求/离线文件”-返回
		_sendfileResponse(pbBody);
		break;
	case IM::BaseDefine::FileCmdID::CID_FILE_NOTIFY://收到“发送文件请求”
		_fileNotify(pbBody);
		break;
	case IM::BaseDefine::FileCmdID::CID_FILE_HAS_OFFLINE_RES:
		_hasOfflineRes(pbBody);
		break;
	default:
		return;
	}
}

发送客户端收到(CID_FILE_RESPONSE 信令)登录文件服务器,并注明自己的角色为(IM::BaseDefine::ClientFileRole::CLIENT_REALTIME_SENDER

void FileTransferModule_Impl::_sendfileResponse(IN std::string& body)
{
	IM::File::IMFileRsp imFileRsp;
	if (!imFileRsp.ParseFromString(body))
	{
		LOG__(ERR, _T("parse failed,body:%s"), util::stringToCString(body));
		return;
	}

	UInt32 nResult = imFileRsp.result_code();
	if (nResult != 0)
	{
		LOG__(ERR, _T("_sendfileResponse result != 0"));
		module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_UPLOAD_FAILED);
	}

	TransferFileEntity fileEntity;
	fileEntity.sTaskID = imFileRsp.task_id();
    assert(!fileEntity.sTaskID.empty());
	fileEntity.sFromID = util::uint32ToString(imFileRsp.from_user_id());
	fileEntity.sToID = util::uint32ToString(imFileRsp.to_user_id());
	fileEntity.sFileName = imFileRsp.file_name();
	fileEntity.setSaveFilePath(util::stringToCString(fileEntity.sFileName));//发送方文件地址,就是保存地址
	fileEntity.time = static_cast<UInt32>(time(0));
	uint32_t transMode = imFileRsp.trans_mode();
	if (IM::BaseDefine::TransferFileType::FILE_TYPE_ONLINE == transMode)
	{
		fileEntity.nClientMode = IM::BaseDefine::ClientFileRole::CLIENT_REALTIME_SENDER;
	}
	else if (IM::BaseDefine::TransferFileType::FILE_TYPE_OFFLINE == transMode)
	{
		fileEntity.nClientMode = IM::BaseDefine::ClientFileRole::CLIENT_OFFLINE_UPLOAD;
	}
	fileEntity.pFileObject = new TransferFile(util::stringToCString(fileEntity.sFileName),FALSE);
	if (fileEntity.pFileObject)
	{
		fileEntity.nFileSize = fileEntity.pFileObject->length();
	}
	
	UINT32 nIPCount = imFileRsp.ip_addr_list_size();
	if (nIPCount <= 0)
	{
		return;
	}
	const IM::BaseDefine::IpAddr& ipAdd = imFileRsp.ip_addr_list(0);
	fileEntity.sIP = ipAdd.ip();
	fileEntity.nPort = ipAdd.port();

	if (!TransferFileEntityManager::getInstance()->pushTransferFileEntity(fileEntity))
		TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);

	LOG__(DEBG, _T("FileTransferSevice_Impl::准备连接文件服务器 sTaskId = %s"), util::stringToCString(fileEntity.sTaskID));
	TransferFileEntityManager::getInstance()->openFileSocketByTaskId(fileEntity.sTaskID);
}

接收客户端收到(CID_FILE_NOTIFY 信令)登录文件服务器,并注明自己的角色为(IM::BaseDefine::ClientFileRole::CLIENT_REALTIME_RECVER

void FileTransferModule_Impl::_fileNotify(IN std::string& body)
{
	IM::File::IMFileNotify imFileNotify;
	if (!imFileNotify.ParseFromString(body))
	{
		LOG__(ERR, _T("parse failed,body:%s"), util::stringToCString(body));
		return;
	}
	TransferFileEntity file;
	file.sFileName = imFileNotify.file_name();
	file.sFromID = util::uint32ToString(imFileNotify.from_user_id());
	file.sToID = util::uint32ToString(imFileNotify.to_user_id());
	file.sTaskID = imFileNotify.task_id();
	file.nFileSize = imFileNotify.file_size();

	UINT32 nIPCount = imFileNotify.ip_addr_list_size();
	if (nIPCount <= 0)
	{
		return;
	}
	const IM::BaseDefine::IpAddr& ipAdd = imFileNotify.ip_addr_list(0);
	file.sIP = ipAdd.ip();
	file.nPort = ipAdd.port();

	uint32_t transMode = imFileNotify.trans_mode();
	if (IM::BaseDefine::TransferFileType::FILE_TYPE_ONLINE == transMode)
	{
		file.nClientMode = IM::BaseDefine::ClientFileRole::CLIENT_REALTIME_RECVER;
	}
	else if (IM::BaseDefine::TransferFileType::FILE_TYPE_OFFLINE == transMode)
	{
		file.nClientMode = IM::BaseDefine::ClientFileRole::CLIENT_OFFLINE_DOWNLOAD;
	}
	file.time = static_cast<UInt32>(time(0));
	TransferFileEntityManager::getInstance()->pushTransferFileEntity(file);
	LOG__(DEBG, _T("FileTransferSevice_Impl::给你发文件 sFileID = %s"), util::stringToCString(file.sTaskID));

	if (1 == imFileNotify.offline_ready())
	{
		//TODO离线文件传输结束
	}

	//连接服务器
	TransferFileEntityManager::getInstance()->openFileSocketByTaskId(file.sTaskID);
}

文件服务器收到(CID_FILE_LOGIN_REQ)信令

void FileClientConn::HandlePdu(CImPdu* pdu) {
    switch (pdu->GetCommandId()) {
        case CID_OTHER_HEARTBEAT:
            _HandleHeartBeat(pdu);
            break;
        case CID_FILE_LOGIN_REQ:
            _HandleClientFileLoginReq(pdu);
            break;
            
        case CID_FILE_STATE:
            _HandleClientFileStates(pdu);
            break ;
        case CID_FILE_PULL_DATA_REQ:
            _HandleClientFilePullFileReq(pdu);
            break ;
        case CID_FILE_PULL_DATA_RSP:
            _HandleClientFilePullFileRsp( pdu);
            break ;

        default:
            log("no such cmd id: %u", pdu->GetCommandId());
            break;
    }
}
  1. 首先通过 task_id 查找 传输任务 transfer_task是否存在
  2. 如果是发送客户端,设置状态为(kTransferTaskStateWaitingReceiver);如果是接收客户端,设置状态为(kTransferTaskStateWaitingSender);最后都会通过自己的连接回一个(CID_FILE_LOGIN_RES)信令。
  3. 两方都准备后,设置状态为(kTransferTaskStateWaitingTransfer),拿到(接收方连接),发送(CID_FILE_STATE)信令,携带(CLIENT_FILE_PEER_READY)???
void FileClientConn::_HandleClientFileLoginReq(CImPdu* pdu) {
    IM::File::IMFileLoginReq login_req;
    CHECK_PB_PARSE_MSG(login_req.ParseFromArray(pdu->GetBodyData(), pdu->GetBodyLength()));
    
    uint32_t user_id = login_req.user_id();
    string task_id = login_req.task_id();
    IM::BaseDefine::ClientFileRole mode = login_req.file_role();
    
    log("Client login, user_id=%d, task_id=%s, file_role=%d", user_id, task_id.c_str(), mode);
    
    BaseTransferTask* transfer_task = NULL;
    
    bool rv = false;
    do {
        // 查找任务是否存在
        transfer_task = TransferTaskManager::GetInstance()->FindByTaskID(task_id);
        
        if (transfer_task == NULL) {
            if (mode == CLIENT_OFFLINE_DOWNLOAD) {
                // 文件不存在,检查是否是离线下载,有可能是文件服务器重启
                // 尝试从磁盘加载
                transfer_task = TransferTaskManager::GetInstance()->NewTransferTask(task_id, user_id);
                // 需要再次判断是否加载成功
                if (transfer_task == NULL) {
                    log("Find task id failed, user_id=%u, taks_id=%s, mode=%d", user_id, task_id.c_str(), mode);
                    break;
                }
            } else {
                log("Can't find task_id, user_id=%u, taks_id=%s, mode=%d", user_id, task_id.c_str(), mode);
                break;
            }
        }
        
        // 状态转换
        rv = transfer_task->ChangePullState(user_id, mode);
        if (!rv) {
            // log();
            break;
            //
        }
        
        // Ok
        auth_ = true;
        transfer_task_ = transfer_task;
        user_id_ = user_id;
        // 设置conn
        transfer_task->SetConnByUserID(user_id, this);
        rv = true;
        
    } while (0);

    IM::File::IMFileLoginRsp login_rsp;
    login_rsp.set_result_code(rv?0:1);
    login_rsp.set_task_id(task_id);

    ::SendMessageLite(this, SID_FILE, CID_FILE_LOGIN_RES, pdu->GetSeqNum(), &login_rsp);

    if (rv) {
        if (transfer_task->GetTransMode() == FILE_TYPE_ONLINE) {
            if (transfer_task->state() == kTransferTaskStateWaitingTransfer) {
                CImConn* conn = transfer_task_->GetToConn();
                if (conn) {
                    _StatesNotify(CLIENT_FILE_PEER_READY, task_id, transfer_task_->from_user_id(), conn);
                } else {
                    log("to_conn is close, close me!!!");
                    Close();
                }
                // _StatesNotify(CLIENT_FILE_PEER_READY, task_id, user_id, this);
                // transfer_task->StatesNotify(CLIENT_FILE_PEER_READY, task_id, user_id_);
            }
        } else {
            if (transfer_task->state() == kTransferTaskStateWaitingUpload) {
                
                OfflineTransferTask* offline = reinterpret_cast<OfflineTransferTask*>(transfer_task);
                
                IM::File::IMFilePullDataReq pull_data_req;
                pull_data_req.set_task_id(task_id);
                pull_data_req.set_user_id(user_id);
                pull_data_req.set_trans_mode(FILE_TYPE_OFFLINE);
                pull_data_req.set_offset(0);
                pull_data_req.set_data_size(offline->GetNextSegmentBlockSize());
                
                ::SendMessageLite(this, SID_FILE, CID_FILE_PULL_DATA_REQ, &pull_data_req);

                log("Pull Data Req");
            }
        }
    } else {
        Close();
    }
}
bool OnlineTransferTask::ChangePullState(uint32_t user_id, int file_role) {
    // 在线文件传输,初始状态:kTransferTaskStateReady
    //  状态转换流程 kTransferTaskStateReady
    //        --> kTransferTaskStateWaitingSender或kTransferTaskStateWaitingReceiver
    //        --> kTransferTaskStateWaitingTransfer
    //
    
    bool rv = false;
    
    do {
        rv = CheckByUserIDAndFileRole(user_id, file_role);
        if (!rv) {
            //
            log("Check error! user_id=%d, file_role=%d", user_id, file_role);
            break;
        }
        
        if (state_ != kTransferTaskStateReady && state_ != kTransferTaskStateWaitingSender && state_ != kTransferTaskStateWaitingReceiver) {
            log("Invalid state, valid state is kTransferTaskStateReady or kTransferTaskStateWaitingSender or kTransferTaskStateWaitingReceiver, but state is %d", state_);
            break;
        }
        
        if (state_ == kTransferTaskStateReady) {
            // 第一个用户进来
            // 如果是sender,则-->kTransferTaskStateWaitingReceiver
            // 如果是receiver,则-->kTransferTaskStateWaitingSender
            if (file_role == CLIENT_REALTIME_SENDER) {
                state_ = kTransferTaskStateWaitingReceiver;
            } else {
                state_ = kTransferTaskStateWaitingSender;
            }
        } else {
            if (state_ == kTransferTaskStateWaitingReceiver) {
                // 此时必须是receiver
                // 需要检查是否是receiver
                if (file_role != CLIENT_REALTIME_RECVER) {
                    log("Invalid user, user_id = %d, but to_user_id_ = %d", user_id, to_user_id_);
                    break;
                }
            } else if (state_ == kTransferTaskStateWaitingSender) {
                // 此时必须是sender
                // 需要检查是否是sender
                if (file_role != CLIENT_REALTIME_SENDER) {
                    log("Invalid user, user_id = %d, but to_user_id_ = %d", user_id, to_user_id_);
                    break;
                }
            }
            
            state_ = kTransferTaskStateWaitingTransfer;
            
        }
        
        SetLastUpdateTime();
        rv = true;
    } while (0);
    
    return rv;
}

客户端收到(CID_FILE_STATE),

void FileTransferSocket::onReceiveData(const char* data, int32_t size)
{
    std::string pbBody;
    imcore::TTPBHeader pbHeader; 
	try
	{
        pbHeader.unSerialize((byte*)data, imcore::HEADER_LENGTH);
        pbBody.assign(data + imcore::HEADER_LENGTH, size - imcore::HEADER_LENGTH);

        if (IM::BaseDefine::OtherCmdID::CID_OTHER_HEARTBEAT == pbHeader.getCommandId()
            && IM::BaseDefine::ServiceID::SID_OTHER == pbHeader.getModuleId())
            return;
	}
	catch (CPduException e)
	{
		LOG__(ERR, _T("onPacket CPduException serviceId:%d,commandId:%d,errCode:%d")
			, e.GetModuleId(), e.GetCommandId(), e.GetErrorCode());
		return;
	}
	catch (...)
	{
		LOG__(ERR, _T("FileTransferSocket onPacket unknown exception"));
		return;
	}
    UInt16 ncmdid = pbHeader.getCommandId();
    switch (ncmdid)
	{	 
    case IM::BaseDefine::FileCmdID::CID_FILE_LOGIN_RES:
        _fileLoginResponse(pbBody);
		break;
    case IM::BaseDefine::FileCmdID::CID_FILE_PULL_DATA_REQ://发文件
        _filePullDataReqResponse(pbBody);
		break;
    case IM::BaseDefine::FileCmdID::CID_FILE_PULL_DATA_RSP://收文件
        _filePullDataRspResponse(pbBody);
		break;
    case IM::BaseDefine::FileCmdID::CID_FILE_STATE://
        _fileState(pbBody);
	default:
		break;
	}
}

收到 CLIENT_FILE_PEER_READY 啥也没干。

void FileTransferSocket::_fileState(IN std::string& body)
{
	IM::File::IMFileState imFileState;
    if (!imFileState.ParseFromString(body))
    {
        LOG__(ERR, _T("parse failed,body:%s"), util::stringToCString(body));
        return;
    }
	UINT32 nfileState = imFileState.state();

	std::string taskId = imFileState.task_id();
	TransferFileEntity fileEntity;
	if (!TransferFileEntityManager::getInstance()->getFileInfoByTaskId(taskId, fileEntity))
	{
		LOG__(ERR, _T("fileState:can't find the fileInfo "));
		return;
	}

	switch (nfileState)
	{
	case IM::BaseDefine::ClientFileState::CLIENT_FILE_PEER_READY:
		LOG__(APP, _T("fileState--CLIENT_FILE_PEER_READY "));
		break;
	case IM::BaseDefine::ClientFileState::CLIENT_FILE_CANCEL ://取消的了文件传输
		LOG__(APP, _T("fileState--CLIENT_FILE_CANCEL "));
		{
            if (fileEntity.pFileObject)
            {
                delete fileEntity.pFileObject;
                fileEntity.pFileObject = nullptr;
            }
			TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);
			module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_UPLOAD_CANCEL, fileEntity.sTaskID);
		}
		break;
	case IM::BaseDefine::ClientFileState::CLIENT_FILE_REFUSE://拒绝了文件
		LOG__(APP, _T("fileState--CLIENT_FILE_REFUSE "));
		{
            if (fileEntity.pFileObject)
            {
                delete fileEntity.pFileObject;
                fileEntity.pFileObject = nullptr;
            }
			TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);
			module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_UPLOAD_REJECT, fileEntity.sTaskID);
		}
		break;
	case IM::BaseDefine::ClientFileState::CLIENT_FILE_DONE:
		LOG__(APP, _T("fileState--CLIENT_FILE_DONE "));
		if (fileEntity.pFileObject)
		{
			delete fileEntity.pFileObject;
			fileEntity.pFileObject = nullptr;
		}
		TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);
		module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_PROGRESSBAR_FINISHED, fileEntity.sTaskID);
		break;
	default:
		break;
	}
}

如果接受,然后怎么触发的(CID_FILE_PULL_DATA_REQ)???客户端逻辑???
不论如何,客户端会发送此(CID_FILE_PULL_DATA_REQ)信令,
文件服务器收到(CID_FILE_PULL_DATA_REQ)信令后,调用 _HandleClientFilePullFileReq,转发给(发送客户端

void FileClientConn::HandlePdu(CImPdu* pdu) {
    switch (pdu->GetCommandId()) {
        case CID_OTHER_HEARTBEAT:
            _HandleHeartBeat(pdu);
            break;
        case CID_FILE_LOGIN_REQ:
            _HandleClientFileLoginReq(pdu);
            break;
            
        case CID_FILE_STATE:
            _HandleClientFileStates(pdu);
            break ;
        case CID_FILE_PULL_DATA_REQ:
            _HandleClientFilePullFileReq(pdu);
            break ;
        case CID_FILE_PULL_DATA_RSP:
            _HandleClientFilePullFileRsp( pdu);
            break ;

        default:
            log("no such cmd id: %u", pdu->GetCommandId());
            break;
    }
}
  void FileClientConn::_HandleClientFilePullFileReq(CImPdu *pdu) {
    if (!auth_ || !transfer_task_) {
        log("Recv a client_file_state, but auth is false");
        return;
    }
    
    IM::File::IMFilePullDataReq pull_data_req;
    CHECK_PB_PARSE_MSG(pull_data_req.ParseFromArray(pdu->GetBodyData(), pdu->GetBodyLength()));
    
    uint32_t user_id = pull_data_req.user_id();
    string task_id = pull_data_req.task_id();
    uint32_t mode = pull_data_req.trans_mode();
    uint32_t offset = pull_data_req.offset();
    uint32_t datasize = pull_data_req.data_size();

    log("Recv FilePullFileReq, user_id=%d, task_id=%s, file_role=%d, offset=%d, datasize=%d", user_id, task_id.c_str(), mode, offset, datasize);

    // rsp
    IM::File::IMFilePullDataRsp pull_data_rsp;
    pull_data_rsp.set_result_code(1);
    pull_data_rsp.set_task_id(task_id);
    pull_data_rsp.set_user_id(user_id);
    pull_data_rsp.set_offset(offset);
    pull_data_rsp.set_file_data("");

    // BaseTransferTask* transfer_task = NULL;
    int rv = -1;
    
    do {
        // 检查user_id
        if (user_id != user_id_) {
            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_);
            break;
        }

        // 检查task_id
        if (transfer_task_->task_id() != task_id) {
            log("Received task_id valid, recv_task_id = %s, this_task_id = %s", task_id.c_str(), transfer_task_->task_id().c_str());
            break;
        }
        
        // 离线传输,需要下载文件
        // 在线传输,从发送者拉数据
        // user_id为transfer_task.to_user_id
        if (!transfer_task_->CheckToUserID(user_id)) {
            log("user_id equal transfer_task.to_user_id, but user_id=%d, transfer_task.to_user_id=%d", user_id, transfer_task_->to_user_id());
            break;
        }
        
        rv =  transfer_task_->DoPullFileRequest(user_id, offset, datasize, pull_data_rsp.mutable_file_data());
        
        if (rv == -1) {
            break;
        }
        
        pull_data_rsp.set_result_code(0);

        if (transfer_task_->GetTransMode() == FILE_TYPE_ONLINE) {
            OnlineTransferTask* online = reinterpret_cast<OnlineTransferTask*>(transfer_task_);
            online->SetSeqNum(pdu->GetSeqNum());
            CImConn* conn = transfer_task_->GetOpponentConn(user_id);
            if (conn) {
                conn->SendPdu(pdu);
                // SendMessageLite(conn, SID_FILE, CID_FILE_PULL_DATA_RSP, pdu->GetSeqNum(), &pull_data_rsp);
            }
            // SendPdu(&pdu);
        } else {
            SendMessageLite(this, SID_FILE, CID_FILE_PULL_DATA_RSP, pdu->GetSeqNum(), &pull_data_rsp);
            if (rv == 1) {
                _StatesNotify(CLIENT_FILE_DONE, task_id, transfer_task_->from_user_id(), this);
            }
        }
        
        // rv = 0;
    } while (0);


    if (rv!=0) {
        Close();
    }

}

发送端收到(CID_FILE_PULL_DATA_REQ)信令后,处理发送文件逻辑,然后返回(CID_FILE_PULL_DATA_RSP)信令。如果是在线文件,就直接转发含有文件数据的包;如果是离线文件,则存入文件服务器。

void FileTransferSocket::_filePullDataReqResponse(IN std::string& body)//发
{
	IM::File::IMFilePullDataReq imFilePullDataReq;
    if (!imFilePullDataReq.ParseFromString(body))
    {
        LOG__(ERR, _T("parse failed,body:%s"), util::stringToCString(body));
        return;
    }
	UInt32 fileSize = imFilePullDataReq.data_size();
	UInt32 fileOffset = imFilePullDataReq.offset();
	std::string taskId = imFilePullDataReq.task_id();
	
	TransferFileEntity fileEntity;
	if (!TransferFileEntityManager::getInstance()->getFileInfoByTaskId(taskId, fileEntity))
	{
		LOG__(ERR, _T("PullDataReqResponse: can't find the fileInfo"));
		return;
	}
	LOG__(DEBG, _T("send:taskId=%s,filesize=%d,name=%s,BolckSize=%d")
		,util::stringToCString(fileEntity.sTaskID)
		,fileEntity.nFileSize
		,fileEntity.getRealFileName()
        ,fileSize);
	std::string buff;
	if (nullptr == fileEntity.pFileObject)
	{
		LOG__(ERR, _T("PullDataReqResponse: file boject Destoryed!"));
		return;
	}
	fileEntity.pFileObject->readBlock(fileOffset, fileSize, buff);//读取本地文件的数据块
	IM::File::IMFilePullDataRsp imFilePullDataRsp;//todo check
    imFilePullDataRsp.set_result_code(0);
	imFilePullDataRsp.set_task_id(taskId);
	imFilePullDataRsp.set_user_id(util::stringToInt32(fileEntity.sFromID));
	imFilePullDataRsp.set_offset(fileOffset);
    imFilePullDataRsp.set_file_data((void*)buff.data(), fileSize);

    //send packet
    sendPacket(IM::BaseDefine::ServiceID::SID_FILE, IM::BaseDefine::FileCmdID::CID_FILE_PULL_DATA_RSP
        , &imFilePullDataRsp);

	fileEntity.nProgress = fileOffset + fileSize;
	if (fileEntity.nProgress < fileEntity.nFileSize)
	{
		//更新进度条
		TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);//保存当前进度
		module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_UPDATA_PROGRESSBAR
            , fileEntity.sTaskID);
	}
	else//传输完成
	{
		if (fileEntity.pFileObject)
		{
			delete fileEntity.pFileObject;
			fileEntity.pFileObject = nullptr;
		}
		module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_PROGRESSBAR_FINISHED
            , fileEntity.sTaskID);
	}
	TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);
}

不断请求数据和发送数据,如果发送完毕,接收端发送done。

void FileTransferSocket::_filePullDataRspResponse(IN std::string& body)//收
{
	IM::File::IMFilePullDataRsp imFilePullDataRsp;
    if (!imFilePullDataRsp.ParseFromString(body))
    {
        LOG__(ERR, _T("parse failed,body:%s"), util::stringToCString(body));
        return;
    }
	UInt32 nRes = imFilePullDataRsp.result_code();
	if (0 != nRes)
	{
		LOG__(ERR, _T("PullDataRspResponse: error result:%d"),nRes);
		return;
	}
	std::string taskId = imFilePullDataRsp.task_id();
	const std::string& strData = imFilePullDataRsp.file_data();//todo ?????要长度
	void* pData = (void*)(strData.data());
	UInt32 nBlockSize = strData.size();
	UInt32 fileOffset = imFilePullDataRsp.offset();
	TransferFileEntity fileEntity;
	if (!TransferFileEntityManager::getInstance()->getFileInfoByTaskId(taskId, fileEntity))
	{
		LOG__(ERR, _T("can't find the fileInfo"));
		return;
	}
	LOG__(DEBG, _T("receive:taskId=%s,filesize=%d,name=%s,BolckSize=%d")
		, util::stringToCString(fileEntity.sTaskID)
		, fileEntity.nFileSize
		, fileEntity.getRealFileName()
        , nBlockSize);

	//存文件...
	if (!fileEntity.pFileObject->writeBlock(fileOffset, nBlockSize, pData))
	{
		LOG__(DEBG, _T("writeBlock failed "));
		return;
	}

	fileEntity.nProgress = fileOffset + nBlockSize;
	if (fileEntity.nProgress < fileEntity.nFileSize)
	{
		//更新进度条
		TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);//保存当前进度
		module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_UPDATA_PROGRESSBAR
            , fileEntity.sTaskID);

		//继续发file block req...
		int mode = fileEntity.nClientMode == IM::BaseDefine::ClientFileRole::CLIENT_OFFLINE_DOWNLOAD ? IM::BaseDefine::TransferFileType::FILE_TYPE_OFFLINE : IM::BaseDefine::TransferFileType::FILE_TYPE_ONLINE;
		IM::File::IMFilePullDataReq imFilePullDataReq;
		imFilePullDataReq.set_task_id(taskId);
		imFilePullDataReq.set_user_id(util::stringToInt32(fileEntity.sToID));
		imFilePullDataReq.set_trans_mode(static_cast<IM::BaseDefine::TransferFileType>(mode));
		imFilePullDataReq.set_offset(fileEntity.nProgress);

        UInt32 pullSize = fileEntity.nFileSize - fileEntity.nProgress;
        pullSize > nBlockSize ? imFilePullDataReq.set_data_size(nBlockSize) : imFilePullDataReq.set_data_size(pullSize);
		
		// 发包
        sendPacket(IM::BaseDefine::ServiceID::SID_FILE, IM::BaseDefine::FileCmdID::CID_FILE_PULL_DATA_REQ, &imFilePullDataReq);
	}
	else//传输完成
	{
		if (fileEntity.pFileObject)
		{
			delete fileEntity.pFileObject;
			fileEntity.pFileObject = nullptr;
		}

        //告知对方文件传输完成了。
        IM::File::IMFileState imFileState;
        imFileState.set_state(IM::BaseDefine::ClientFileState::CLIENT_FILE_DONE);
        imFileState.set_task_id(taskId);
        imFileState.set_user_id(util::stringToInt32(fileEntity.sToID));
        sendPacket(IM::BaseDefine::ServiceID::SID_FILE, IM::BaseDefine::FileCmdID::CID_FILE_STATE, &imFileState);

		TransferFileEntityManager::getInstance()->updateFileInfoBysTaskID(fileEntity);
		module::getFileTransferModule()->asynNotifyObserver(module::KEY_FILESEVER_PROGRESSBAR_FINISHED, fileEntity.sTaskID);
	}
}

proto设计信令


enum FileCmdID{
    CID_FILE_LOGIN_REQ = 0x0501; // sender/receiver need to login to
    CID_FILE_LOGIN_RES = 0x0502; // login success or failure
    CID_FILE_STATE                  = 0x0503;
    CID_FILE_PULL_DATA_REQ = 0x0504;
    CID_FILE_PULL_DATA_RSP = 0x0505;
    // To MsgServer
    CID_FILE_REQUEST= 0x0506; // sender -> receiver
    CID_FILE_RESPONSE = 0x0507; // receiver -> sender
    CID_FILE_NOTIFY = 0x0508;
    CID_FILE_HAS_OFFLINE_REQ        = 0x0509;
    CID_FILE_HAS_OFFLINE_RES        = 0x050a;
    CID_FILE_ADD_OFFLINE_REQ        = 0x050b;
    CID_FILE_DEL_OFFLINE_REQ        = 0x050c;
}

CID_FILE_REQUEST

封装 IM::File::IMFileReq imFileReq;
message IMFileReq{
	//cmd id: 	0x0506
	required uint32 from_user_id = 1;
	required uint32 to_user_id = 2;
	required string file_name = 3; // 文件名
	required uint32 file_size = 4; // 文件大小
	required IM.BaseDefine.TransferFileType trans_mode = 5;
}

enum TransferFileType{
    FILE_TYPE_ONLINE = 0x01; // 在线
    FILE_TYPE_OFFLINE = 0x02; // 离线
}

CID_OTHER_FILE_TRANSFER_REQ

msg_server向file_server请求创建传输任务,此时注意到结构体里面并没有task_id。

封装 IM::Server::IMFileTransferReq msg2;

message IMFileTransferReq{
	//cmd id:			0x0715
	required uint32 from_user_id = 1;
	required uint32 to_user_id = 2;
	required string file_name = 3; // 文件名
	required uint32 file_size = 4; // 文件大小
	required IM.BaseDefine.TransferFileType trans_mode = 5; 
	optional bytes attach_data = 20; // ???
}

CID_OTHER_FILE_TRANSFER_RSP

file_server 回复 msg_server,此时注意到结构体里面有 task_id

封装 IM::Server::IMFileTransferRsp transfer_rsp;

message IMFileTransferRsp{
	//cmd id:			0x0716
	required uint32 result_code = 1; // 1:失败 0:成功
	required uint32 from_user_id = 2;
	required uint32 to_user_id = 3;
	optional string file_name = 4;
	optional uint32 file_size = 5;
	optional string task_id = 6;
	optional IM.BaseDefine.TransferFileType trans_mode = 7;
	optional bytes attach_data = 20; //???什么作用???
}

CID_FILE_RESPONSE

发送方请求发送文件后,msg_server回应其请求,此时附带了task_id 和 对应file_server 的 ip 和 port。接下来发送方需要登录file_server。

封装 IM::File::IMFileRsp msg2;

message IMFileRsp{
	//cmd id: 	0x0507
	required uint32	result_code = 1;			//1: 失败 0:成功
	required uint32 from_user_id = 2;
	required uint32 to_user_id = 3;
	required string file_name = 4;
	required string task_id = 5;
	repeated IM.BaseDefine.IpAddr ip_addr_list = 6;
	required IM.BaseDefine.TransferFileType trans_mode = 7;
}

CID_FILE_NOTIFY

在线文件传输时,msg_server将请求在线文件传输发送给接收方,里面包含了对应的file_server 的 ip 和 port地址。接收方收到后也是要登录 file_server 。

封装 IM::File::IMFileNotify msg3;

message IMFileNotify{
	//cmd id: 	0x0508
	required uint32 from_user_id = 1;
	required uint32 to_user_id = 2;
	required string file_name = 3;
	required uint32 file_size = 4;
	required string task_id = 5;
	repeated IM.BaseDefine.IpAddr ip_addr_list = 6;
	required IM.BaseDefine.TransferFileType trans_mode = 7;
	required uint32 offline_ready = 8;				//1:True 0:False
}

CID_FILE_LOGIN_REQ

真正开始传输文件时先要登录 file_server ,使用 user_id 和 task_id ,并注明自己的角色

message IMFileLoginReq{
	//cmd id:	0x0501
	required uint32 user_id = 1;
	required string task_id = 2;
	required IM.BaseDefine.ClientFileRole file_role= 3;
}

enum ClientFileRole{
    CLIENT_REALTIME_SENDER  = 0x01; // 在线发送者
    CLIENT_REALTIME_RECVER  = 0x02; // 在线接收者
    CLIENT_OFFLINE_UPLOAD   = 0x03; // 离线上传
    CLIENT_OFFLINE_DOWNLOAD = 0x04; // 离线下载
}

CID_FILE_LOGIN_RES

message IMFileLoginRsp{
	//cmd id:	0x0502
	required uint32 result_code = 1;		//0:successed    1:failed
	required string task_id = 2;
}

CID_FILE_PULL_DATA_REQ

文件传输采用拉取的方式。接收方主动去拉取数据,服务器向客户端请求上传文件数据。

message IMFilePullDataReq{
	//cmd id:	0x0504
	required string task_id = 1;
	required uint32 user_id = 2;
	required IM.BaseDefine.TransferFileType trans_mode = 3;
	required uint32 offset = 4; // ?
	required uint32 data_size = 5; // ?
}

CID_FILE_PULL_DATA_RSP

发送方被动去回应数据。

message IMFilePullDataRsp{
	//cmd id: 	0x0505
	required uint32 result_code = 1;
	required string task_id = 2;
	required uint32 user_id = 3;
	required uint32 offset = 4; // 偏移位置
	required bytes file_data = 5; // 文件数据
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值