本文转载自【微信公众号:MicroPest ,ID:gh_696c36c5382b】,经微信公众号授权转载,如需转载原文作者联系
UDP面向报文传输数据,在数据传输过程中不能保证可靠性,可能会出现丢包的情况。由于UDP是一种无连接传输方式,所以支持一对一、一对多、多对一和多对多的交互通信。
一、函数介绍
1.1 sendto()
int sendto(int s, const void *buf, int len, unsigned int flags,const struct sockaddr *to, int tolen);
返回值说明:
成功则返回实际传送出去的字符数,失败返回-1,错误原因会存于errno 中。
参数说明:
s: socket描述符; buf: UDP数据报缓存区(包含待发送数据); len: UDP数据报的长度; flags:调用方式标志位(一般设置为0); to: 指向接收数据的主机地址信息的结构体(sockaddr_in需类型转换); tolen:to所指结构体的长度;
1.2 recvfrom()
int recvfrom(int s, void *buf, int len, unsigned int flags,struct sockaddr *from, int *fromlen);
返回值说明:
成功则返回实际接收到的字符数,失败返回-1,错误原因会存于errno 中。
参数说明:
s: socket描述符; buf: UDP数据报缓存区(包含所接收的数据); len: 缓冲区长度。 flags: 调用操作方式(一般设置为0)。 from: 指向发送数据的客户端地址信息的结构体(sockaddr_in需类型转换); fromlen:指针,指向from结构体长度值。
二、实现原理
UDP基于无连接通信,所以不区分服务器端与客户端,因此在程序实现的时候,只需要同一个程序就可以完成数据通信了。
初始化Winsock环境后,便调用Socket函数创建数据报套接字;然后对sockaddr_in结构体进行设置,设置绑定的Ip地址和端口等信息并调用bind函数进行绑定;绑定成功后,可以使用recvfrom函数与sendto函数与另一UDP程序进行数据的收发。通信结束后,关闭套接字,释放资源。
三、源码
由于UDP基于无连接通信,不区分客户端与服务端,所以使用同一程序来实现数据通信。首先要做的便是绑定IP和端口。具体操作是初始化Winsock库环境,创建数据报套接字,绑定IP和端口,同时创建多线程接收通信数据。
// 绑定IP地址和端口
BOOL Bind(char *lpszIp, int iPort)
{
// 初始化 Winsock 库
WSADATA wsaData = { 0 };
::WSAStartup(MAKEWORD(2, 2), &wsaData);
// 创建数据报套接字
g_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (INVALID_SOCKET == g_sock)
{
return FALSE;
}
// 设置绑定IP地址和端口信息
sockaddr_in addr = { 0 };
addr.sin_family = AF_INET;
addr.sin_port = ::htons(iPort);
addr.sin_addr.S_un.S_addr = ::inet_addr(lpszIp);
// 绑定IP地址和端口
if (0 != bind(g_sock, (sockaddr *)(&addr), sizeof(addr)))
{
return FALSE;
}
// 创建接收信息多线程
::CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)RecvThreadProc, NULL, NULL, NULL);
return TRUE;
}
UDP不需要建立那边就可以直接发送和接收数据了,所以只要指明IP地址和端口即可。
// 数据发送
void SendMsg(char *lpszText, char *lpszIp, int iPort)
{
// 设置目的主机的IP地址和端口等地址信息
sockaddr_in addr = { 0 };
addr.sin_family = AF_INET;
addr.sin_port = ::htons(iPort);
addr.sin_addr.S_un.S_addr = ::inet_addr(lpszIp);
// 发送数据到目的主机
::sendto(g_sock, lpszText, (1 + ::lstrlen(lpszText)), 0, (sockaddr *)(&addr), sizeof(addr));
printf("[sendto]%s\n", lpszText);
}
同样,由于UDP基于无连接通信,所以可以直接接收数据,而且还可以获取接收数据的源地址信息。
// 数据接收
void RecvMsg()
{
char szBuf[MAX_PATH] = { 0 };
while (TRUE)
{
sockaddr_in addr = { 0 };
// 注意此处, 既是输入参数也是输出参数
int iLen = sizeof(addr);
// 接收数据
::recvfrom(g_sock, szBuf, MAX_PATH, 0, (sockaddr *)(&addr), &iLen);
printf("[recvfrom]%s\n", szBuf);
}
}
// 接收数据多线程
UINT RecvThreadProc(LPVOID lpVoid)
{
// 接收数据
RecvMsg();
return 0;
}
四、测试
因为使用的是同一个程序,意味着要运行两次;第一次首先输入源Ip:127.0.0.1和源端口号13579,再输入目的Ip:127.0.0.1和目的端口3579;提示绑定;
第二次首先输入源Ip:127.0.0.1和源端口号3579,再输入目的Ip:127.0.0.1和目的端口13579;提示绑定;正好和第一次的相反;
在第一个窗口中回车发送数据,则在第二个窗口中收到数据;反之,也是如此。
验证1:本机下,
查看两个程序状态,
均只能看到本地IP和端口号;远程IP和端口号不可见。
有一点没搞明白,为什么WireShark抓这个UDP程序的包没有抓到?是哪里有问题吗?
验证2:本机+虚拟机环境下,
再来抓包下,
发现了数据包,这是成功的场景。