boost網絡庫開發

1、前言

網絡庫是從事C++開發最基礎、最核心、最經常使用的一個庫,全部的協議都是創建在一個穩定高效的網絡庫之上的,因此對於c++程序員來講它是必不可少且很是很是重要的一個核心組件,咱們可使用網絡庫作任何咱們想作的事情,好比   java

  • 用於文件數據上傳、下載  
  • 全部通訊協議如http、rtp、rtsp等協議的封裝  
  • 服務器模塊多客戶端監聽、鏈接、通訊
  • 客戶端與服務端通訊  
  • 局域網廣播搜索
  • 局域網設備搜索
  • 多播組播

一直不停的在C++、Java、Web以及linux系統運維等技術方面不停的來回切換,忽然發現好久沒有作c++了,最近一直在作java的要多,關於c++這塊從事有8-9年的開發,這塊相關的文章總結的卻比Java要少不少。 linux

  • 一個是c++的技術難度和門檻要高不少,一直都在不斷積累,可概要的技術點很少,抽不出時間進行文章化。
  • 一個是Java相關的技術容易的多,可寫的內容也多,研究一門基本上都有對應的doc文檔,因此將文檔在翻譯一遍成網絡文章就相對容易不少

無論如何,由於工做緣由,最近又槓上了C++的音視頻這塊相關技術,正好抽空將以前全部的c++資料和庫總結了一下:c++

1.png
不總結還好,一旦總結,仍是不少的,不少實際項目中開發的庫和組件還沒提取出來,不過對於一個老C++程序員來講,項目中開發好的穩定的庫和資料是彌足珍貴的,要多善於總結和滾雪球。  
在開始講解boost網絡庫以前,我這裏先給你們稍微普及一下windows下的網絡知識,我是windows下開發的,關於socket編程在windows下有多重開發模型,能夠參考一下《winsock網絡編程》 一書    程序員

  • 選擇模型(select)  

選擇模型是網絡編程中最基礎、最簡單的一種模型,由於其簡單易用性因此備受學生時代的在校生或畢業生使用,可是它也是性能最差的一種編程模型。選擇模型是針對select而言的,它能夠阻塞能夠是非阻塞的,因此它包括兩種模式:    
(1)、 阻塞模式  
執行I/O操做完成前會一直進行等待,不會將控制權交給程序  
(2)、 非阻塞模式  
執行I/O操做時,WinSock函數會返回並交出控制權。由於函數在沒有運行完成就進行返回,並會不斷地返回 WSAEWOULDBLOCK錯誤,可是它功能很強大。  express

  • 異步消息選擇  

異步消息模型就是藉助windows的消息機制來實現的,熟悉win32應用開發或者mfc應用開發的人員都知道,全部的窗口都有對應的消息處理機制,咱們只須要建立一個窗口句柄HWND,而後將HWND與對應的消息進行綁定便可,它是經過WsaAsyncSelect接口函數來實現的,它實現的主要思路是:  
(1)、首先咱們定義一個消息,告訴系統當有客戶端消息到達的時候,發送該消息通知咱們  
(2)、而後在消息處理函數裏面添加對消息的處理便可   編程

  • 事件模型  

除此以外,winsock還提供了一個異步I/O模型-事件模型--WsaEventSelect ,和WSAAsyncSelect模型相似的是,它也容許應用程序在一個或多個套接字上,接收以事件爲基礎的網絡事件通知。該模型最主要的差異在於網絡事件會投遞至一個事件對象句柄,而非投遞至一個窗口例程。  
事件選擇模型不用主動去輪詢全部客戶端套接字是否有數據到來的模型,它也是在客戶端有數據到來時,系統發送通知給咱們的程序,可是,它不是發送消息,而是經過事件的方式來通知咱們的程序,這就解決了WsaAsyncSelect模型只能用在Win32窗口程序的問題。  windows

  • 重疊I/O模型  

