TeamTalk 文件服务器(file_server)分析
写在前边
过去一年自己学习了很多c++后台开发的相关知识,但回头发现其实自己实际掌握的知识并不多。我觉着可能有下面几个原因吧,第一点:把看技术书籍当做完任务,看书的时候缺乏耐心,看着看着就一目十行,想着尽快把书看完,骗自己说看完其实就掌握了,其实这样书中的细节很多都没有掌握,只是知道了一些概念而已。第二点,很多时候只是看书,看源码,鲜少编码,这样就会导致你看起来你好像什么都懂,让你自己写代码你很难写出来。第三点,缺乏总结,我觉着说一个人最快进步的方式就是犯了错或者做完一件事情之后去总结,改进,而不是什么都不做,这样永远也不会进步,该掌握的知识还是没有掌握,改犯的错还是会犯。
最近这一个月以来都是在看teamtalk 服务器的相关代码,但发现自己看了好几遍也说不出所以然来,也不能完成把看过的东西描述出来,所以在仔细看完file_server之后,想写一篇东西做个总结,因为自己平时很不擅长写东西,做总结,希望这篇东西能对自己这方面能力有所历练。
概述
Teamtalk文件服务器主要提供客户端文件传输的服务,分为在线传输和离线传输两种文件传输形式,本文对于文件传输的主要业务逻辑进行分析,不分析teamtalk的网络传输库。
teamtalk的后台服务整体架构图
file_server在文件传输时需要与msg_server 进行消息传递,现先将后台服务整体架构图列出来
源码分析与整体流程分析
1.file_server服务连接相关
msg_server 启动的时候的时候会去连接file_server(必须在msg_server之前启动)
void init_file_serv_conn(serv_info_t* server_list, uint32_t server_count)
{
g_file_server_list = server_list;
g_file_server_count = server_count;
serv_init<CFileServConn>(g_file_server_list, g_file_server_count);
netlib_register_timer(file_server_conn_timer_callback, NULL, 1000);
s_file_handler = CFileHandler::getInstance();
}
连接成功之后,msg_server 会发消息查询file_server 的ip地址(CID:CID_OTHER_FILE_SERVER_IP_REQ,具体消息:IMFileServerIPReq)
void CFileServConn::OnConfirm()
{
log("connect to file server success ");
m_bOpen = true;
m_connect_time = get_tick_count();
g_file_server_list[m_serv_idx].reconnect_cnt = MIN_RECONNECT_CNT / 2;
//连上file_server以后,给file_server发送获取ip地址的数据包
IM::Server::IMFileServerIPReq msg;
CImPdu pdu;
pdu.SetPBMsg(&msg);
pdu.SetServiceId(SID_OTHER);
pdu.SetCommandId(CID_OTHER_FILE_SERVER_IP_REQ);
SendPdu(&pdu);
}
file_server 收到消息后,会将自己配置文件中的IP地址发给msg_server
void FileMsgServerConn::_HandleGetServerAddressReq(CImPdu* pPdu) {
IM::Server::IMFileServerIPRsp msg;
const std::list<IM::BaseDefine::IpAddr>& addrs = ConfigUtil::GetInstance()->GetAddressList();
for (std::list<IM::BaseDefine::IpAddr>::const_iterator it=addrs.begin(); it!=addrs.end(); ++it) {
IM::BaseDefine::IpAddr* addr = msg.add_ip_addr_list();
*addr = *it;
log("Upload file_client_conn addr info, ip=%s, port=%d", addr->ip().c_str(), addr->port());
}
SendMessageLite(this, SID_OTHER, CID_OTHER_FILE_SERVER_IP_RSP, pPdu->GetSeqNum(), &msg);
}
msg_server 收到Ip地址之后会把ip地址存到m_ip_list中
void CFileServConn::_HandleFileServerIPRsp(CImPdu* pPdu)
{
IM::Server::IMFileServerIPRsp msg;
CHECK_PB_PARSE_MSG(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()));
uint32_t ip_addr_cnt = msg.ip_addr_list_size();
for (uint32_t i = 0; i < ip_addr_cnt ; i++)
{
IM::BaseDefine::IpAddr ip_addr = msg.ip_addr_list(i);
log("_HandleFileServerIPRsp -> %s : %d ", ip_addr.ip().c_str(), ip_addr.port());
m_ip_list.push_back(ip_addr);
}
}
2.开始发文件msg_server 连接
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9vc4P5V4-1584002589483)(C:\Users\dykes\AppData\Roaming\Typora\typora-user-images\image-20200307084035864.png)]
pc客户端在聊天对话框点击上图的发送按之后,会弹出一个文件选择对话框,让用户选择要发送的文件,向文件模块发送 (module::getFileTransferModule()->sendFile)发送文件所在的路径,收文件用户Id,收用户如果在线发送在线文件,否则发送离线文件
else if (msg.pSender == m_pBtnsendfile) //文件传输
{
module::UserInfoEntity userInfo;
if (!module::getUserListModule()->getUserInfoBySId(m_sId, userInfo))
{
LOG__(ERR, _T("SendFile can't find the sid"));
return;
}
CFileDialog fileDlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST
, _T("文件|*.*||"), AfxGetMainWnd());
fileDlg.m_ofn.Flags |= OFN_NOCHANGEDIR;
fileDlg.DoModal();
CString sPathName;
POSITION nPos = fileDlg.GetStartPosition();
if (nPos != NULL)
{
sPathName = fileDlg.GetNextPathName(nPos);
}
if (sPathName.IsEmpty())
return;
module::getFileTransferModule()->sendFile(sPathName, m_sId, userInfo.isOnlne());
}
然后把文件的大小,发送端用户fromId,接收端用户toId,文件大小,文件全路径,文件状态(在线/离线文件)发送给消息服务器msg_server发消息(CID_FILE_REQUEST)
BOOL FileTransferModule_Impl::sendFile(IN const CString& sFilePath, IN const std::string& sSendToSID,IN BOOL bOnlineMode)
{
if (TransferFileEntityManager::getInstance()->checkIfIsSending(sFilePath))
{
return FALSE;
}
TransferFileEntity fileEntity;
//获取文件大小
struct __stat64 buffer;
_wstat64(sFilePath, &buffer);
fileEntity.nFileSize = (UInt32)buffer.st_size;
if (0 != fileEntity.nFileSize)
{
CString strFileName = sFilePath;
strFileName.Replace(_T("\\"), _T("/"));//mac上对于路径字符“\”需要做特殊处理,windows上则可以识别
fileEntity.sFileName = util::cStringToString(strFileName);
fileEntity.sFromID = module::getSysConfigModule()->userID();
fileEntity.sToID = sSendToSID;
uint32_t transMode = 0;
transMode = bOnlineMode ? IM::BaseDefine::TransferFileType::FILE_TYPE_ONLINE : IM::BaseDefine::TransferFileType::FILE_TYPE_OFFLINE;
LOG__(DEBG,_T("FileTransferSevice_Impl::sendFile sTaskID = %s"), util::stringToCString(fileEntity.sTaskID));
imcore::IMLibCoreStartOperationWithLambda(
[=]()
{
IM::File::IMFileReq imFileReq;
LOG__(APP, _T("imFileReq,name=%s,size=%d,toId=%s"),util::stringToCString(fileEntity.sFileName)
,fileEntity.nFileSize,util::stringToCString(fileEntity.sToID));
imFileReq.set_from_user_id(util::stringToInt32(fileEntity.sFromID));
imFileReq.set_to_user_id(util::stringToInt32(fileEntity.sToID));
imFileReq.set_file_name(fileEntity.sFileName);
imFileReq.set_file_size(fileEntity.nFileSize);
imFileReq.set_trans_mode(static_cast<IM::BaseDefine::TransferFileType>(transMode));
module::getTcpClientModule()->sendPacket(IM::BaseDefine::ServiceID::SID_FILE
, IM::BaseDefine::FileCmdID::CID_FILE_REQUEST
, &imFileReq);
});
return TRUE;
}
LOG__(ERR, _T("fileEntity FileSize error,size = %d"), fileEntity.nFileSize);
return FALSE;
}
msg_server 收到消息后
case CID_FILE_REQUEST:
s_file_handler->HandleClientFileRequest(this, pPdu);
break;
会从msg_server 和file_server 中建立的连接中随机挑选一个连接,发送CID_OTHER_FILE_TRANSFER_REQ如果是离线文件,直接把消息发给发给file_server ,如果是在线文件,需要查询当前msg_server是否是否与接收端pc连接,如果没有连接,则需要和去route_server 查询状态,如果没有file_server 直接回应pc端失败
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();
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());
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());
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);
}
}
fil_server收到消息后,会新建一个guid作为一个文件任务标识符,然后新建一个文件任务插入队列,接下来把这个任务状态 加上taskId回给msg_server,
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();
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();
}
}
msg_server接收到这个回应后,会把上述建立的文件传输任务相关信息还有之前缓存的IP地址包装成IMFileRsp通过cid CID_FILE_RESPONSE发送给客户端发送端,如果前面建立任务成功(result=0)则通知所有接收端的连接(CID_FILE_NOTIFY) 包括任务ID,文件服务器ip地址等,并且发送给route_server ,然后route_server 通知所有的服务端
3,客户端与file_server 相关
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;
}
enum ClientFileRole {
CLIENT_REALTIME_SENDER = 1, 在线发送端
CLIENT_REALTIME_RECVER = 2,在线接收端
CLIENT_OFFLINE_UPLOAD = 3,离线发送端
CLIENT_OFFLINE_DOWNLOAD = 4 离线接收端
};
(1)在线文件传输
发送端CID_FILE_RESPONSE:发送端收到CID_FILE_RESPONSE后,会新建一个任务相关信息到任务map,如果是在线文件置clientrole为CLIENT_REALTIM