一、前言

网络库是从事C++开发最基础、最核心、最常用的一个库,所有的协议都是建立在一个稳定高效的网络库之上的,所以对于c++程序员来说它是必不可少且非常非常重要的一个核心组件,我们可以使用网络库做任何我们想做的事情,比如   

  • 用于文件数据上传、下载  
  • 所有通信协议如http、rtp、rtsp等协议的封装  
  • 服务器模块多客户端监听、连接、通信
  • 客户端与服务端通信  
  • 局域网广播搜索
  • 局域网设备搜索
  • 多播组播

一直不停的在C++、Java、Web以及linux系统运维等技术方面不停的来回切换,突然发现很久没有做c++了,最近一直在做java的要多,关于c++这块从事有8-9年的开发,这块相关的文章总结的却比Java要少很多。 

  • 一个是c++的技术难度和门槛要高很多,一直都在不断积累,可概要的技术点不多,抽不出时间进行文章化。
  • 一个是Java相关的技术容易的多,可写的内容也多,研究一门基本上都有对应的doc文档,所以将文档在翻译一遍成网络文章就相对容易很多

不管如何,因为工作原因,最近又杠上了C++的音视频这块相关技术,正好抽空将之前所有的c++资料和库总结了一下:

1.png
不总结还好,一旦总结,还是很多的,很多实际项目中开发的库和组件还没提取出来,不过对于一个老C++程序员来说,项目中开发好的稳定的库和资料是弥足珍贵的,要多善于总结和滚雪球。  
在开始讲解boost网络库之前,我这里先给大家稍微普及一下windows下的网络知识,我是windows下开发的,关于socket编程在windows下有多重开发模型,可以参考一下《winsock网络编程》 一书    

  • 选择模型(select)  

选择模型是网络编程中最基础、最简单的一种模型,因为其简单易用性所以备受学生时代的在校生或毕业生使用,但是它也是性能最差的一种编程模型。选择模型是针对select而言的,它可以阻塞可以是非阻塞的,所以它包括两种模式:    
(1)、 阻塞模式  
执行I/O操作完成前会一直进行等待,不会将控制权交给程序  
(2)、 非阻塞模式  
执行I/O操作时,WinSock函数会返回并交出控制权。因为函数在没有运行完成就进行返回,并会不断地返回 WSAEWOULDBLOCK错误,但是它功能很强大。  

  • 异步消息选择  

异步消息模型就是借助windows的消息机制来实现的,熟悉win32应用开发或者mfc应用开发的人员都知道,所有的窗口都有对应的消息处理机制,我们只需要创建一个窗口句柄HWND,然后将HWND与对应的消息进行绑定即可,它是通过WsaAsyncSelect接口函数来实现的,它实现的主要思路是:  
(1)、首先我们定义一个消息,告诉系统当有客户端消息到达的时候,发送该消息通知我们  
(2)、然后在消息处理函数里面添加对消息的处理即可   

  • 事件模型  

除此之外,winsock还提供了一个异步I/O模型-事件模型--WsaEventSelect ,和WSAAsyncSelect模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知。该模型最主要的差别在于网络事件会投递至一个事件对象句柄,而非投递至一个窗口例程。  
事件选择模型不用主动去轮询所有客户端套接字是否有数据到来的模型,它也是在客户端有数据到来时,系统发送通知给我们的程序,但是,它不是发送消息,而是通过事件的方式来通知我们的程序,这就解决了WsaAsyncSelect模型只能用在Win32窗口程序的问题。  

  • 重叠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完成端口模型实现的,所以是性能最佳的一种网络模型实现。  

二、实现

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客户端管理对象进行管理。

三、测试

核心的代码我在上面已经列举出来,其他的枝节大家可以自行补脑或搜索实现,下面我们使用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...


贝壳里的沙
47 声望4 粉丝

毕业于中国石油大学软件工程系,先后就职于北京方正集团、北京用友财务软件股份有限公司、广东安居宝数码科技股份有限公司、广东东道信息科技有限公司,拥有10年以上的开发管理经验,擅长安防相关的音视频编技术...


引用和评论

0 条评论