日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

teamtalk的conn框架简介及netlib线程安全问题

發(fā)布時間:2024/1/17 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 teamtalk的conn框架简介及netlib线程安全问题 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

2019獨角獸企業(yè)重金招聘Python工程師標準>>>

最近把teamtalk的conn_map改成了智能指針,但改了總要多方面試試有沒有問題,總不能編譯通過,能正常啟動就萬事大吉了。所以就寫了一個shell client客戶端來進行功能的測試。

tt的官方上一次發(fā)布版本里有一個test目錄,里面寫了一個簡易的測試客戶端。不過這個test根本不可用,因為不是代碼寫的有錯誤,就是功能缺失,所以我只好親自動手重做了。

在做的過程中總是發(fā)現(xiàn)client發(fā)起的connect偶爾會有連接不上,這個偶爾的概率非常低,但既然發(fā)生了,那肯定是有問題的。于是就查啊查啊。。。

test的測試客戶端相當于是一個命令行shell的客戶端,也就是沒有圖形界面,你的功能通過在終端上輸入命令來完成。目前我僅實現(xiàn)了注冊和登陸。未來打算把聊天等各種功能也做了,這樣就差不多相當于實現(xiàn)了一個命令行式的客戶端。有人也許會問,TT有windows,mac,ios,android全平臺客戶端,做個命令行的客戶端有什么用?當然有用了,測試功能方便啊,你不用考慮折騰界面就能把各種功能給測了。未來添加功能也方便寫測試,比如我現(xiàn)在新增一個注冊功能,在這個命令行上面輸入reg xx oo,那么一個用戶名叫xx的用戶便以密碼為oo注冊進了數(shù)據(jù)庫。對命令的解析可比做界面的事件響應函數(shù)方便多了。

好了,現(xiàn)在問題來了,shell命令的輸入是需要一個死循環(huán)來反復等待用戶輸入的,而tt的異步網(wǎng)絡框架又需要另一個死循環(huán),如果兩個死循環(huán)放在同一線程里顯然不行,所以就把接受用戶輸入的死循環(huán)放到了另一個線程里面。那么當用戶輸入reg xx oo時,將這條命令解析出用戶名和密碼,并開始啟動注冊流程,這一切都是在另一個線程里做的。

注冊流程是怎么樣的呢?這里先講一講TT的conn框架。

TT的底層異步網(wǎng)絡庫是將socket和epoll封裝成一個netlib庫,你要做的任何有關異步網(wǎng)絡的操作都是通過調用netlib來實現(xiàn)的。但netlib只是一個原始的對tcp報文發(fā)送接收的異步庫,你要做即時通訊,還需要在此基礎上實現(xiàn)一套通訊協(xié)議,并且封裝一組接口來完成對這些協(xié)議的操作。

于是TT就定義了一個叫

CImConn的類,這個類定義在imconn.h里面。

為了方便大家閱讀,這里摘入部分代碼

