win系统C++的udp通信(接收并发送)详细教程、win下inet_pton和inet_ntop无法使用解决方法

对UDP编程0基础的可以参考这篇记录博文。

我做的是同一个程序中接收指定IP地址和端口号的信息作为输入,通过程序的算法进行处理,处理后的信息再通过另一个指定IP地址和端口号进行发送。也就是需要做两个udp一个接收数据,另一个发送数据。

网上的教程都是两个或多个.CPP文件的代码,对于UDP零基础的不太友好,代码结构包括运行步骤也不清晰。

一、源码编写

源码的话网上有很多资源,大多大同小异。

修改后源码:

//
// Created by JinxBIGBIG on 2022/8/3.
//
#include 
#include 
#include 
#include 
#include "positionInfo.h"
#include "OdrManager.hh"
#include "ShareMessage.h"
//链接静态库
#pragma comment (lib,"ws2_32.lib")

using namespace std;
using namespace OpenDrive;

//传参分别为:待发送信息;发送端口号;发送ip地址
int sendTo(char *sendBuf, short sendHostShort, const char *sendIP)
{
    WSADATA wdata;
    WORD wVersion;
    wVersion = MAKEWORD(2, 2);
    WSAStartup(wVersion, &wdata);
    if (HIBYTE(wdata.wVersion) != 2 || LOBYTE(wdata.wVersion) != 2)
    {
        return -1;
    }
    sockaddr_in sClient;
    sClient.sin_family = AF_INET;
    sClient.sin_port = htons(sendHostShort);

    //此处或出现bug,解决办法详见下文
    //inet_pton(AF_INET, "127.0.0.1", &sClient.sin_addr);
    sClient.sin_addr.S_un.S_addr = inet_addr(sendIP);

    SOCKET psock = socket(AF_INET, SOCK_DGRAM, 0);
    int len = sizeof(sClient);
    //char sendBuf[128];
    while (1)
    {

        //memset(sendBuf, 0, sizeof(sendBuf));
        //cout << "pelase input word:";
        //cin.getline(sendBuf, 64);
        sendto(psock, sendBuf, sizeof(sendBuf), 0, (sockaddr*)&sClient, len);
        //cout << "Send over;" << endl;
    }
    return 0;
}

int main()
{
   //以下为对接收数据进行处理的相关算法
    OpenDrive::OdrManager manager;
    Point point;
    PointLaneInfo pointLaneInfo;
    string xodrPath = "..\\map2.xodr";
    //string xodrPath = "..\\data\\map.xodr";
    bool xodrLoad = manager.loadFile(xodrPath);
    OpenDrive::Position* pos = manager.createPosition();
    manager.activatePosition(pos);
    
    //接收IP和发送IP、接收端口号和发送端口号设置
    char const *receiveIP = "127.0.0.1";
    char const *sendIP = "127.0.0.1";
    short receiveHostShort;
    short sendHostShort;
    cout << "Please input receiveHostPort(like:9999) and sendHostPort(like:9999)" << endl;
    cin >> receiveHostShort >> sendHostShort;
  
    //接收UDP编写
    WSADATA  wsData;
    int nret = WSAStartup(MAKEWORD(2, 2), &wsData);
    if(nret!=0)
        return nret;
    sockaddr_in sa,recSa;
    int len = sizeof(sa);
    sa.sin_addr.S_un.S_addr = inet_addr(receiveIP);
    sa.sin_family = AF_INET;
    sa.sin_port = htons(receiveHostShort);
    SOCKET  sock = socket(AF_INET, SOCK_DGRAM, 0);

    if (sock==INVALID_SOCKET)
        return WSAGetLastError();
    bind(sock, (sockaddr*)&sa, len);
    while (true)
    {
        char buf[1024];
        memset(buf, 0, 1024);
        int nlen = recvfrom(sock, buf, 1024, 0, (sockaddr*)&recSa, &len);
        if (nlen>0)
        {
            //char sIP[20];

            //此处或出现BUG,详见下文
            //inet_ntop(AF_INET, &recSa.sin_addr, sIP, 20);
            inet_ntoa(recSa.sin_addr);
            cout << "The information :" << buf << endl;
            istringstream str(buf);
            double out;
            int i = 0;
            //接收的信息设置是一个三维坐标,类型为double,(x, y , z)
            double p[3];
            //将接收的char类型的数据以空格为分隔符分别读取xyz值
            while (str >> out) {
                //cout << out << endl;
                p[i] = out;
                i++;
            }
            point = {p[0], p[1], p[2]};
            //对接收的数据进行处理
            manager.setInertialPos(point.x, point.y, point.z);
            bool result = manager.inertial2lane();
            cout << "Position initialing result :" << result << endl;
            //处理后的输出信息为roadID
            int roadID = manager.getRoadHeader()->mId;
            //转换为char *类型后进行发送
            string str1 = "";
            str1 += to_string(roadID);
            char* sendBuf = const_cast<char *>(str1.data());
            cout << "sendBuf: "<< *sendBuf << endl;
            //此处对发送方法进行了封装直接调用sendTo即可,sendTo见main函数上方
            sendTo(sendBuf, sendHostShort, sendIP);
        }
    }
    /*int count = 1;
    string str = "";
    str += to_string(count);
    char* roadID = const_cast(str.data());
    sendTo(roadID, sendHostShort, sendIP);*/
}