重疊模型是讓應用程序使用重疊數據結構(WSAOVERLAPPED),一次投遞一個或多個 WinSock I/O請求。針對這些請求,在它們完成後,應用程序會受到通知,因而就能夠經過另外的代碼來處理這些數據了。有兩種方法能夠用來管理重疊I/O請求完成的狀況 – 即接到重疊操做完成的通知時處理  
(1) 事件對象通知 (Event Object Notification)  
基於事件通知的方法,就是要將WinSock事件對象與WSAOVERLPPED結構關聯在一塊兒,在使用重疊結構的狀況下,咱們經常使用的 send,sendto,recv,recvfrom 也要被WSASend,WSASendto,WSARecv,WSARecvfrom 替換掉了。  
(2) 完成例程(Completion Routies)  
完成例程來實現重疊I/O比用事件通知簡單得多,在這個模型中,主線程只用不停的接受鏈接便可服務器

WSARecv(
 lpPerIOData->sClient,
 &lpPerIOData->Buffer,
 1,
 &lpPerIOData->NumberOfByteRecvd,
 &lpPerIOData->Flags,
 &lpPerIOData->overlap,
 CompletionRoutine);
  • 完成端口  

完成端口對象取代了WSAAsyncSelect中的消息驅動和WSAEventSelect中的事件對象,固然完成端口模型的內部機制要比WSAAsyncSelect和WSAEventSelect模型複雜得多,可是是性能最佳的網絡編程模型。從本質上說,完成端口模型要求咱們建立一個Win32完成端口對象,經過指定數量的線程,對重疊I/O請求進行管理,以便爲已經完成的重疊I/O請求提供服務。   
倘若一個應用程序同時須要管理爲數衆多的套接字,那麼採用這種模型,每每能夠達到最佳的系統性能!       
boost庫的網絡模型實如今windows上底層是經過iocp完成端口模型實現的,因此是性能最佳的一種網絡模型實現。  微信

2、實現

boost庫中有一個專門的用於網絡編程的庫-asio,也就是異步io,它能實現tcp、udp、甚至usb串口數據讀取的功能,它是一個很是強大的、跨平臺的異步網絡通訊庫,這就是我爲何選擇它的緣由。  
在介紹源碼實現的時候,咱們先了解一下asio中的幾個經常使用對象,和socket同樣,它包含以下幾個對象  網絡

  • io_service  

它主要做爲一個事件驅動器,在多線程編程裏面提供了任務隊列和任務分發功能

  • acceptor  

它和socket同樣,提供了端點鏈接監聽相關能力

  • endpoint和address  

address封裝了設備ipv4和ipv6的地址,endpoint標識address和port的組合,決定一臺機器的某個端口(咱們稱之爲端點)。  

  • socket  

套接口編程模型的核心類,提供了同步和異步操做接口集合  
說明了以上幾個核心概念以後,我須要經過boost網絡庫封裝實現以下幾個接口的能力,包括tcp、udp、廣播、數據收發、同步異步等,各個接口以及說明以下:  

// ---------------------------------------------------------------------------------------
// 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);

實現總的類圖以下所示
2.png
各個類的做用以下:  

  • CClient 

提供了做爲tcp客戶端和udp客戶端的基礎實現類,包括獲取客戶id、獲取地址、端口、日誌等接口封裝  

  • CUdpClient  

實現了udp本地端口綁定、數據同步異步發送、數據接收、廣播等接口  

  • CTcpClient  

實現了tcp客戶端鏈接、斷開、發送數據、處理數據等接口  

  • CNetWork  

服務端網絡的抽象,服務端能夠同時監聽多個網卡的多個網口,它包括啓動、中止等接口  

  • CTcpServer  

是tcp網絡的實現,服務端能夠同時監聽多個tcp端點,實現多端口通訊。  

  • CNetWorkMgr

網絡管理器,管理多個網絡對象,包括添加網絡、移除網絡、清理等接口

udp數據異步發送  

