当前位置: 代码网 > 科技>操作系统>Windows > win系统C++的udp通信(接收并发送)详细教程、win下inet_pton和inet_ntop无法使用解决方法

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

2024年08月01日 Windows 我要评论
实现同一个程序中接收指定IP地址和端口号的信息作为输入,通过程序的算法进行处理,处理后的信息再通过另一个指定IP地址和端口号进行发送。也就是需要做两个udp一个接收数据,另一个发送数据;以及单独测试发送端和接收端的upd通信教程。...............

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

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

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

一、源码编写

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

修改后源码:

//
// created by jinxbigbig on 2022/8/3.
//
#include <sstream>
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#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<char *>(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 <ws2tcpip.h>
并且需要添加链接库#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 <ws2tcpip.h>
并且需要添加链接库#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:40222865@qq.com or weixin:40222865
*/
#include <stdio.h>
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
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 <iostream>
#include <ws2tcpip.h>

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文件后,点击运行:
在这里插入图片描述
此处附上接收端代码:

receive.cpp:

//
// created by jinxbigbig on 2022/8/3.
//

#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#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解决测试时的发送端问题,但本程序在接收发送端发送的数据并处理后还需要进行发送,那么如何查看发送的消息是否有正确接收呢?
可以下载网络调试助手
在这里插入图片描述
点集立即下载:
在这里插入图片描述
我选的是箭头所指这个就自动跳到迅雷下载了。下载后是一个单独的.exe文件,
在这里插入图片描述
双击即可运行:
在这里插入图片描述
协议选择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文件运行,
在这里插入图片描述
本程序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)):输入坐标后回车:
在这里插入图片描述

可在本程序的终端界面看到:
在这里插入图片描述
图中的:

the information :5915.00 -2937.76 0
position initialing result :1
sendbuf: 4

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

在这里插入图片描述
点击打开:
在这里插入图片描述
即可看到已成功接收传递的数据40,3.2中的sendbuf: 4表示的是传递的char类型数据第一个元素的信息,也就是40的4。
3.3.4 另外可在图中箭头所示设置接收数据显示的类型,hex为16进制。
在这里插入图片描述
如果传输的数据是1,则其hex为:在这里插入图片描述
也就是31,数字“1”被当做字符存储时,用的ascii码,值是49(10进制),转化为16进制就是31(316^1 + 116 ^ 0)。

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

至此结束。

四、补充

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

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com