对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_32
或link_libraries(ws2_32)
指定链接库。
但inet_ntop()
在win下运行可能会无法调用,linux下可正常调用。
解决办法有3个:
其一:按上述代码所示通过函数inet_ntoa()
替代,但其传参和返回值有所区别。
都无需返回值;
inet_ntop()
传参分别为:第一个参数可以是af_inet
或af_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测试即可。
发表评论