NETSDK_RETURN CUdpClient::AsynSendTo(udp::endpoint ep, unsigned char* pBytes, int nLen)
{
 try
 {
 // 獲取空buffer
 BufferPtr pBuffer = m_write_buffer.GetEmptyBuffer();
 if (!pBuffer)
 return NETERR_BUFERR_FULL;
 // 填充數據
 pBuffer->FillData(pBytes, nLen);
 pBuffer->m_ep = ep;
 m_write_buffer.AddFullBuffer(pBuffer);
 // 數據是否發送完畢
 boost::mutex::scoped_lock a_lock(m_send_lock);
 if (m_send_finish)
 {
 // 獲取當前發送buffer
 BufferPtr pBuffer = m_write_buffer.GetFullBuffer();
 // 無可發送的buffer
 if (!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);
 }
 // 非阻塞模式鏈接,方式默認等待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數據處理

NETSDK_RETURN CTcpClient::AsynWrite(unsigned char* pBytes, int nLen)
{
 try
 {
 if(!m_bconnected)
 return NETERR_SEND_ERR;
 // 獲取空buffer
 BufferPtr pBuffer = m_write_buffer.GetEmptyBuffer(nLen);
 if (!pBuffer)
 return NETERR_BUFERR_FULL;
 // 填充數據
 pBuffer->FillData(pBytes, nLen);
 m_write_buffer.AddFullBuffer(pBuffer);
 // 數據是否發送完畢
 boost::mutex::scoped_lock a_lock(m_send_lock);
 if (m_send_finish)
 {
 // 獲取當前發送buffer
 BufferPtr pNextBuffer = m_write_buffer.GetFullBuffer();
 // 無可發送的buffer
 if (!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數據發送的時候,我這裏用了一個環形緩衝區,數據發送首先進入環形緩衝器,發送完成後繼續從環形緩衝區中獲取數據併發送出去。

tcp數據讀取  

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());
 // 其餘異常不認爲是斷開
 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);
 }
}

當有數據回調的時候,咱們首先檢查是否有錯誤,如網絡斷開、對方重置鏈接、關閉、超時等,若是沒有錯誤則回調出去數據,若是異常則回調斷開異常!

服務端實現

服務端實現,最重要的就是支持多個端點的監聽,支持多個客戶端的鏈接,如下一tcp服務爲例進行說明  
tcp服務端鏈接監聽實現   

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;
}

當客戶端鏈接到服務端以後,會調用異步回調  

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客戶端管理對象進行管理。

3、測試

核心的代碼我在上面已經列舉出來,其餘的枝節你們能夠自行補腦或搜索實現,下面咱們使用tcp-udp測試工具進行測試或使用我寫的自帶測試工具測試,首先來了解一下netsdk網絡的使用,個人頭文件中有關於該網絡的說明:  

// ------------------------------------------------------------------------------------------------
// 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
//
//-------------------------------------------------------------------------------------------------

   

服務端netsdk使用流程

我這裏稍做說明,若是是做爲服務器端咱們的使用方式以下:  

NetSdk_Init初始化網絡庫=>NetSdk_Listen開始監聽本機端口=>
接收鏈接=>處理數據=>
NetSdk_UnListen中止端口監聽=>NetSdk_Cleanup清理網絡庫資源

咱們能夠同時監聽多個網卡的多個端口,若是須要服務端反饋數據給客戶端,能夠經過鏈接返回的clientId,使用客戶端相關接口進行回覆和反饋。  

客戶端netsdk使用流程

做爲客戶端,若是是tcp客戶端,使用接口流程以下:  

NetSdk_Init初始化網絡庫=>NetSdk_Connect鏈接服務器=>處理數據=>NetSdk_Send發送數據到服務端=>
NetSdk_CloseHandle關閉與服務端鏈接=>NetSdk_Cleanup清理網絡庫資源

做爲tcp客戶端,數據發送也能夠支持同步和異步發送,默認是異步,同步發送接口NetSdk_SendSync

做爲客戶端,若是是udp客戶端,使用接口流程以下:  

NetSdk_Init初始化網絡庫=>NetSdk_Bind綁定本機端口=>處理數據=>NetSdk_SendTo發送數據報文到對應機器=>
NetSdk_CloseHandle關閉與服務端鏈接=>NetSdk_Cleanup清理網絡庫資源

做爲udp客戶端,數據發送也支持同步發送,同步發送接口:NetSdk_SendTo_Sync;除此以外udp還支持廣播,廣播接口也支持同步也異步發送,接口以下:  

NetSdk_Broadcast
NetSdk_Broadcast_Sync

 
個人測試工具以下所示:
3.png
包括了服務端和客戶端,能夠將該工具放在2臺機器,一臺作服務端,一臺作客戶端,若是沒有多餘機器,可使用一個工具進行測試既做爲服務端,也做爲客戶端,客戶端使用同一個ip和端口。  

做爲服務端進行測試

服務端監聽和斷開監聽代碼

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("監聽本機端口失敗!");
 return;
 }
 GetDlgItem(IDC_BUTTON_LISTEN)->SetWindowText("中止監聽");
 AddLog("服務端:開始監聽本機端口:%d", m_local_port);
 EnableServerButton(FALSE);
 }
 else {
 CloseServer();
 GetDlgItem(IDC_BUTTON_LISTEN)->SetWindowText("開始監聽");
 AddLog("服務端:中止監聽本機端口");
 EnableServerButton(TRUE);
 }
}
void CNetsdkDemonDlg::CloseServer()
{
 if (m_listenHandle > 0)
 {
 NetSdk_UnListen(m_listenHandle);
 m_listenHandle = -1;
 }
}

