boost网络库开发
一、前言
網(wǎng)絡(luò)庫是從事C++開發(fā)最基礎(chǔ)、最核心、最常用的一個(gè)庫,所有的協(xié)議都是建立在一個(gè)穩(wěn)定高效的網(wǎng)絡(luò)庫之上的,所以對(duì)于c++程序員來說它是必不可少且非常非常重要的一個(gè)核心組件,我們可以使用網(wǎng)絡(luò)庫做任何我們想做的事情,比如
- 用于文件數(shù)據(jù)上傳、下載
- 所有通信協(xié)議如http、rtp、rtsp等協(xié)議的封裝
- 服務(wù)器模塊多客戶端監(jiān)聽、連接、通信
- 客戶端與服務(wù)端通信
- 局域網(wǎng)廣播搜索
- 局域網(wǎng)設(shè)備搜索
- 多播組播
一直不停的在C++、Java、Web以及l(fā)inux系統(tǒng)運(yùn)維等技術(shù)方面不停的來回切換,突然發(fā)現(xiàn)很久沒有做c++了,最近一直在做java的要多,關(guān)于c++這塊從事有8-9年的開發(fā),這塊相關(guān)的文章總結(jié)的卻比Java要少很多。
- 一個(gè)是c++的技術(shù)難度和門檻要高很多,一直都在不斷積累,可概要的技術(shù)點(diǎn)不多,抽不出時(shí)間進(jìn)行文章化。
- 一個(gè)是Java相關(guān)的技術(shù)容易的多,可寫的內(nèi)容也多,研究一門基本上都有對(duì)應(yīng)的doc文檔,所以將文檔在翻譯一遍成網(wǎng)絡(luò)文章就相對(duì)容易很多
不管如何,因?yàn)楣ぷ髟?#xff0c;最近又杠上了C++的音視頻這塊相關(guān)技術(shù),正好抽空將之前所有的c++資料和庫總結(jié)了一下:
不總結(jié)還好,一旦總結(jié),還是很多的,很多實(shí)際項(xiàng)目中開發(fā)的庫和組件還沒提取出來,不過對(duì)于一個(gè)老C++程序員來說,項(xiàng)目中開發(fā)好的穩(wěn)定的庫和資料是彌足珍貴的,要多善于總結(jié)和滾雪球。
在開始講解boost網(wǎng)絡(luò)庫之前,我這里先給大家稍微普及一下windows下的網(wǎng)絡(luò)知識(shí),我是windows下開發(fā)的,關(guān)于socket編程在windows下有多重開發(fā)模型,可以參考一下《winsock網(wǎng)絡(luò)編程》 一書
- 選擇模型(select)
選擇模型是網(wǎng)絡(luò)編程中最基礎(chǔ)、最簡單的一種模型,因?yàn)槠浜唵我子眯运詡涫軐W(xué)生時(shí)代的在校生或畢業(yè)生使用,但是它也是性能最差的一種編程模型。選擇模型是針對(duì)select而言的,它可以阻塞可以是非阻塞的,所以它包括兩種模式:
(1)、 阻塞模式
執(zhí)行I/O操作完成前會(huì)一直進(jìn)行等待,不會(huì)將控制權(quán)交給程序
(2)、 非阻塞模式
執(zhí)行I/O操作時(shí),WinSock函數(shù)會(huì)返回并交出控制權(quán)。因?yàn)楹瘮?shù)在沒有運(yùn)行完成就進(jìn)行返回,并會(huì)不斷地返回 WSAEWOULDBLOCK錯(cuò)誤,但是它功能很強(qiáng)大。 - 異步消息選擇
異步消息模型就是借助windows的消息機(jī)制來實(shí)現(xiàn)的,熟悉win32應(yīng)用開發(fā)或者mfc應(yīng)用開發(fā)的人員都知道,所有的窗口都有對(duì)應(yīng)的消息處理機(jī)制,我們只需要?jiǎng)?chuàng)建一個(gè)窗口句柄HWND,然后將HWND與對(duì)應(yīng)的消息進(jìn)行綁定即可,它是通過WsaAsyncSelect接口函數(shù)來實(shí)現(xiàn)的,它實(shí)現(xiàn)的主要思路是:
(1)、首先我們定義一個(gè)消息,告訴系統(tǒng)當(dāng)有客戶端消息到達(dá)的時(shí)候,發(fā)送該消息通知我們
(2)、然后在消息處理函數(shù)里面添加對(duì)消息的處理即可 - 事件模型
除此之外,winsock還提供了一個(gè)異步I/O模型-事件模型–WsaEventSelect ,和WSAAsyncSelect模型類似的是,它也允許應(yīng)用程序在一個(gè)或多個(gè)套接字上,接收以事件為基礎(chǔ)的網(wǎng)絡(luò)事件通知。該模型最主要的差別在于網(wǎng)絡(luò)事件會(huì)投遞至一個(gè)事件對(duì)象句柄,而非投遞至一個(gè)窗口例程。
事件選擇模型不用主動(dòng)去輪詢所有客戶端套接字是否有數(shù)據(jù)到來的模型,它也是在客戶端有數(shù)據(jù)到來時(shí),系統(tǒng)發(fā)送通知給我們的程序,但是,它不是發(fā)送消息,而是通過事件的方式來通知我們的程序,這就解決了WsaAsyncSelect模型只能用在Win32窗口程序的問題。
- 重疊I/O模型
重疊模型是讓應(yīng)用程序使用重疊數(shù)據(jù)結(jié)構(gòu)(WSAOVERLAPPED),一次投遞一個(gè)或多個(gè) WinSock I/O請(qǐng)求。針對(duì)這些請(qǐng)求,在它們完成后,應(yīng)用程序會(huì)受到通知,于是就可以通過另外的代碼來處理這些數(shù)據(jù)了。有兩種方法可以用來管理重疊I/O請(qǐng)求完成的情況 – 即接到重疊操作完成的通知時(shí)處理
(1) 事件對(duì)象通知 (Event Object Notification)
基于事件通知的方法,就是要將WinSock事件對(duì)象與WSAOVERLPPED結(jié)構(gòu)關(guān)聯(lián)在一起,在使用重疊結(jié)構(gòu)的情況下,我們常用的 send,sendto,recv,recvfrom 也要被WSASend,WSASendto,WSARecv,WSARecvfrom 替換掉了。
(2) 完成例程(Completion Routies)
完成例程來實(shí)現(xiàn)重疊I/O比用事件通知簡單得多,在這個(gè)模型中,主線程只用不停的接受連接即可
- 完成端口
完成端口對(duì)象取代了WSAAsyncSelect中的消息驅(qū)動(dòng)和WSAEventSelect中的事件對(duì)象,當(dāng)然完成端口模型的內(nèi)部機(jī)制要比WSAAsyncSelect和WSAEventSelect模型復(fù)雜得多,但是是性能最佳的網(wǎng)絡(luò)編程模型。從本質(zhì)上說,完成端口模型要求我們創(chuàng)建一個(gè)Win32完成端口對(duì)象,通過指定數(shù)量的線程,對(duì)重疊I/O請(qǐng)求進(jìn)行管理,以便為已經(jīng)完成的重疊I/O請(qǐng)求提供服務(wù)。
假若一個(gè)應(yīng)用程序同時(shí)需要管理為數(shù)眾多的套接字,那么采用這種模型,往往可以達(dá)到最佳的系統(tǒng)性能!
boost庫的網(wǎng)絡(luò)模型實(shí)現(xiàn)在windows上底層是通過iocp完成端口模型實(shí)現(xiàn)的,所以是性能最佳的一種網(wǎng)絡(luò)模型實(shí)現(xiàn)。
二、實(shí)現(xiàn)
boost庫中有一個(gè)專門的用于網(wǎng)絡(luò)編程的庫-asio,也就是異步io,它能實(shí)現(xiàn)tcp、udp、甚至usb串口數(shù)據(jù)讀取的功能,它是一個(gè)非常強(qiáng)大的、跨平臺(tái)的異步網(wǎng)絡(luò)通信庫,這就是我為什么選擇它的原因。
在介紹源碼實(shí)現(xiàn)的時(shí)候,我們先了解一下asio中的幾個(gè)常用對(duì)象,和socket一樣,它包含如下幾個(gè)對(duì)象
- io_service
它主要作為一個(gè)事件驅(qū)動(dòng)器,在多線程編程里面提供了任務(wù)隊(duì)列和任務(wù)分發(fā)功能 - acceptor
它和socket一樣,提供了端點(diǎn)連接監(jiān)聽相關(guān)能力 - endpoint和address
address封裝了設(shè)備ipv4和ipv6的地址,endpoint標(biāo)識(shí)address和port的組合,決定一臺(tái)機(jī)器的某個(gè)端口(我們稱之為端點(diǎn))。 - socket
套接口編程模型的核心類,提供了同步和異步操作接口集合
說明了以上幾個(gè)核心概念之后,我需要通過boost網(wǎng)絡(luò)庫封裝實(shí)現(xiàn)如下幾個(gè)接口的能力,包括tcp、udp、廣播、數(shù)據(jù)收發(fā)、同步異步等,各個(gè)接口以及說明如下:
// --------------------------------------------------------------------------------------- // Function: // Init network SDK // Parameters: // [in]pConnectCallBack : connect or disconnect callback // [in]pDataCallBack : receive data callback // [in]pErrCallBack : error message callback // [in]pContext : callback context // Remark: // This interface must be invoked at the beginning of application, when connect server // success or disconnect server pConnectCallBack callback will be invoked; when data // arrived, pDataCallBack callback will be invoked ; when error occur at the process // of running, pErrCallBack callback will be invoked; // pContext parameter express the scenes of the callback, if the callback invoked by // SDK, pContext will be passed to you by callback last parameter // Return: // if error occur, the result value as the top description will be return by SDK // --------------------------------------------------------------------------------------- NETSDK_API NETSDK_RETURN NetSdk_Init();// --------------------------------------------------------------------------------------- // Function: // Clean and release SDK resource // Parameters: // NULL // Remark: // This interface must be invoked at the end of application // Return: // return value returned by SDK As mentioned above // --------------------------------------------------------------------------------------- NETSDK_API NETSDK_RETURN NetSdk_Cleanup();// tcp server // --------------------------------------------------------------------------------------- // Function: // Start or stop listen local port // Parameters: // [in]nPort : listen port // [out]pHandle : server handle address // [lHandle] : server handle output by NetSdk_Listen // Remark: // This interface is adapted to server, not for client // Example: // PCONNECTCALLBACK // NetSdk_Listen--> PRECVDATACALLBACK -->NetSdk_UnListen // PNETSDKERRCALLBACK // || // \/ // NetSdk_Send // Return: // return value returned by SDK As mentioned above // --------------------------------------------------------------------------------------- NETSDK_API NETSDK_RETURN NetSdk_Listen(int nPort, PCONNECTCALLBACK pConnectCallBack,PRECVDATACALLBACK pDataCallBack, PNETSDKERRCALLBACK pErrorBack,void* pContext, long* pHandle); NETSDK_API NETSDK_RETURN NetSdk_ListenEx(const char* ipV4OrV6, int nPort, PCONNECTCALLBACK pConnectCallBack,PRECVDATACALLBACK pDataCallBack,PNETSDKERRCALLBACK pErrorBack,void* pContext, long* pHandle); NETSDK_API NETSDK_RETURN NetSdk_UnListen(long lHandle);// tcp client // --------------------------------------------------------------------------------------- // Function: // Connect or disconnect server // Parameters: // [in]pServerIp : server ip address // [in]nPort : server port // [out]pHandle : client handle address // [in]lHandle : client handle // [in]nTimeoutSec : timeout // [in]bindLocalPort : tcp client bind local port // Remark: // This interface is adapted to tcp client, not for server // if set bindLocalPort to none zero, then client bind local port by manual // Return: // return value returned by SDK As mentioned above // --------------------------------------------------------------------------------------- NETSDK_API NETSDK_RETURN NetSdk_Connect(const char* pServerIp, int nPort, int nTimeoutSec,PCONNECTCALLBACK pConnectCallBack, PRECVDATACALLBACK pDataCallBack, void* pContext, long* pHandle, int bindLocalPort = 0);// tcp client // --------------------------------------------------------------------------------------- // Function: // Send message to server or reply message to client // Parameters: // [in]nClientId : client handle // [in]pBytes : send data address // [in]nLen : send data length // Remark: // This interface is adapted to tcp client, not for server // Example: // NetSdk_Connect-->NetSdk_Send-->NetSdk_DisConnect // Return: // return value returned by SDK As mentioned above // --------------------------------------------------------------------------------------- NETSDK_API NETSDK_RETURN NetSdk_Send(long nClientId, unsigned char* pBytes, int nLen); // return byte have send, if error occur then return 0 or less than 0 NETSDK_API int NetSdk_SendSync(long nClientId, unsigned char* pBytes, int nLen);// tcp client // --------------------------------------------------------------------------------------- // Function: // Get TCP Client local address and peer address // Parameters: // [in]nClientId : client handle // [in]pBytes : send data address // [in]nLen : send data length // Remark: // This interface is adapted to tcp client // Example: // NetSdk_Connect-->NetSdk_GetConnectAddr // NetSdk_Listen-->PCONNECTCALLBACK-->NetSdk_GetConnectAddr // Return: // return value returned by SDK As mentioned above // --------------------------------------------------------------------------------------- NETSDK_API NETSDK_RETURN NetSdk_GetConnectAddr(long nClientId, sockaddr_in* pLocalAddr, sockaddr_in* pPeerAddr);// udp client // --------------------------------------------------------------------------------------- // Function: // Bind local port and send udp data to another device // Parameters: // NULL // Remark: // This interface is adapted to udp client, not for server // NetSdk_Broadcast_Sync and NetSdk_SendTo_Sync is synchronized interface // the value returned is indicate the size has send // NetSdk_Broadcast and NetSdk_SendTo is asynchronized interface // Example: // NetSdk_Bind-->NetSdk_Broadcast // or // NetSdk_Bind-->NetSdk_SendTo // Return: // return value returned by SDK As mentioned above // --------------------------------------------------------------------------------------- NETSDK_API NETSDK_RETURN NetSdk_Bind(const char* pServerIp, int nPort, PRECVDATACALLBACK pDataCallBack, void* pContext,long* pHandle);// --------------------------------------------------------------------------------------- // Function: // Send Udp broadcast message to local network // Parameters: // [in]nClientId : returned by NetSdk_Bind // [in]nPort : which port to all machine // [in]pBytes : the message body // [in]nLen : the message length // Remark: // this interface is used to send broadcast to all // machine in local network // Example: // NetSdk_Bind-->NetSdk_Broadcast // or // NetSdk_Bind-->NetSdk_SendTo // Return: // return value returned by SDK As mentioned above // --------------------------------------------------------------------------------------- NETSDK_API NETSDK_RETURN NetSdk_Broadcast(long nClientId,int nPort,unsigned char* pBytes, int nLen); // --------------------------------------------------------------------------------------- // Function: // Send Udp message to specific machine // Parameters: // [in]nClientId : returned by NetSdk_Bind // [in]pAddr : the endpoint to received message // [in]pBytes : the message body // [in]nLen : the message length // Remark: // NULL // Example: // NetSdk_Bind-->NetSdk_SendTo // Return: // return value returned by SDK As mentioned above // --------------------------------------------------------------------------------------- NETSDK_API NETSDK_RETURN NetSdk_SendTo(long nClientId,sockaddr_in* pAddr,unsigned char* pBytes, int nLen);NETSDK_API int NetSdk_Broadcast_Sync(long nClientId,int nPort,unsigned char* pBytes, int nLen); NETSDK_API int NetSdk_SendTo_Sync(long nClientId,sockaddr_in* pAddr,unsigned char* pBytes, int nLen);// tcp or udp client // --------------------------------------------------------------------------------------- // Function: // Close client handle // Parameters: // NULL // Remark: // // Example: // NetSdk_Bind-->NetSdk_CloseHandle // or // NetSdk_Connect-->NetSdk_CloseHandle // Return: // return value returned by SDK As mentioned above // --------------------------------------------------------------------------------------- NETSDK_API NETSDK_RETURN NetSdk_CloseHandle(long lHandle);實(shí)現(xiàn)總的類圖如下所示
各個(gè)類的作用如下:
- CClient
提供了作為tcp客戶端和udp客戶端的基礎(chǔ)實(shí)現(xiàn)類,包括獲取客戶id、獲取地址、端口、日志等接口封裝 - CUdpClient
實(shí)現(xiàn)了udp本地端口綁定、數(shù)據(jù)同步異步發(fā)送、數(shù)據(jù)接收、廣播等接口 - CTcpClient
實(shí)現(xiàn)了tcp客戶端連接、斷開、發(fā)送數(shù)據(jù)、處理數(shù)據(jù)等接口 - CNetWork
服務(wù)端網(wǎng)絡(luò)的抽象,服務(wù)端可以同時(shí)監(jiān)聽多個(gè)網(wǎng)卡的多個(gè)網(wǎng)口,它包括啟動(dòng)、停止等接口 - CTcpServer
是tcp網(wǎng)絡(luò)的實(shí)現(xiàn),服務(wù)端可以同時(shí)監(jiān)聽多個(gè)tcp端點(diǎn),實(shí)現(xiàn)多端口通信。 - CNetWorkMgr
網(wǎng)絡(luò)管理器,管理多個(gè)網(wǎng)絡(luò)對(duì)象,包括添加網(wǎng)絡(luò)、移除網(wǎng)絡(luò)、清理等接口
客戶端實(shí)現(xiàn)
作為一個(gè)客戶端,我想要的主要功能是:連接服務(wù)器(tcp)、綁定本機(jī)端口(udp接收數(shù)據(jù))、發(fā)送數(shù)據(jù)(同步異步)、接收數(shù)據(jù)等功能。
udp客戶端綁定本機(jī)端口:
bool CUdpClient::Bind(udp::endpoint ep) {try{m_bindPort = ep;boost::system::error_code err;if (!m_socket.is_open()){m_socket.open(/*ip::udp::v4()*/ep.protocol(), err);if (err){std::string strErr = (boost::format("open socket error[%d]")%err.value()).str();CCallBack::NotifyErrCB(m_eType, GetSId(), GetId(), NETERR_INISOCK_ERR,(unsigned char*)strErr.c_str() , strErr.length());return false;}}m_socket.set_option(asio::socket_base::reuse_address(true),err);if (err){std::string strErr = (boost::format("reuse address error[%d]")%err.value()).str();CCallBack::NotifyErrCB(m_eType, GetSId(), GetId(), NETERR_INISOCK_ERR,(unsigned char*)strErr.c_str() , strErr.length());return false;} #ifdef _WIN32BOOL bNewBehavior = FALSE;DWORD dwBytesReturned = 0;WSAIoctl(m_socket.native_handle(), SIO_UDP_CONNRESET, &bNewBehavior, sizeof bNewBehavior, NULL, 0, &dwBytesReturned, NULL, NULL); #endifm_socket.bind(ep, err);if (err){std::string strErr = (boost::format("bind socket error[%d]")%err.value()).str();CCallBack::NotifyErrCB(m_eType, GetSId(), GetId(), NETERR_INISOCK_ERR,(unsigned char*)strErr.c_str() , strErr.length());return false;}return true;}catch (std::exception& e){CCallBack::NotifyErrCB(m_eType, GetSId(), GetId(), NETERR_UNKNOWN, (unsigned char*)e.what(), strlen(e.what()));}return false; }udp數(shù)據(jù)異步發(fā)送
NETSDK_RETURN CUdpClient::AsynSendTo(udp::endpoint ep, unsigned char* pBytes, int nLen) {try{// 獲取空bufferBufferPtr pBuffer = m_write_buffer.GetEmptyBuffer();if (!pBuffer)return NETERR_BUFERR_FULL;// 填充數(shù)據(jù)pBuffer->FillData(pBytes, nLen);pBuffer->m_ep = ep;m_write_buffer.AddFullBuffer(pBuffer);// 數(shù)據(jù)是否發(fā)送完畢boost::mutex::scoped_lock a_lock(m_send_lock);if (m_send_finish){// 獲取當(dāng)前發(fā)送bufferBufferPtr pBuffer = m_write_buffer.GetFullBuffer();// 無可發(fā)送的bufferif (!pBuffer)return NETERR_UNKNOWN;m_send_finish = false;AsyncSend(pBuffer);}return NETERR_SUCCESS;}catch (std::exception& e){CCallBack::NotifyErrCB(m_eType, GetSId(), GetId(), NETERR_UNKNOWN, (unsigned char*)e.what(), strlen(e.what()));}return NETERR_UNKNOWN; }tcp客戶端連接
bool CTcpClient::Connect(std::string strIp, int nPort, bool bSync, int nTimeout) {try{tcp::endpoint ep(ip::address::from_string(strIp), nPort);if (bSync){m_bconnected = false;boost::system::error_code err_code;if (m_bindLocalPort != 0) {m_socket.open(/*tcp::v4()*/ep.protocol(), err_code);m_socket.set_option(tcp::acceptor::reuse_address(true), err_code);tcp::endpoint bind_ep(ip::address::from_string("0.0.0.0"), m_bindLocalPort);m_socket.bind(bind_ep, err_code);}// 非阻塞模式連接,方式默認(rèn)等待20秒 // { // m_socket.io_control(boost::asio::ip::tcp::socket::non_blocking_io(true)); // //err_code = m_socket.connect(ep, err_code); // m_socket.connect(ep, err_code); // // fd_set fdWrite; // FD_ZERO(&fdWrite); // FD_SET(m_socket.native(), &fdWrite); // timeval tv = { nTimeout }; // if (select(0, NULL, &fdWrite, NULL, &tv) <= 0 || !FD_ISSET(m_socket.native(), &fdWrite)) // { // m_bconnected = false; // return m_bconnected; // } // // m_socket.io_control(boost::asio::ip::tcp::socket::non_blocking_io(false)); // m_bconnected = true; // StartKeepAlive(); // }// err_code = m_socket.connect(ep, err_code); // if (!err_code) // { // m_bconnected = true; // StartKeepAlive(); // return true; // } // return m_bconnected;boost::recursive_mutex::scoped_lock guard(m_connect_mutext);m_socket.async_connect(ep, boost::bind(&CTcpClient::ConnectHandler, shared_from_this(), boost::asio::placeholders::error));m_connect_cond.wait_for(guard, boost::chrono::seconds(nTimeout));return m_bconnected;}else{m_socket.async_connect(ep, boost::bind(&CTcpClient::ConnectHandler, shared_from_this(), boost::asio::placeholders::error));return true;}}catch (std::exception& e){CCallBack::NotifyErrCB(m_eType, GetSId(), GetId(), NETERR_UNKNOWN, (unsigned char*)e.what(), strlen(e.what()));}return false; }tcp數(shù)據(jù)處理
NETSDK_RETURN CTcpClient::AsynWrite(unsigned char* pBytes, int nLen) {try{if(!m_bconnected)return NETERR_SEND_ERR;// 獲取空bufferBufferPtr pBuffer = m_write_buffer.GetEmptyBuffer(nLen);if (!pBuffer)return NETERR_BUFERR_FULL;// 填充數(shù)據(jù)pBuffer->FillData(pBytes, nLen);m_write_buffer.AddFullBuffer(pBuffer);// 數(shù)據(jù)是否發(fā)送完畢boost::mutex::scoped_lock a_lock(m_send_lock);if (m_send_finish){// 獲取當(dāng)前發(fā)送bufferBufferPtr pNextBuffer = m_write_buffer.GetFullBuffer();// 無可發(fā)送的bufferif (!pNextBuffer)return NETERR_UNKNOWN;m_send_finish = false;AsyncSend(pNextBuffer);}return NETERR_SUCCESS;}catch (std::exception& e){CCallBack::NotifyErrCB(m_eType, GetSId(), GetId(), NETERR_UNKNOWN, (unsigned char*)e.what(), strlen(e.what()));}return NETERR_UNKNOWN; }稍微注意的是,tcp數(shù)據(jù)發(fā)送的時(shí)候,我這里用了一個(gè)環(huán)形緩沖區(qū),數(shù)據(jù)發(fā)送首先進(jìn)入環(huán)形緩沖器,發(fā)送完成后繼續(xù)從環(huán)形緩沖區(qū)中獲取數(shù)據(jù)并發(fā)送出去。
tcp數(shù)據(jù)讀取
void CTcpClient::ReadHandler(const boost::system::error_code err, const size_t nTransferedSize) {sockaddr_in stAddr;try{ stAddr.sin_family = AF_INET;stAddr.sin_addr.s_addr = inet_addr(GetAddr().c_str());stAddr.sin_port = htons(GetPort());if (!err && nTransferedSize > 0){CCallBack::NotifyDataCB(m_eType, GetSId(), GetId(),&stAddr,m_read_buffer->m_pBuffer, nTransferedSize);AsynRead();}else if(err){std::string strErr = (boost::format("read tcp data error[%d]\n")%err.value()).str();CCallBack::NotifyErrCB(m_eType, GetSId(), GetId(), NETERR_RECV_ERR, (unsigned char*)strErr.c_str(), strErr.length());// 其他異常不認(rèn)為是斷開if (WSAECONNRESET == err.value() || WSAECONNABORTED == err.value() ||WSAENETRESET == err.value() || WSAESHUTDOWN == err.value() || WSAENETDOWN == err.value() || WSAEHOSTDOWN == err.value()||ERROR_SEM_TIMEOUT == err.value() || ERROR_FILE_NOT_FOUND == err.value()){m_bconnected = false;if(CClientMgr::get_mutable_instance().GetTcpClient(GetId())){CClientMgr::get_mutable_instance().PopTcpClient(GetId());CCallBack::NotifyConnectCB(m_eType, GetSId(), GetId(), &stAddr, true);}}else{std::string strErr = (boost::format("ReadHandle error[%d-%s]\n")%err.value()%err.message()).str();WriteLog(strErr);}}else{std::string strErr = (boost::format("ReadHandle error[%d-%s]\n")%err.value()%err.message()).str();WriteLog(strErr);}}catch (std::exception& e){std::string strErr = (boost::format("read tcp[%d] data exception[%s]\n")%GetId()%e.what()).str();CCallBack::NotifyErrCB(m_eType, GetSId(), GetId(), NETERR_RECV_ERR, (unsigned char*)strErr.c_str(), strErr.length());WriteLog(strErr);} }當(dāng)有數(shù)據(jù)回調(diào)的時(shí)候,我們首先檢查是否有錯(cuò)誤,如網(wǎng)絡(luò)斷開、對(duì)方重置連接、關(guān)閉、超時(shí)等,如果沒有錯(cuò)誤則回調(diào)出去數(shù)據(jù),如果異常則回調(diào)斷開異常!
服務(wù)端實(shí)現(xiàn)
服務(wù)端實(shí)現(xiàn),最重要的就是支持多個(gè)端點(diǎn)的監(jiān)聽,支持多個(gè)客戶端的連接,以下一tcp服務(wù)為例進(jìn)行說明
tcp服務(wù)端連接監(jiān)聽實(shí)現(xiàn)
bool CTcpServer::Listen() {try{boost::system::error_code err;if (!m_acceptor.is_open()){m_acceptor.open(m_endPoint.protocol(),err);if (err){Stop();return false;}m_acceptor.set_option(tcp::acceptor::reuse_address(true),err);if (err){Stop();return false;}m_acceptor.bind(m_endPoint,err);if (err){Stop();return false;}m_acceptor.listen(socket_base::max_connections,err);if (err){Stop();return false;}}TcpClientPtr client(new CTcpClient(m_io_server,GetId(),true));client->RegisterConnectCallBack(GetConnectCallBack(), GetConnectCallBackContext());client->RegisterReceiveDataCallBack(GetReceiveDataCallBack(), GetReceiveDataCallBackContext());m_acceptor.async_accept(client->GetSocket(),bind(&CTcpServer::AcceptClient,shared_from_this(),boost::asio::placeholders::error,client));}catch (std::exception){return false;}return true; }當(dāng)客戶端連接到服務(wù)端之后,會(huì)調(diào)用異步回調(diào)
void CTcpServer::AcceptClient(boost::system::error_code err, TcpClientPtr client) {if (err){return ;}// client connect{sockaddr_in stAddr;std::memset(&stAddr, 0, sizeof(stAddr));stAddr.sin_family = AF_INET;stAddr.sin_addr.s_addr = inet_addr(client->GetAddr().c_str());stAddr.sin_port = ::ntohs(client->GetPort());CCallBack::NotifyConnectCB(client_type_tcp, GetId(), client->GetId(), &stAddr, false);}CClientMgr::get_mutable_instance().PushTcpClient(client->GetId(), client);client->StartKeepAlive();client->AsynRead();Listen(); }客戶端連接之后將由CClientMgr客戶端管理對(duì)象進(jìn)行管理。
三、測(cè)試
核心的代碼我在上面已經(jīng)列舉出來,其他的枝節(jié)大家可以自行補(bǔ)腦或搜索實(shí)現(xiàn),下面我們使用tcp-udp測(cè)試工具進(jìn)行測(cè)試或使用我寫的自帶測(cè)試工具測(cè)試,首先來了解一下netsdk網(wǎng)絡(luò)的使用,我的頭文件中有關(guān)于該網(wǎng)絡(luò)的說明:
// ------------------------------------------------------------------------------------------------ // File: // netsdk.h // Usage: // net library for tcp client or udp client or tcp server // Remark: // usage for this library discribed as follow // Author: // lixiangxiang from founder // History: // 2015/10/1 v1.0 // Contact: // lixiang6153@126.com csdn name:lixiang987654321 // Copyright: // lixiang6153@126.com // ------------------------------------------------------------------------------------------------//------------------------------------------------------------------------------------------------- // >>>>>>>>>>>>>>>>>>>>>>>usage for this library<<<<<<<<<<<<<<<< // ********************************tcp server************************************************* // NetSdk_Init → PNETSDKERRCALLBACK → NetSdk_Cleanup // ↓ // ↓nClientId (client connected,bExit is false,save client id) // ↓ // PRECVDATACALLBACK(data from client) // ↓ // ↓ // NetSdk_Send (response data toclient) // ↓ // ↓maybe occur // PCONNECTCALLBACK(client disconnect, bExit is true) // // ********************************tcp client************************************************* // NetSdk_Init → NetSdk_Connect → NetSdk_CloseHandle → NetSdk_Cleanup // ↓ // ↓ // PRECVDATACALLBACK(data from server) // ↓ // ↓maybe occur // PCONNECTCALLBACK(server disconnect, bExit is true) // // ********************************udp client************************************************* // NetSdk_Init → NetSdk_Bind NetSdk_CloseHandle→ NetSdk_Cleanup // ↓ // ↓ // NetSdk_SendTo(send data to endpoint) // // ********************************udp broadcast********************************************** // NetSdk_Init → NetSdk_Bind NetSdk_CloseHandle→ NetSdk_Cleanup // ↓ ↓ // ↓ ↓ // (send broadcast to everyone) (receive data from endpoint) // NetSdk_Broadcast PRECVDATACALLBACK // //-------------------------------------------------------------------------------------------------服務(wù)端netsdk使用流程
我這里稍作說明,如果是作為服務(wù)器端我們的使用方式如下:
NetSdk_Init初始化網(wǎng)絡(luò)庫=>NetSdk_Listen開始監(jiān)聽本機(jī)端口=> 接收連接=>處理數(shù)據(jù)=> NetSdk_UnListen停止端口監(jiān)聽=>NetSdk_Cleanup清理網(wǎng)絡(luò)庫資源我們可以同時(shí)監(jiān)聽多個(gè)網(wǎng)卡的多個(gè)端口,如果需要服務(wù)端反饋數(shù)據(jù)給客戶端,可以通過連接返回的clientId,使用客戶端相關(guān)接口進(jìn)行回復(fù)和反饋。
客戶端netsdk使用流程
作為客戶端,如果是tcp客戶端,使用接口流程如下:
NetSdk_Init初始化網(wǎng)絡(luò)庫=>NetSdk_Connect連接服務(wù)器=>處理數(shù)據(jù)=>NetSdk_Send發(fā)送數(shù)據(jù)到服務(wù)端=> NetSdk_CloseHandle關(guān)閉與服務(wù)端連接=>NetSdk_Cleanup清理網(wǎng)絡(luò)庫資源作為tcp客戶端,數(shù)據(jù)發(fā)送也可以支持同步和異步發(fā)送,默認(rèn)是異步,同步發(fā)送接口NetSdk_SendSync
作為客戶端,如果是udp客戶端,使用接口流程如下:
NetSdk_Init初始化網(wǎng)絡(luò)庫=>NetSdk_Bind綁定本機(jī)端口=>處理數(shù)據(jù)=>NetSdk_SendTo發(fā)送數(shù)據(jù)報(bào)文到對(duì)應(yīng)機(jī)器=> NetSdk_CloseHandle關(guān)閉與服務(wù)端連接=>NetSdk_Cleanup清理網(wǎng)絡(luò)庫資源作為udp客戶端,數(shù)據(jù)發(fā)送也支持同步發(fā)送,同步發(fā)送接口:NetSdk_SendTo_Sync;除此之外udp還支持廣播,廣播接口也支持同步也異步發(fā)送,接口如下:
NetSdk_Broadcast NetSdk_Broadcast_Sync我的測(cè)試工具如下所示:
包括了服務(wù)端和客戶端,可以將該工具放在2臺(tái)機(jī)器,一臺(tái)做服務(wù)端,一臺(tái)做客戶端,如果沒有多余機(jī)器,可以使用一個(gè)工具進(jìn)行測(cè)試既作為服務(wù)端,也作為客戶端,客戶端使用同一個(gè)ip和端口。
作為服務(wù)端進(jìn)行測(cè)試
服務(wù)端監(jiān)聽和斷開監(jiān)聽代碼
void CNetsdkDemonDlg::OnBnClickedButtonListen() {UpdateData(TRUE);if (m_listenHandle < 0) {if (NETERR_SUCCESS != NetSdk_Listen(m_local_port, Connect_Callback,Receive_Data_Callback,Error_Callback,this, &m_listenHandle)) {AfxMessageBox("監(jiān)聽本機(jī)端口失敗!");return;}GetDlgItem(IDC_BUTTON_LISTEN)->SetWindowText("停止監(jiān)聽");AddLog("服務(wù)端:開始監(jiān)聽本機(jī)端口:%d", m_local_port);EnableServerButton(FALSE);}else {CloseServer();GetDlgItem(IDC_BUTTON_LISTEN)->SetWindowText("開始監(jiān)聽");AddLog("服務(wù)端:停止監(jiān)聽本機(jī)端口");EnableServerButton(TRUE);} } void CNetsdkDemonDlg::CloseServer() {if (m_listenHandle > 0){NetSdk_UnListen(m_listenHandle);m_listenHandle = -1;} }如本機(jī)默認(rèn)監(jiān)聽的端口為1234,啟動(dòng)demon后點(diǎn)擊監(jiān)聽(本地ip使用默認(rèn)的127.0.0.1監(jiān)聽所有網(wǎng)卡)
監(jiān)聽成功后效果
監(jiān)聽成功后可以使用tcp-dup測(cè)試工具測(cè)試連接本機(jī)的1234端口了
可以看到使用tcp-udp測(cè)試工具連接上了我的服務(wù)端,注意我的本機(jī)真實(shí)地址192.168.50.7,服務(wù)端監(jiān)聽的是所有網(wǎng)卡的1234端口!
然后我們發(fā)送數(shù)據(jù)試試看!
可以看到服務(wù)端收到了客戶端id為3的客戶發(fā)送到服務(wù)端的數(shù)據(jù)!
作為客戶端測(cè)試
為了避免干擾,我們使用tcp-udp工具作為服務(wù)端,我的測(cè)試工具作為客戶端連接到服務(wù)端(當(dāng)然客戶端和服務(wù)端完全可以用我的工具)測(cè)試如下:
可以看到客戶端連接到了服務(wù)端,下面我們開始發(fā)送數(shù)據(jù):
總結(jié)
我給的demon僅僅調(diào)用了tcp服務(wù)端和tcp客戶端相關(guān)接口,當(dāng)然還包括udp相關(guān)接口,這里可以自己測(cè)試調(diào)用,我想說明的是本網(wǎng)絡(luò)庫是經(jīng)過了大量數(shù)據(jù)測(cè)試和多個(gè)實(shí)戰(zhàn)項(xiàng)目測(cè)試的一個(gè)穩(wěn)定高效的網(wǎng)絡(luò)庫組件(120路音視頻主碼流同時(shí)發(fā)送接收),只要有了網(wǎng)絡(luò)庫組件,你再也不在為項(xiàng)目之間的通信而擔(dān)憂,只需關(guān)注業(yè)務(wù)邏輯處理即可。
當(dāng)然,網(wǎng)絡(luò)庫進(jìn)行是封裝了tcp和udp等相關(guān)通信,協(xié)議層(私有協(xié)議或http、rtsp等與業(yè)務(wù)相關(guān)的協(xié)議)設(shè)計(jì)還是需要你個(gè)人去設(shè)計(jì)的,這個(gè)與項(xiàng)目的具體業(yè)務(wù)息息相關(guān),與底層的通信無關(guān)(不關(guān)心傳輸?shù)氖鞘裁磾?shù)據(jù))。
另外,本網(wǎng)絡(luò)庫是經(jīng)過了大量實(shí)戰(zhàn)和不斷更新和修改的穩(wěn)定高效的網(wǎng)絡(luò)庫,花費(fèi)了較多的心思,所以如果需要本項(xiàng)目的源碼,可以與本人直接溝通購買意向,價(jià)格在500-1000不等(自己考量一下應(yīng)該是很值得了,買一個(gè)視頻教程都已經(jīng)到這個(gè)數(shù)了),該庫是一個(gè)boost封裝了跨平臺(tái)的庫(linux只需要稍作改動(dòng),請(qǐng)自行修改,本人不會(huì)幫忙修改),代碼風(fēng)格絕對(duì)是一個(gè)c++老手寫的代碼,閱讀性較強(qiáng),新手上手較快,很容易修改得linux或定制自己接口或添加其他模塊或功能,代碼一瞥:
源碼獲取、合作、技術(shù)交流請(qǐng)獲取如下聯(lián)系方式:
QQ交流群:961179337
微信賬號(hào):lixiang6153
公眾號(hào):IT技術(shù)快餐
電子郵箱:lixx2048@163.com
總結(jié)
以上是生活随笔為你收集整理的boost网络库开发的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 免费搭建属于自己的域名个性邮箱
- 下一篇: Excel四舍六入五单双公式