teamtalk文件服务器学习
背景
对于独立多媒体上传下载,比如文件数据传输量一般都比较大,如果也和普通文本消息收发的通道放在一条连接里,可能会导致消息收发通道出现阻塞,因此一般会开辟新的连接通道来传输二进制的文件流。这种优化方式除了能保护消息收发的核心通道,也能缩短上传下载的链路,提高媒体消息收发的性能。
提纲
- 在线发送和接受文件宏观流程(流程图)
- protobuf 信令解析
- 微观实现
- 思考
- 优化点
宏观流程
在线流程图,后续增加离线流程以及更加细致的分析。
文件传输前置准备
在线文件传输
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小步骤:
- 生成task_id
- 创建传输任务 transfer_task ,分在线和离线
- 设置初始状态为(kTransferTaskStateReady)
- 封装 (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;
}
}
- 首先通过 task_id 查找 传输任务 transfer_task是否存在
- 如果是发送客户端,设置状态为(kTransferTaskStateWaitingReceiver);如果是接收客户端,设置状态为(kTransferTaskStateWaitingSender);最后都会通过自己的连接回一个(CID_FILE_LOGIN_RES)信令。
- 两方都准备后,设置状态为(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; // 文件数据
}