如本機默認監聽的端口爲1234,啓動demon後點擊監聽(本地ip使用默認的127.0.0.1監聽全部網卡)
4.png
監聽成功後效果
5.png
監聽成功後可使用tcp-dup測試工具測試鏈接本機的1234端口了
6.png
能夠看到使用tcp-udp測試工具鏈接上了個人服務端,注意個人本機真實地址192.168.50.7,服務端監聽的是全部網卡的1234端口!
而後咱們發送數據試試看!
7.png
能夠看到服務端收到了客戶端id爲3的客戶發送到服務端的數據!

做爲客戶端測試

爲了不干擾,咱們使用tcp-udp工具做爲服務端,個人測試工具做爲客戶端鏈接到服務端(固然客戶端和服務端徹底能夠用個人工具)測試以下:
8.png
能夠看到客戶端鏈接到了服務端,下面咱們開始發送數據:
9.png

總結

我給的demon僅僅調用了tcp服務端和tcp客戶端相關接口,固然還包括udp相關接口,這裏能夠本身測試調用,我想說明的是本網絡庫是通過了大量數據測試和多個實戰項目測試的一個穩定高效的網絡庫組件(120路音視頻主碼流同時發送接收),只要有了網絡庫組件,你不再在爲項目之間的通訊而擔心,只需關注業務邏輯處理便可。
固然,網絡庫進行是封裝了tcp和udp等相關通訊,協議層(私有協議或http、rtsp等與業務相關的協議)設計仍是須要你我的去設計的,這個與項目的具體業務息息相關,與底層的通訊無關(不關心傳輸的是什麼數據)。  
另外,本網絡庫是通過了大量實戰和不斷更新和修改的穩定高效的網絡庫,花費了較多的心思,因此若是須要本項目的源碼,能夠與本人直接溝通購買意向,價格在500-1000不等(本身考量一下應該是很值得了,買一個視頻教程都已經到這個數了),該庫是一個boost封裝了跨平臺的庫(linux只須要稍做改動,請自行修改,本人不會幫忙修改),代碼風格絕對是一個c++老手寫的代碼,閱讀性較強,新手上手較快,很容易修改得linux或定製本身接口或添加其餘模塊或功能,代碼一瞥:
10.png

源碼獲取、合做、技術交流請獲取以下聯繫方式:
QQ交流羣:961179337  
logo.png
微信帳號:lixiang6153  
公衆號:IT技術快餐  
電子郵箱:lixx2048@163.com  
參考文章:  
http://blog.tk-xiong.com/arch...

相關文章
相關標籤/搜索