TeamTalk源码分析之http_msg_server对外提供API
2017-07-27 16:23
489 查看
平时;由于公司需要部署一个企业内部即时通讯软件,通过对开源的im进行研究,发现了两款比较合适的im:铛铛和TeamTalk,本文介绍了TeamTalk的消息接口,找了好久才找到,先转载过来做个记录,万一公司真的使用这个im,也方便对其进行二次开发。原文地址:http://www.bluefoxah.org/teamtalk/provide_api.html
人都是有惰性的,我也不排除,下班回来后真的不想再写东西了,只想好好休息下,看看书,然后睡觉。不过之前答应过大家,只能坚持写下去。
在TT群里也发现了一个现象,也许这也是国内IT的普片现象吧——拿来主义。什么都希望别人给你完全提供好,自己最好不动手就可以拿到现成的成果。TT没有做到这一点,于是很多人就来群里问,有技术层面的和非技术层面的,每天都会遇到各种问题,很多问题真的只是自己稍微谷歌,百度下就能知道结果的,却非要来问下,给我的感觉是,你问出这个问题,到别人回答你的时间,你早就在网上搜到答案了。
由于QQ每天消息太多,所以可能会遇到没办法及时或者完整的看完群里的消息。所以如果真的有什么紧急的问题的话,可以加我微信:
添加我微信的时候,请备注下"TeamTalk".
好了言归正传。
作为一个完整的平台,对外提供API是必不可少的,TT第二版跟第一版一个比较明显的变化,就是相比第一版多了一个http_msg_server这个模块,虽然这个模块暂时没有提供太多的功能,但是却也提供了一个参考。不过还是有很多朋友在群里咨询询问如果利用http_msg_server。今天就以提供一个发送消息给某个用户的接口为例子讲解利用http_msg_server。
这次相对于上一篇博客,会涉及到route_server,需要对TT整个架构有个比较明确的了解。
TT的整个架构如下图所示:
对上图各个模块做个简单的说明(之前在新版TT部署中已经做过说明)。
Android/iOS/PC:各种客户端。
login_server:主要负责负载均衡的作用,当客户端来请求的时候,login_server会可以分配一个负载最小的msg_server给客户端。
msg_server:TT的主要服务端,负责维护各个客户端的链接,消息转发等功能。
route_server:负责消息路由的功能,当msg_server发现某个用户不在本服务器内,而又有消息需要发给他,就会将消息转发给route_server,route_server会将消息发给相应的msg_server,由此可知,route_server也维护了一定的用户状态。
db_proxy_server:在TT中负责了主要的业务逻辑,主要与存储层打交道.
msfs:小文件存储,负责存储聊天过程中的图片,语音信息。
http_msg_server:主要对外提供接口功能。
web:简单的管理功能。
本来不打算讲解这一节的,但是发现很多用户到现在还是没能弄清楚tt的消息流程,所以乘机做个讲解,也为下面讲解http_msg_server发送消息做个铺垫。
消息时序图如下:
在讲解如何增加消息API之前,我们先做好如下约束:
1、通过http_msg_server发送消息的url如下:http://ip:port/api/sendmsg
2、数据以post形式提交。
3、所有数据以json格式提交。{"appKey":"1234556","from_id":1,"to_id":2,"msg_content":"Hello World!"}
其中appKey是之前用来校验调用api权限使用的,这里大家根据自己的需要去处理。
我们首先观察下http_msg_server的目录结构:
我们来讲解各个文件的功能:
AttachData:老文件了,对需要放入协议中attach_data封装。
CMakeLists.txt:cmake文件
DBServConn:负责与db_proxy_server的链接。
HttpConn:负责解析外部传入的调用请求。
HttpPdu:主要是解析post数据类。
HttpQuery:啊哈,主要解析业务逻辑的哦,这里凡是http://ip:port/query/xxxx的请求都是这里处理的哦。
RouteServer:负责与route_server的链接。
http_msg_server:main函数入口,负责启动各种链接,启动监听等功能。
httpmsgserver.conf:配置文件
log4cxx.properties:log的配置文件。
由上一节介绍我们大致了解了http_msg_server各个文件的作用,之前我们提到了HttpQuery主要负责/query/xxxx的请求,这里为了简便起见,我们/api/xxx的请求也由它来负责处理(大家完全可以模仿HttpQuery写出一个HttpApi这样的东西出来)。
由于时间关系,下面的我都尽量简单去处理,也没有经过测试,但是大致思路一定是对的。
首先我们在HttpConn.cpp的OnRead函数中修改:
为如下形式:
这里代码很简单,我就不具体解释了,下面我们去HttpQuery.cpp查看DispatchQuery函数:
这个函数开始的主要功能就是解析post的数据,然后更具url调用不同的处理逻辑函数。
我们在这个类中增加一个处理发送消息的函数:
接着我们修改DispatchQuery,增加一个调用发送消息的。
接着我们去实现_ApiSendMsg函数:
我们已经将消息发送到db_proxy_server中去了,db_proxy_server存储完成后会返回,我们需要在DBServConn中增加一个处理。
去DBServConn.cpp中实现:
当db_proxy_server存储完毕返回后,http_msg_server将消息发送到route_server即可完成消息的发送了。
由于时间关系,本次讲解未涉及到route_server的修改,但是基本原理与这些类似,大家可以仿照已有的功能去添加。如果有必要,下次再进行补充。
1、继续杂谈
人都是有惰性的,我也不排除,下班回来后真的不想再写东西了,只想好好休息下,看看书,然后睡觉。不过之前答应过大家,只能坚持写下去。在TT群里也发现了一个现象,也许这也是国内IT的普片现象吧——拿来主义。什么都希望别人给你完全提供好,自己最好不动手就可以拿到现成的成果。TT没有做到这一点,于是很多人就来群里问,有技术层面的和非技术层面的,每天都会遇到各种问题,很多问题真的只是自己稍微谷歌,百度下就能知道结果的,却非要来问下,给我的感觉是,你问出这个问题,到别人回答你的时间,你早就在网上搜到答案了。
由于QQ每天消息太多,所以可能会遇到没办法及时或者完整的看完群里的消息。所以如果真的有什么紧急的问题的话,可以加我微信:
添加我微信的时候,请备注下"TeamTalk".
好了言归正传。
2、如何提供一个接口
作为一个完整的平台,对外提供API是必不可少的,TT第二版跟第一版一个比较明显的变化,就是相比第一版多了一个http_msg_server这个模块,虽然这个模块暂时没有提供太多的功能,但是却也提供了一个参考。不过还是有很多朋友在群里咨询询问如果利用http_msg_server。今天就以提供一个发送消息给某个用户的接口为例子讲解利用http_msg_server。这次相对于上一篇博客,会涉及到route_server,需要对TT整个架构有个比较明确的了解。
3、TT架构图
TT的整个架构如下图所示:对上图各个模块做个简单的说明(之前在新版TT部署中已经做过说明)。
Android/iOS/PC:各种客户端。
login_server:主要负责负载均衡的作用,当客户端来请求的时候,login_server会可以分配一个负载最小的msg_server给客户端。
msg_server:TT的主要服务端,负责维护各个客户端的链接,消息转发等功能。
route_server:负责消息路由的功能,当msg_server发现某个用户不在本服务器内,而又有消息需要发给他,就会将消息转发给route_server,route_server会将消息发给相应的msg_server,由此可知,route_server也维护了一定的用户状态。
db_proxy_server:在TT中负责了主要的业务逻辑,主要与存储层打交道.
msfs:小文件存储,负责存储聊天过程中的图片,语音信息。
http_msg_server:主要对外提供接口功能。
web:简单的管理功能。
4、发送消息流程
本来不打算讲解这一节的,但是发现很多用户到现在还是没能弄清楚tt的消息流程,所以乘机做个讲解,也为下面讲解http_msg_server发送消息做个铺垫。消息时序图如下:
5、前奏
在讲解如何增加消息API之前,我们先做好如下约束:1、通过http_msg_server发送消息的url如下:http://ip:port/api/sendmsg
2、数据以post形式提交。
3、所有数据以json格式提交。{"appKey":"1234556","from_id":1,"to_id":2,"msg_content":"Hello World!"}
其中appKey是之前用来校验调用api权限使用的,这里大家根据自己的需要去处理。
我们首先观察下http_msg_server的目录结构:
lanhu:http_msg_server lanhu$ tree . ├── AttachData.cpp ├── AttachData.h ├── CMakeLists.txt ├── DBServConn.cpp ├── DBServConn.h ├── HttpConn.cpp ├── HttpConn.h ├── HttpPdu.cpp ├── HttpPdu.h ├── HttpQuery.cpp ├── Http 4000 Query.h ├── RouteServConn.cpp ├── RouteServConn.h ├── http_msg_server.cpp ├── httpmsgserver.conf └── log4cxx.properties
我们来讲解各个文件的功能:
AttachData:老文件了,对需要放入协议中attach_data封装。
CMakeLists.txt:cmake文件
DBServConn:负责与db_proxy_server的链接。
HttpConn:负责解析外部传入的调用请求。
HttpPdu:主要是解析post数据类。
HttpQuery:啊哈,主要解析业务逻辑的哦,这里凡是http://ip:port/query/xxxx的请求都是这里处理的哦。
RouteServer:负责与route_server的链接。
http_msg_server:main函数入口,负责启动各种链接,启动监听等功能。
httpmsgserver.conf:配置文件
log4cxx.properties:log的配置文件。
6、实践
由上一节介绍我们大致了解了http_msg_server各个文件的作用,之前我们提到了HttpQuery主要负责/query/xxxx的请求,这里为了简便起见,我们/api/xxx的请求也由它来负责处理(大家完全可以模仿HttpQuery写出一个HttpApi这样的东西出来)。由于时间关系,下面的我都尽量简单去处理,也没有经过测试,但是大致思路一定是对的。
首先我们在HttpConn.cpp的OnRead函数中修改:
if (m_HttpParser.IsReadAll()) { string url = m_HttpParser.GetUrl(); if (strncmp(url.c_str(), "/query/", 7) == 0) { string content = m_HttpParser.GetBodyContent(); CHttpQuery* pQueryInstance = CHttpQuery::GetInstance(); pQueryInstance->DispatchQuery(url, content, this); } else { log("url unknown, url=%s ", url.c_str()); Close(); } }
为如下形式:
if (m_HttpParser.IsReadAll()) { string url = m_HttpParser.GetUrl(); if (strncmp(url.c_str(), "/query/", 7) == 0) { string content = m_HttpParser.GetBodyContent(); CHttpQuery* pQueryInstance = CHttpQuery::GetInstance(); pQueryInstance->DispatchQuery(url, content, this); } else if(strncmp(url.c_str(), "/api/", 5)) { string content = m_HttpParser.GetBodyContent(); CHttpQuery::GetInstance()->DispatchQuery(url, content, this); } else { log("url unknown, url=%s ", url.c_str()); Close(); } }
这里代码很简单,我就不具体解释了,下面我们去HttpQuery.cpp查看DispatchQuery函数:
void CHttpQuery::DispatchQuery(std::string& url, std::string& post_data, CHttpConn* pHttpConn) { ++g_total_query; log("DispatchQuery, url=%s, content=%s ", url.c_str(), post_data.c_str()); Json::Reader reader; Json::Value value; Json::Value root; if ( !reader.parse(post_data, value) ) { log("json parse failed, post_data=%s ", post_data.c_str()); pHttpConn->Close(); return; } string strErrorMsg; string strAppKey; HTTP_ERROR_CODE nRet = HTTP_ERROR_SUCCESS; try { string strInterface(url.c_str() + strlen("/query/")); strAppKey = value["app_key"].asString(); string strIp = pHttpConn->GetPeerIP(); uint32_t nUserId = value["req_user_id"].asUInt(); nRet = _CheckAuth(strAppKey, nUserId, strInterface, strIp); } catch ( std::runtime_error msg) { nRet = HTTP_ERROR_INTERFACE; } if(HTTP_ERROR_SUCCESS != nRet) { if(nRet < HTTP_ERROR_MAX) { root["error_code"] = nRet; root["error_msg"] = HTTP_ERROR_MSG[nRet]; } else { root["error_code"] = -1; root["error_msg"] = "未知错误"; } string strResponse = root.toStyledString(); pHttpConn->Send((void*)strResponse.c_str(), strResponse.length()); return; } // process post request with post content if (strcmp(url.c_str(), "/query/CreateGroup") == 0) { _QueryCreateGroup(strAppKey, value, pHttpConn); } else if (strcmp(url.c_str(), "/query/ChangeMembers") == 0) { _QueryChangeMember(strAppKey, value, pHttpConn); } else { log("url not support "); pHttpConn->Close(); return; } }
这个函数开始的主要功能就是解析post的数据,然后更具url调用不同的处理逻辑函数。
我们在这个类中增加一个处理发送消息的函数:
static void _ApiSendMsg(uint32_t nFromId, uint32_t nToId, const string& strMsg);
接着我们修改DispatchQuery,增加一个调用发送消息的。
if (strcmp(url.c_str(), "/query/CreateGroup") == 0) { _QueryCreateGroup(strAppKey, value, pHttpConn); } else if (strcmp(url.c_str(), "/query/ChangeMembers") == 0 cb18 ) { _QueryChangeMember(strAppKey, value, pHttpConn); } else if (strcmp(url.c_str(), "/api/sendmsg") == 0) { uint32_t nFromId = value["req_id"].asUInt(); uint32_t nToId = value["to_id"].asUInt(); string strMsg = value["msg_content"].asString(); _ApiSendMsg(nFromId, nToId, strMsg); } else { log("url not support "); pHttpConn->Close(); return; }
接着我们去实现_ApiSendMsg函数:
void CHttpQuery::_ApiSendMsg(uint32_t nFromId, uint32_t nToId, const string &strMsg, CHttpConn* pHttpConn) { HTTP::CDBServConn* pDBConn = HTTP::get_db_serv_conn(); if(!pDBConn) { log("no db server"); pHttpConn->Close(); } IM::Message::IMMsgData msg; msg.set_from_user_id(nFromId); msg.set_msg_id(0); msg.set_to_session_id(nToId); msg.set_create_time(time(NULL)); msg.set_msg_type(::IM::BaseDefine::MSG_TYPE_SINGLE_TEXT); msg.set_msg_data(strMsg); CImPdu pdu; pdu.SetPBMsg(&msg); pdu.SetServiceId(IM::BaseDefine::SID_MSG); pdu.SetCommandId(IM::BaseDefine::CID_MSG_DATA); pDBConn->SendPdu(&pdu); pHttpConn->Close(); }
我们已经将消息发送到db_proxy_server中去了,db_proxy_server存储完成后会返回,我们需要在DBServConn中增加一个处理。
void _HandleSendMsg(CImPdu* pPdu);
去DBServConn.cpp中实现:
void CDBServConn::_HandleSendMsg(CImPdu *pPdu) { IM::Message::IMMsgData msg; CHECK_PB_PARSE_MSG(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength())); uint32_t from_user_id = msg.from_user_id(); uint32_t to_user_id = msg.to_session_id(); uint32_t msg_id = msg.msg_id(); if (msg_id == 0) { log("_HandleSendMsg, write db failed, %u->%u.", from_user_id, to_user_id); return; } log("_HandleSendMsg, from_user_id=%u, to_user_id=%u, msg_id=%u", from_user_id, to_user_id, msg_id); CRouteServConn* pRouteConn = get_route_serv_conn(); if (pRouteConn) { pRouteConn->SendPdu(pPdu); } }
当db_proxy_server存储完毕返回后,http_msg_server将消息发送到route_server即可完成消息的发送了。
由于时间关系,本次讲解未涉及到route_server的修改,但是基本原理与这些类似,大家可以仿照已有的功能去添加。如果有必要,下次再进行补充。
相关文章推荐
- TeamTalk源码分析之http_msg_server对外提供API
- TeamTalk源码分析之http_msg_server对外提供API
- 【原创】k8s源码分析----apiserver之APIGroupVersion
- Ambari-Server Rest API处理2(Ambari-Server通过Rest API进行服务安装、部署、操作流程+操作源码分析)
- TeamTalk源码分析(九) —— 服务器端route_server源码分析
- BaseHTTPServer与CGIHTTPServer源码分析 - 技术手札
- HTTP SERVER[ListenAndServe函数]源码分析图
- 【原创】k8s源码分析------kube-apiserver分析(1)
- 【原创】k8s源码分析------kube-apiserver分析(2)
- 【原创】k8s源码分析------kube-apiserver分析(3)
- nova-api源码分析(WSGI server的创建及启动)
- docker1.9源码分析(五):server分配handler提供服务的流程
- Python标准库源码分析:BaseHTTPServer.py
- 【kubernetes/k8s源码分析】kube-apiserver 启动
- libevent源码分析:http-server例子
- kubernetes的kube-apiserver组件源码分析
- NICTCLAS:词法分析系统ICTCLAS的.NET版(源码提供下载) http://www.cnblogs.com/edison1024/archive/2006/05/03/390832.aspx
- TeamTalk源码分析之login_server
- golang-net/http源码分析之http server
- TeamTalk源码分析(五) —— 服务器端msg_server源码分析