UDP C++类封装

udp.h

#ifndef _UDP_H_20230628
#define _UDP_H_20230628

/**************************************************************
 * @brief   UDP通讯底层接口封装,适配多平台
 * @author  luotao
 * @date    20230628
 * @note    UDP是面向非连接的协议,它不与对方建立连接,用户可以一对多,传输数据量大,速度快;UDP信息包只有8字节相对TCP的20字节开销小。
 * @refer   https://blog.csdn.net/sinat_38102206/article/details/80320111
 *          https://blog.csdn.net/weixin_44522306/article/details/117734571
 ***************************************************************/

#include <stdio.h>
#include <string.h>

#ifdef WIN32
#include <winsock2.h>
#include <Windows.h>
#pragma comment(lib,"ws2_32.lib")
#define SOCKET_LEN int
#else
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#define SOCKET_LEN socklen_t
#endif

/**
 * @brief UDP服务器端类
 */
class CServerUdp{
public:
    /**
     * @brief 构造函数
     */
    CServerUdp();

    ~CServerUdp();

    //绑定接收端口进行监听,返回true表示绑定成功
    bool Bind(int nRecvPort);

    //接收客户端数据,返回实际接收数据长度
    int RecvData(char* pBuff, int nMaxLen);

    //回复数据给最近时间发送过来的客户端,成功:返回发送成功的数据长度; 失败: -1
    int SendTo(const char* pData, int nLen);

    //获取最近发送客户端信息,(存在多个客户端)
    void GetClientInfo(char ip[16], int* pPort)const;

    //指定ip端口发送数据,(存在多个客户端)
    int SendTo(const char* ip, int nPort, const char* pData, int nLen);

private:
#ifdef WIN32
    SOCKET m_socket;        //套接字
#else
    int m_socket;
#endif
    sockaddr_in m_clientAddr;  //客户端地址
};

/**
 * @brief UDP客户端类
 */
class CClientUdp
{
public:
    /**
     * @brief 构造函数
     */
    explicit CClientUdp(const char* serverIP/*服务器IP*/, int nServerPort/*服务器端口*/);
    ~CClientUdp();

    //发送数据,成功:返回发送成功的数据长度; 失败: -1
    int SendTo(const char* pData, int nLen);

    //接收客户端数据,返回实际接收数据长度
    int RecvData(char* pBuff, int nBuffLen);

    //建立与指定socket的连接 (可选)
    bool Connect();

private:

#ifdef WIN32
    SOCKET m_socket;        //套接字
#else
    int m_socket;
#endif
     char m_serverIP[16];   //服务器IP
     int m_nServerPort;     //服务器端口
};

#endif

udp.cpp

#include "udp.h"


CServerUdp::CServerUdp()
{

#ifdef WIN32
    //初始化网络环境
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    {
        printf("WSAStartup failed\n");
        return;
    }

    //建立一个UDP的socket
    //建立socket参数:socket(协议域,指定socket类型,指定协议)(和TCP协议后两个参数不同,都为IP协议族)
    m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (m_socket == SOCKET_ERROR)
    {
        printf("create socket failed\n");
        return;
    }
#else
    if( (m_socket = socket(AF_INET, SOCK_DGRAM, 0) ) ==  -1)	//socket函数类似于open函数,打开并创建一个socket,AF_INET使用IPV4协议族,SOCK_STREAM为TCP套接口
    {
        perror("socket create failed!");
        return;
    }
#endif

}

CServerUdp::~CServerUdp()
{
#ifdef WIN32
    //关闭SOCKET连接
    closesocket(m_socket);

    //清理网络环境
    WSACleanup();
#else
    close(m_socket);
#endif
}

//绑定接收端口进行监听,返回true表示绑定成功
bool CServerUdp::Bind(int nRecvPort)
{
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;		// 协议簇为IPV4的
    serverAddr.sin_port = htons(nRecvPort);		// 端口  因为本机是小端模式,网络是大端模式,调用htons把本机字节序转为网络字节序
    serverAddr.sin_addr.s_addr= htonl(INADDR_ANY);	// ip地址,INADDR_ANY表示绑定电脑上所有网卡IP

    //bind(socket描述字, 绑定给listenfd的协议地址,地址长度)
    return bind(m_socket, (sockaddr*)&serverAddr, sizeof(sockaddr)) == 0;
}