class CImConn : public CRefObject { public:CImConn();virtual ~CImConn();int Send(void* data, int len);virtual void OnRead();virtual void OnWrite();bool IsBusy() { return m_busy; }int SendPdu(CImPdu* pPdu) { return Send(pPdu->GetBuffer(), pPdu->GetLength()); }virtual void OnConnect(net_handle_t handle) { m_handle = handle; }virtual void OnConfirm(){}virtual void OnClose(){}virtual void OnTimer(uint64_t){}virtual void OnWriteCompelete(){}virtual void HandlePdu(CImPdu*){}



接口的含義是顯而易見的,OnConnect就是有連接接入事件的響應函數(shù), OnConfirm這個含義有點含糊,其實就是你發(fā)起netlib_connect,當這個connect連接建立完成時調用的函數(shù)。

這里為了方便你理解,做個類比,如果你做過android開發(fā),想一下每次你寫一個應用的最常用的流程是什么樣的?定義一個類繼承Activity,然后override里面的onCreate等xx方法,是不是很相似?當然如果你沒有安卓開發(fā)經(jīng)驗,類比一下ios吧,ios也是這樣的,如果ios也沒做過,那也沒關系,繼續(xù)往下看。

這里CImConn其實就是留給你繼承的,當你繼承后,請實現(xiàn)里面對應的成員函數(shù)。

如果你做的是服務端,那么需要實現(xiàn)OnConnect來響應用戶的接入,如果是客戶端,就需要OnConfirm來定義連接上服務器后的操作。其他幾個接口服務端和客戶端是通用的。

所以,看完這里你就會理解msg_server目錄下為什么有DBServConn,FileServConn, LoginServConn, RouteServConn, PushServConn以及MsgConn。

前面幾個都是消息服務器主動向其他幾個服務器發(fā)起的客戶端連接,最后一個是消息服務器自己的服務端Conn,用來等待用戶接入,所以需要實現(xiàn)OnConnect函數(shù)。

而login_server里面的HttpConn和LoginConn含義也顯而易見了,一個是用來響應http請求的,另一個是響應消息服務器login信息登記請求的。其他幾個服務器里的conn也以此類推。

之前對TT感到很凌亂的朋友是不是突然感覺自己頓悟了?感謝我吧。

另外一個疑問,TT的imconn框架是如何把這個CImConn和netlib連接起來的?

這里以DBServConn為例做一個解釋,看代碼

void CDBServConn::Connect(const char* server_ip, uint16_t server_port, uint32_t serv_idx) {log("Connecting to DB Storage Server %s:%d ", server_ip, server_port);m_serv_idx = serv_idx;m_handle = netlib_connect(server_ip, server_port, imconn_callback, (void*)&g_db_server_conn_map);if (m_handle != NETLIB_INVALID_HANDLE) {g_db_server_conn_map.insert(make_pair(m_handle, this));} }



CDBServConn是消息服務器像數(shù)據(jù)庫代理發(fā)起連接時需要繼承的一個CImConn類,里面的Connect函數(shù)是發(fā)起連接時調用的。看里面有調到netlib_connect,并傳入imconn_callback和g_db_server_conn_map。

這兩個參數(shù)就是連接imconn和netlib的關鍵。g_db_server_conn_map是定義在CDBServConn里的一個static全局map映射表,用來保存什么呢?下面一句

g_db_server_conn_map.insert(make_pair(m_handle, this)) 很明顯,這個映射表保存了每次連接的socket句柄(m_handle)和imconn對象(this)的映射關系。

當TT底層的事件分發(fā)器產生事件后,便會調用imconn_callback,里面有一個FindImConn會反查到對應的Conn,然后再調用Conn對象的OnConfirm等函數(shù),這些函數(shù)就是你之前繼承CImConn自己實現(xiàn)的。運行時多態(tài)有木有?是不是覺得TT的框架做的還挺不錯的。conn對象的OnRead其實是最重要的一個函數(shù),因為你的業(yè)務代碼都將在這里面自行實現(xiàn)。

void imconn_callback(void* callback_data, uint8_t msg, uint32_t handle, void* pParam) {NOTUSED_ARG(handle);NOTUSED_ARG(pParam);if (!callback_data)return;ConnMap_t* conn_map = (ConnMap_t*)callback_data;CImConn* pConn = FindImConn(conn_map, handle); //這里將會通過socket句柄反查到對于的imconnif (!pConn)return;//log("msg=%d, handle=%d ", msg, handle);switch (msg){case NETLIB_MSG_CONFIRM:pConn->OnConfirm(); //connect連接成功后會調此pConn的OnConfirm()函數(shù)break;case NETLIB_MSG_READ:pConn->OnRead(); //業(yè)務代碼會在這里面執(zhí)行break;case NETLIB_MSG_WRITE:pConn->OnWrite();break;case NETLIB_MSG_CLOSE:pConn->OnClose();break;default:log("!!!imconn_callback error msg: %d ", msg);break;}pConn->ReleaseRef(); }

看看OnRead代碼,里面有一個HandlePdu

void CImConn::OnRead() {for (;;){uint32_t free_buf_len = m_in_buf.GetAllocSize() - m_in_buf.GetWriteOffset();if (free_buf_len < READ_BUF_SIZE)m_in_buf.Extend(READ_BUF_SIZE);int ret = netlib_recv(m_handle, m_in_buf.GetBuffer() + m_in_buf.GetWriteOffset(), READ_BUF_SIZE);if (ret <= 0)break;m_recv_bytes += ret;m_in_buf.IncWriteOffset(ret);m_last_recv_tick = get_tick_count();}CImPdu* pPdu = NULL;try{while ( ( pPdu = CImPdu::ReadPdu(m_in_buf.GetBuffer(), m_in_buf.GetWriteOffset()) ) ){uint32_t pdu_len = pPdu->GetLength();HandlePdu(pPdu); //這里面將會完成各種業(yè)務代碼m_in_buf.Read(NULL, pdu_len);delete pPdu;pPdu = NULL; // ++g_recv_pkt_cnt;}} catch (CPduException& ex) {log("!!!catch exception, sid=%u, cid=%u, err_code=%u, err_msg=%s, close the connection ",ex.GetServiceId(), ex.GetCommandId(), ex.GetErrorCode(), ex.GetErrorMsg());if (pPdu) {delete pPdu;pPdu = NULL;}OnClose();} }



摘一段CDBServConn的HandlePdu void CDBServConn::HandlePdu(CImPdu* pPdu) {switch (pPdu->GetCommandId()) {case CID_OTHER_HEARTBEAT:break;case CID_OTHER_VALIDATE_RSP:_HandleValidateResponse(pPdu );break;case CID_LOGIN_RES_DEVICETOKEN:_HandleSetDeviceTokenResponse(pPdu);break;case CID_MSG_UNREAD_CNT_RESPONSE:_HandleUnreadMsgCountResponse( pPdu );break;case CID_MSG_LIST_RESPONSE:_HandleGetMsgListResponse(pPdu);break;case CID_MSG_GET_BY_MSG_ID_RES:_HandleGetMsgByIdResponse(pPdu);break;case CID_MSG_DATA:_HandleMsgData(pPdu);break;case CID_MSG_GET_LATEST_MSG_ID_RSP:_HandleGetLatestMsgIDRsp(pPdu);break;



里面的handler就是對應不同協(xié)議的處理器。所以到此,你就會差不多明白,大部分時候,你要做的就是繼承CImConn然后寫handler。

TT的conn框架簡介就到此為止了,其實還有很多細節(jié)需要你自己去摳代碼,慢慢來。

現(xiàn)在回到一開始說的在另一個線程里發(fā)起注冊流程,你應該會很清楚整個過程是怎么做的了,其實就是繼承CImConn,然后在里面發(fā)起連接和接受連接處理。這里摘一段我代碼

net_handle_t CClientConn::Connect(const char* ip, uint16_t port, uint32_t idx) {m_handle = netlib_connect(ip, port, imconn_callback_sp, (void*)&s_client_conn_map);log("connect handle %d", m_handle);if (m_handle != NETLIB_INVALID_HANDLE) {log("in invalid %d", m_handle);s_client_conn_map.insert(make_pair(m_handle, shared_from_this()));//這里!!!}return m_handle; }



注意這里我自己的代碼跟之前給出的TT源碼略有不同,imconn_callback_sp是我改成智能指針的版本,插入conn_map表的不是原始this指針,而是shared_ptr。

這個操作是在子線程里進行的,所以netlib_connect會把imconn_callback_sp加入到底層事件分發(fā)器里進行監(jiān)聽,而事件分發(fā)器是在主線程里運行的一個循環(huán),這個循環(huán)會在socket文件句柄發(fā)生讀寫事件后對你加入的函數(shù)進行回調。所以netlib_connect會里面把imconn_callback加入主線程的監(jiān)聽器,主線程一旦監(jiān)聽到事件發(fā)生就會立刻調用此函數(shù),而此函數(shù)里的

CImConn* pConn = FindImConn(conn_map, handle);

conn_map是在netlib_connect后insert的,所以就有可能出現(xiàn)FindImConn時,conn_map里面還沒有來得及insert這對關系,也就造成了偶爾會發(fā)生connect后沒有繼續(xù)調用后續(xù)的OnConfirm函數(shù),而你跑到服務端看,connect確實成功的奇怪現(xiàn)象。多線程真要命啊。。。

那么如何解決這個問題呢?這個不是本文的要講的,各位有興趣請自行考慮解決的方法,這里友情提示,加鎖是沒有用的。

轉載于:https://my.oschina.net/u/877397/blog/486617

總結

以上是生活随笔為你收集整理的teamtalk的conn框架简介及netlib线程安全问题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內容還不錯,歡迎將生活随笔推薦給好友。