详解

2.1、代码运行可能BUG

对于上述代码有两个函数的调用可能会出现BUG:

receive:

  //inet_ntop(AF_INET, &recSa.sin_addr, sIP, 20);
  inet_ntoa(recSa.sin_addr);

这两个函数实现的都是将网络字节序列IP地址转化为字符串IP地址。都依赖于头文件#include
并且需要添加链接库#pragma comment (lib,"ws2_32.lib")

可能BUG:添加头文件和链接库调用仍然出错的情况,可在cmakelist.txt中添加target_link_libraries(get_udp ws2_32link_libraries(ws2_32)指定链接库。

inet_ntop()在win下运行可能会无法调用,linux下可正常调用。
解决办法有3个:
其一:按上述代码所示通过函数inet_ntoa()替代,但其传参和返回值有所区别。
都无需返回值;
inet_ntop()传参分别为:第一个参数可以是AF_INETAF_INET6:第二个参数是一个指向网络字节序的二进制值的指针;第三个参数是一个指向转换后的点分十进制串的指针;第四个参数是目标的大小,以免函数溢出其调用者的缓冲区。
inet_ntoa()则只需要指向网络字节序的二进制值的指针。

其二:通过WSAAddressToStringA()方法实现并封装为inet_ntop()方法:

 
PCSTR WSAAPI inet_ntop(INT Family,const VOID *pAddr,PSTR pStringBuf, size_t  StringBufSize)
{
    if(pStringBuf ==NULL || StringBufSize == 0)
    {
       WSASetLastError(ERROR_INVALID_PARAMETER);
       return NULL;
    }
    if(Family == AF_INET6)
    {
        int ret=0;
        ret=WSAAddressToStringA((PSOCKADDR)pAddr,sizeof(PSOCKADDR),NULL,pStringBuf,(LPDWORD)&StringBufSize);
        if(ret!=0)
        {
            return NULL;
        }
    }
    else if(Family == AF_INET)
    {
        struct in_addr a;
        memcpy(&a,pAddr,sizeof(struct in_addr));
        pStringBuf = inet_ntoa(a);
    }
    else
    {
        WSASetLastError(WSAEAFNOSUPPORT);
        return NULL;
    }
    return pStringBuf;
}

封装后再调用inet_ntop()即可。

其三:直接使用WSAAddressToStringA(),代码见下方send中其三

send:

   //inet_pton(AF_INET, sendIP, &sClient.sin_addr);
   sClient.sin_addr.S_un.S_addr = inet_addr(sendIP);

这两个函数实现的都是将字符串IP地址转化为网络字节序列IP地址。都依赖于头文件#include
并且需要添加链接库#pragma comment (lib,"ws2_32.lib")。但inet_pton()其在win下运行可能会无法调用,linux下可正常调用。

解决办法有两个:
其一:按上述代码所示通过函数inet_addr()替代,但其传参和返回值有所区别。inet_pton()无需返回值,转化后的IP地址直接存储在sClient.sin_addr中;inet_addr()需要返回sClient.sin_addr的子变量sClient.sin_addr.S_un.S_addr。传参就比较好理解,前者第一个参数为固定值,其余为字符串形式的发送IP地址和转化后IP地址;后者只需要传输字符串形式的发送IP地址。

其二:同理改写封装WSAStringToAddress

其三

/*
 ipv6 address to string or string to ipv6 address;
Edited by Mr Zhu,email:[email protected] or weixin:40222865
*/
#include 
#include 
#include 
#include 
using namespace std;
int main()
{
   WSADATA wsa_data;
       WORD sockversion = MAKEWORD(2,2);
       if(WSAStartup(sockversion, &wsa_data) != 0)
       {
           return 0;
       }
    struct sockaddr_in6 ser_addr;
    int addr_size=sizeof(struct sockaddr_in6);
    char ip_addr[100]="";
    DWORD string_leng=100;
    int i;

        WSAStringToAddress( (LPSTR)"ff::1:ff:1",    
                             AF_INET6,
                             NULL,
                            (LPSOCKADDR) &ser_addr,
                            &addr_size );

printf("16进制ip地址是:");
for(i=0;i<15;i=i+2)
{
    printf("%x%x:",ser_addr.sin6_addr.u.Byte[i],ser_addr.sin6_addr.u.Byte[i+1]);
}

ser_addr.sin6_port=htons(5240);
    WSAAddressToStringA(
        (LPSOCKADDR)&ser_addr,        // sockaddr类型指针
                    addr_size,        //地址长度
                    NULL,             //地址协议指针
        (LPSTR)     ip_addr,          //转换后字符串地址
                &string_leng          //函数返回的字符串长度
    );

    printf("\nipv6 address is\"%s\"\n",ip_addr);

memset(ip_addr,0,100);
ser_addr.sin6_port=htons(0);
    WSAAddressToStringA(
        (LPSOCKADDR)&ser_addr,        // sockaddr类型指针
                    addr_size,        //地址长度
                    NULL,             //地址协议指针
        (LPSTR)     ip_addr,          //转换后字符串地址
                &string_leng          //函数返回的字符串长度
    );
    printf("\n端口为0后显示 address is\"%s\"\n",ip_addr);
    return 1;
}

2.2、输入输出设置相关

开篇说过两个udp只需要两对IP地址和端口号即可,那么只需要在代码:

    char const *receiveIP = "127.0.0.1";
    char const *sendIP = "127.0.0.1";
    short receiveHostShort;
    short sendHostShort;

此处进行修改。上述代码接收和发送IP地址均为本机(测试用),而输入和输出端口由控制台输入,若需要指定在此处直接赋值即可。

2.3、 接收信息后的处理和发送相关

接收后的信息处理和发送均在while循环中。
receive

while (true)
    {
        char buf[1024];
        memset(buf, 0, 1024);
        int nlen = recvfrom(sock, buf, 1024, 0, (sockaddr*)&recSa, &len);
        if (nlen>0)
        {
            //char sIP[20];
            //inet_ntop(AF_INET, &recSa.sin_addr, sIP, 20);
            inet_ntoa(recSa.sin_addr);
            cout << "The information :" << buf << endl;
            istringstream str(buf);
            double out;
            int i = 0;
            double p[3];
            while (str >> out) {
                //cout << out << endl;
                p[i] = out;
                i++;
            }
            point = {p[0], p[1], p[2]};
            manager.setInertialPos(point.x, point.y, point.z);
            bool result = manager.inertial2lane();
            cout << "Position initialing result :" << result << endl;
            int roadID = manager.getRoadHeader()->mId;
            //转化为char *类型
            string str1 = "";
            str1 += to_string(roadID);
            char* sendBuf = const_cast<char *>(str1.data());
            cout << "sendBuf: "<< *sendBuf << endl;
            sendTo(sendBuf, sendHostShort, sendIP);
        }
    }

上述代码中变量buf即为接收的信息,此处博主设置的接收信息的格式为三个double类型的数据,形如1 1 1,按空格划分;接收后为char类型的buf,首先将其读取为Point类型数据point,Point结构定义如下:

struct Point{
    double x{0.};
    double y{0.};
    double z{0.};
    Point() = default;
    Point(double _x, double _y, double _z)
            : x(_x)
            , y(_y)
            , z(_z)
    {}
};

再将其作为算法输入进行处理,最后输出为一个int型的数据roadID;发送之前需要将其转换char *类型;最后再调用封装好的sendTo()方法。
send

这块代码改动不大,写好入参皆可。

三、代码运行

上述代码是本程序里边的源码,完成接收、处理和发送三个操作。直接将程序编译好运行即可。但是测试的时候还需要编写一个发送端的代码,负责发送本程序待接收的信息;另外,如果要测试是否能够正常接收,还可以再单独写一个接收端的代码。

3.1、发送端代码

send.cpp:

//
// Created by JinxBIGBIG on 2022/8/3.
//

#include 
#include 

using namespace std;

#pragma comment(lib,"ws2_32.lib")

int main()
{

    WSADATA wdata;
    
    WORD wVersion;

    wVersion = MAKEWORD(2, 2);

    WSAStartup(wVersion, &wdata);

    if (HIBYTE(wdata.wVersion) != 2 || LOBYTE(wdata.wVersion) != 2)
    {
        return -1;
    }

    sockaddr_in sClient;

    sClient.sin_family = AF_INET;
    sClient.sin_port = htons(9999);

    //inet_pton(AF_INET, "127.0.0.1", &sClient.sin_addr);
    sClient.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

    SOCKET psock = socket(AF_INET, SOCK_DGRAM, 0);

    int len = sizeof(sClient);

    char sendBuf[128];
    while (1)
    {

        memset(sendBuf, 0, sizeof(sendBuf));

        cout << "Please input the point(x, y, z):";

        cin.getline(sendBuf, 64);
        sendto(psock, sendBuf, sizeof(sendBuf), 0, (sockaddr*)&sClient, len);

    }
    return 0;
}

将此代码单独编译运行生成get_send.exe文件后,点击运行:
win系统C++的udp通信(接收并发送)详细教程、win下inet_pton和inet_ntop无法使用解决方法_第1张图片
此处附上接收端代码:

receive.cpp:

//
// Created by JinxBIGBIG on 2022/8/3.
//

#include 
#include 
#include 
#pragma comment (lib,"ws2_32.lib")
using namespace  std;

int main()
{
    WSADATA  wsData;

    int nret=WSAStartup(MAKEWORD(2, 2), &wsData);
    if(nret!=0)
    {
        return nret;
    }

    sockaddr_in sa,recSa;

    int len = sizeof(sa);

    sa.sin_addr.S_un.S_addr = INADDR_ANY;

    sa.sin_family = AF_INET;
    sa.sin_port = htons(9999);

    SOCKET  sock = socket(AF_INET, SOCK_DGRAM, 0);

    if (sock==INVALID_SOCKET)
    {
        return WSAGetLastError();
    }

    bind(sock, (sockaddr*)&sa, len);

    while (true)
    {
        char buf[1024];

        memset(buf, 0, 1024);

        int nlen = recvfrom(sock, buf, 1024, 0, (sockaddr*)&recSa, &len);

        if (nlen>0)
        {
            //char sIP[20];
            //inet_ntop(AF_INET, &recSa.sin_addr, sIP, 20);
            inet_ntoa(recSa.sin_addr);
            cout << buf << endl;
        }
    }
}

同理,将此代码单独编译运行生成get_receive.exe文件后,如有需要点击运行。

3.2、调试助手

上述1解决测试时的发送端问题,但本程序在接收发送端发送的数据并处理后还需要进行发送,那么如何查看发送的消息是否有正确接收呢?
可以下载网络调试助手,
win系统C++的udp通信(接收并发送)详细教程、win下inet_pton和inet_ntop无法使用解决方法_第2张图片
点集立即下载:
win系统C++的udp通信(接收并发送)详细教程、win下inet_pton和inet_ntop无法使用解决方法_第3张图片
我选的是箭头所指这个就自动跳到迅雷下载了。下载后是一个单独的.exe文件,
win系统C++的udp通信(接收并发送)详细教程、win下inet_pton和inet_ntop无法使用解决方法_第4张图片
双击即可运行:
win系统C++的udp通信(接收并发送)详细教程、win下inet_pton和inet_ntop无法使用解决方法_第5张图片
协议选择udp协议,ip地址下拉选项可设置为本机地址,主机端口也可设置,此处博主设置的8080。即本程序中的处理后数据的发送目标是char const *sendIP = "127.0.0.1; sendHostShort = 8080;,而负责接收数据的目标地址是char const *receiveIP = "127.0.0.1; receiveHostShort = 9999;也就是本机。

3.3、运行测试

3.3.1 运行本程序:可在编辑器里边运行也可以通过.exe文件运行,
win系统C++的udp通信(接收并发送)详细教程、win下inet_pton和inet_ntop无法使用解决方法_第6张图片
本程序IP地址都直接给的本机,端口号则是手动输入9999 8080

    char const *receiveIP = "127.0.0.1";
    char const *sendIP = "127.0.0.1";
    short receiveHostShort;
    short sendHostShort;
    cout << "Please input receiveHostPort(like:9999) and sendHostPort(like:9999)" << endl;
    cin >> receiveHostShort >> sendHostShort;

3.3.2 运行发送端程序get_send.exe给本程序发送坐标((5915.00, -2937.76, 0)):输入坐标后回车:
win系统C++的udp通信(接收并发送)详细教程、win下inet_pton和inet_ntop无法使用解决方法_第7张图片

可在本程序的终端界面看到:
win系统C++的udp通信(接收并发送)详细教程、win下inet_pton和inet_ntop无法使用解决方法_第8张图片
图中的:

The information :5915.00 -2937.76 0
Position initialing result :1
sendBuf: 4

第1行为接收的坐标信息,2、3行则是对接收的数据处理后的自定义显示结果。
3.3.3 通过调试助手接收本程序发送的数据:打开网络调试助手

win系统C++的udp通信(接收并发送)详细教程、win下inet_pton和inet_ntop无法使用解决方法_第9张图片
点击打开:
win系统C++的udp通信(接收并发送)详细教程、win下inet_pton和inet_ntop无法使用解决方法_第10张图片
即可看到已成功接收传递的数据40,3.2中的sendBuf: 4表示的是传递的char类型数据第一个元素的信息,也就是40的4。
3.3.4 另外可在图中箭头所示设置接收数据显示的类型,HEX为16进制。
win系统C++的udp通信(接收并发送)详细教程、win下inet_pton和inet_ntop无法使用解决方法_第11张图片
如果传输的数据是1,则其HEX为:win系统C++的udp通信(接收并发送)详细教程、win下inet_pton和inet_ntop无法使用解决方法_第12张图片
也就是31,数字“1”被当做字符存储时,用的ASCII码,值是49(10进制),转化为16进制就是31(316^1 + 116 ^ 0)。

如果我们要想接收端接收的是16进制的字符,那么我们就需要在发送之前进行字符串转16进制操作。

至此结束。

四、补充

如果是单独运行发送端和接收端的代码,则直接将三中的发送端代码send.cpp和接收端代码receive.cpp分别运行后执行生成的两个.exe文件再分别运行c测试即可。

你可能感兴趣的:(教程记录,#,C++相关,udp,c++,网络)