//接收客户端数据,返回实际接收数据长度
int CServerUdp::RecvData(char* pBuff, int nBuffLen)
{
    // 网络节点的信息,用来保存客户端的网络信息
    memset(&m_clientAddr, 0, sizeof(m_clientAddr));

    size_t nClientAddrLen = sizeof(sockaddr);
    //接收客户端发来的数据
    //recvfrom参数:socket名称,接收数据的缓冲区,缓冲区大小,标志位(调用操作方式),sockaddr结构地址,sockaddr结构大小地址
    //sockaddr地址用来保存从哪里发来,和发送到哪里的地址信息
    int ret =recvfrom(m_socket, pBuff, nBuffLen, 0, (sockaddr*)&m_clientAddr, (SOCKET_LEN*)&nClientAddrLen);

    //inet_ntoa函数转化为ip,ntohs函数转化为端口号
    printf("Recv msg:%s from IP:[%s] Port:[%d]\n", pBuff, inet_ntoa(m_clientAddr.sin_addr), ntohs(m_clientAddr.sin_port));

    return ret;

}

//回复数据给最近时间发送过来的客户端,成功:返回发送成功的数据长度; 失败: -1
int CServerUdp::SendTo(const char* pData, int nLen)
{
    printf("Send msg back to IP:[%s] Port:[%d]\n", inet_ntoa(m_clientAddr.sin_addr), ntohs(m_clientAddr.sin_port));
    // 发一个数据包返回给客户
    //sendto参数:socket名称,发送数据的缓冲区,缓冲区大小,标志位(调用操作方式),sockaddr结构地址,sockaddr结构大小地址
    return sendto(m_socket, pData, nLen, 0, (sockaddr*)&m_clientAddr, sizeof(sockaddr));
}

//获取最近发送客户端信息,(存在多个客户端)
void CServerUdp::GetClientInfo(char ip[16], int* pPort)const
{
    strcpy(ip, inet_ntoa(m_clientAddr.sin_addr));
    *pPort = ntohs(m_clientAddr.sin_port);
}

//指定ip端口发送,(存在多个客户端)
int CServerUdp::SendTo(const char* ip, int nPort, const char* pData, int nLen)
{
    sockaddr_in addr = { 0 };
    addr.sin_family = AF_INET;		// 协议簇为IPV4的
    addr.sin_port = htons(nPort);	// 端口  因为本机是小端模式,网络是大端模式,调用htons把本机字节序转为网络字节序
    addr.sin_addr.s_addr = inet_addr(ip);	// 客户端的ip地址

    // 发一个数据包返回给客户
    //sendto参数:socket名称,发送数据的缓冲区,缓冲区大小,标志位(调用操作方式),sockaddr结构地址,sockaddr结构大小地址
    return sendto(m_socket, pData, nLen, 0, (sockaddr*)&addr, sizeof(sockaddr));
}

/*****************************客户端UDP***************************************************/
CClientUdp::CClientUdp(const char* serverIP/*服务器IP*/, int nServerPort/*服务器端口*/)
{
    strcpy(m_serverIP, serverIP);
    m_nServerPort = nServerPort;

#ifdef WIN32
    //初始化网络环境
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    {
        printf("WSAStartup failed\n");
        return;
    }

    //建立一个UDP的socket
    //建立socket参数:socket(协议域,指定socket类型,指定协议)(和TCP协议后两个参数不同,都为IP协议族)
    m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (m_socket == SOCKET_ERROR)
    {
        printf("create socket failed\n");
        return;
    }
#else
    if( (m_socket = socket(AF_INET, SOCK_DGRAM, 0) ) ==  -1)	//socket函数类似于open函数,打开并创建一个socket,AF_INET使用IPV4协议族,SOCK_STREAM为TCP套接口
    {
        perror("socket create failed!");
        return;
    }
#endif

}


CClientUdp::~CClientUdp()
{
#ifdef WIN32
    //关闭SOCKET连接
    closesocket(m_socket);

    //清理网络环境
    WSACleanup();
#else
    close(m_socket);
#endif
}

//发送数据,成功:返回发送成功的数据长度; 失败: -1
int CClientUdp::SendTo(const char* pData, int nLen)
{
    sockaddr_in addr = { 0 };
    addr.sin_family = AF_INET;		// 协议簇为IPV4的
    addr.sin_port = htons(m_nServerPort);	// 端口  因为本机是小端模式,网络是大端模式,调用htons把本机字节序转为网络字节序
    addr.sin_addr.s_addr = inet_addr(m_serverIP);	// 服务器的ip地址

    //发送数据
    //sendto参数:socket名称,接收数据的缓冲区,缓冲区大小,标志位(调用操作方式),sockaddr结构地址,sockaddr结构大小地址
    int dwSent = sendto(m_socket, pData, nLen, 0, (sockaddr *)&addr, sizeof(sockaddr));
    if (dwSent == 0)
    {
        printf("send %s failed\n", pData);
        return -1;
    }

    return dwSent;
}

//接收客户端数据,返回实际接收数据长度
int CClientUdp::RecvData(char* pBuff, int nBuffLen)
{
    memset(pBuff, 0, nBuffLen);

    sockaddr_in addrSever = { 0 };
    size_t nServerAddrLen = sizeof(sockaddr_in);

    // 接收数据
    //recvfrom参数:socket名称,接收数据的缓冲区,缓冲区大小,标志位(调用操作方式),sockaddr结构地址,sockaddr结构大小地址
    int dwRecv = recvfrom(m_socket, pBuff, nBuffLen, 0, (sockaddr *)&addrSever, (SOCKET_LEN*)&nServerAddrLen);
    printf("Recv msg from server : %s\n", pBuff);

    return dwRecv;
}

//建立与指定socket的连接 (可选)
bool CClientUdp::Connect()
{
    sockaddr_in addr = { 0 };
    addr.sin_family = AF_INET;		// 协议簇为IPV4的
    addr.sin_port = htons(m_nServerPort);	// 端口  因为本机是小端模式,网络是大端模式,调用htons把本机字节序转为网络字节序
    addr.sin_addr.s_addr = inet_addr(m_serverIP);	// 服务器的ip地址

    return connect(m_socket, (sockaddr *)&addr, sizeof(sockaddr_in)) == 0;
}

linux客户端测试代码

#include "udp.h"

int main()
{
    CClientUdp clientUdp("192.168.10.160", 5000);
    if (clientUdp.Connect())
    {
       printf("connect server success\n");
    }
    clientUdp.SendTo("hello world", strlen("hello world"));

    char szBuf[512] = "";
    clientUdp.RecvData(szBuf, 512);
    return 0;
}

linux gcc编译:gcc -o main main.cpp udp.cpp -lstdc++

客户端运行结果:connect server success
Recv msg from server : hello world

服务器端 ip:192.168.10.160 ,windows Qt 部分测试代码

#include <QDebug>
#include <thread>
#include "util/udp.h"

void RecvThread(int nPort)
{
    printf("%d", nPort);
    CServerUdp serverUdp;
    if (serverUdp.Bind(nPort))
    {
        qDebug() << "udp port Bind success";
    }
    else
    {
        qDebug() << "udp port Bind failed";
    }

    char szBuff[4096] = "";
    int nRecvLen = 0;
    while(1)
    {
        nRecvLen = serverUdp.RecvData(szBuff, 4096);//堵塞
        if (nRecvLen)
        {
            serverUdp.SendTo(szBuff, nRecvLen);
        }
    }
}

//开启线程接收
std::thread t(RecvThread, 5000);
t.detach();

测试流程:先启动服务器程序监听端口,再启动客户端。

测试问题:虽然功能都正常但是异常情况还是和预期有点差异

1. 客户端SendTo即使给ping不通的ip发送也是同样返回成功,估计因为UDP是无连接的,只是成功发出了本机端口,对方是否接收成功不管;

2. Connect接口按理是通信方只有一个时使用,只是将IP地址和端口号进行了存储,发送时不会对数据包再进行 IP地址和Port的安全检查,更快速,适用于发送频繁场景。测试发现ip不通Connect接口都是返回成功,估计底层返回的是存储结果,并不是连接对端的结果。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UDP(用户数据报协议)是一种无连接的传输层协议,它提供了一种快速、简单和高效的数据传输方式。 UDP封装是指在使用UDP协议进行数据传输时,将应用层数据封装UDP报文的过程。UDP报文的结构包括源端口号、目的端口号、长度、校验和和数据部分。 首先,源端口号和目的端口号是UDP报文中的重要字段。源端口号表示发送端的应用程序使用的端口号,目的端口号表示接收端的应用程序监听的端口号。通过端口号,UDP协议可以将接收到的报文正确地传递给相应的应用程序。 其次,长度字段指示了UDP报文的总长度,包括UDP头部和数据部分的长度。通过长度字段,接收端可以正确地解析完整的UDP报文。 再次,校验和字段用于确认UDP报文的完整性。发送端将UDP报文的头部和数据部分计算出一个检验和,并将其放在校验和字段中。接收端在接收到UDP报文后,也会计算一个检验和。如果两个检验和相等,则可以确认UDP报文在传输过程中没有错误或损坏。 最后,数据部分是将应用层的数据打包进UDP报文中的部分。UDP报文中可以携带各种不同型的数据,包括文本、音频、视频等。 总结起来,UDP封装是将应用层数据打包成UDP报文的过程。通过源端口号、目的端口号、长度、校验和和数据部分,UDP协议可以实现无连接、快速和高效的数据传输。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值