C++ UDP通信Demo

1,554 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

概述

  • 本文中通过C++实现:
    • 在linux下的IPv6 UDP组播通信功能;
    • windows下IPv4 UDP单播通信功能;
    • windows下IPv6 UDP单播通信功能;
    • windows下IPv6 UDP组播通信功能;

UDP定义

  • UDP(用户数据报协议)是一个简单的面向数据报的传输层协议。
  • 提供的是非面向连接的、不可靠的数据流传输。
  • UDP不提供可靠性,也不提供报文到达确认、排序以及流量控制等功能。它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。因此报文可能会丢失、重复以及乱序等。但由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
  • UDP支持一对一,一对多

UDP 优缺点

优点: 通信速度快,比TCP稍安全

UDP没有TCP拥有的各种机制,是一种无状态的传输协议,所以传输数据非常快,没有TCP的这些机制,被攻击利用的机会就少一些,但是也无法避免被攻击。

缺点: 不可靠,不稳定

因为UDP没有TCP的那些可靠机制,在网络质量不好时很容易丢包。

适用场景

UDP适用于对网络通讯质量要求不高时,要求网络通讯速度要快的场景

UDP通信方式

UDP信息传递的方式分三类

  1. 单播Unicast:是客户端与服务器之间的点到点连接。

  2. 广播BroadCast:主机之间“一对所有”的通讯模式,广播者可以向网络中所有主机发送信息。广播禁止在Internet宽带网上传输(广播风暴)。

  3. 组播MultiCast:主机之间“一对一组”的通讯模式,也就是加入了同一个组的主机可以接受到此组内的所有数据。

这里需要注意的是:只有UDP才有广播、组播的传递方式;而TCP是一对一连接通信。多播的重点是高效的把同一个包尽可能多的发送到不同的,甚至可能是未知的设备。但是TCP连接是一对一明确的,只能单播。

linux下 IPv6 UDP组播通信(C++)

Server

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
 
int main()
{
    //创建套接字
    int fd = socket(AF_INET6, SOCK_DGRAM, 0);

    //绑定本地网络信息
    struct sockaddr_in6 address = {AF_INET6, htons(6060)};
    bind(fd, (struct sockaddr*)&address, sizeof address);

    //ipv6_mreq结构提供了用于IPv6地址的多播组的信息。
    struct ipv6_mreq group;
    //将接口索引指定为0,则使用默认的多播接口。
    group.ipv6mr_interface = 0;    
    //IPv6组播组的地址。
    inet_pton(AF_INET6, "ff02::1", &group.ipv6mr_multiaddr);  
    //将套接字加入到指定接口上提供的多播组。此选项仅对数据报和原始套接字有效(套接字类>型必须为SOCK_DGRAM或SOCK_RAW)。
    setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &group, sizeof(group));

    printf("等待接收信息!\n");
    //接收数据
    char buffer[128];
    read(fd, buffer, sizeof(buffer));    
    printf("%s\n", buffer);
    return 0;

}

Cilect

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <net/if.h>
 
int main()
{
    // 创建套接字
    int fd = socket(AF_INET6, SOCK_DGRAM, 0);

    // ADDRESS
    struct sockaddr_in6 address = {AF_INET6, htons(6060)};
    //这个函数转换字符串到网络地址,第一个参数af是地址簇,第二个参数*src是来源地址,第三个参数* dst接收转换后的数据。
    inet_pton(AF_INET6, "ff02::1", &address.sin6_addr);

    //发送数据
    char buffer[128];
    strcpy(buffer, "");
    sendto(fd, "hello world!", sizeof buffer, 0, (struct sockaddr*)&address, sizeof address);
    return 0;
}

windows下IPv4 UDP单播通信(C++、MFC)

Cilect

#include <stdio.h>
#include <Ws2tcpip.h>
#include <winsock2.h>
#define HELLO_PORT  7905    
#define HELLO_GROUP "224.0.0.1"    
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
	WSADATA wsaData;//初始化
	//初始化Socket
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	sockaddr_in RecvAddr;         //服务器地址
	sockaddr_in SenderAddr;       //本地地址
	int localPort = 4001;         //本地监听端口
	int Port = 4000;              //服务器监听端口

	char SendBuf[1024];           //发送数据的缓冲区
	int BufLen = 1024;            //缓冲区大小
	//创建Socket对象
	SOCKET SendSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	//设置服务器地址
	RecvAddr.sin_family = AF_INET;
	RecvAddr.sin_port = htons(Port);
	inet_pton(AF_INET, "127.0.0.1", &RecvAddr.sin_addr);

	//绑定绑定监听端口
	SenderAddr.sin_family = AF_INET;
	SenderAddr.sin_port = htons(localPort);
	SenderAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	bind(SendSocket, (SOCKADDR*)&SenderAddr, sizeof(SenderAddr));

	int l_naddLen1 = sizeof(SenderAddr);
	printf("客户端发送:\n");
	while (1)
	{
		strcpy_s(SendBuf, "hello");
		int l_nLen = sendto(SendSocket, SendBuf, strlen(SendBuf), 0, (SOCKADDR*)&RecvAddr, sizeof(RecvAddr));
		if (l_nLen < 0)
		{
			perror("发送失败");
			exit(1);
		}

		printf("\nSend:");
		for (int i = 0; i < strlen(SendBuf); i++)
		{
			printf("%02x ", (UCHAR)SendBuf[i]);
		}
		int l_nReadLen = recvfrom(SendSocket, SendBuf, BufLen, 0, (struct sockaddr*)&SenderAddr, &l_naddLen1);
		printf("\nread:");
		for (int i = 0; i < l_nReadLen; i++)
		{
			printf("%02x ", SendBuf[i]);
		}
		Sleep(1000);

	}
	//发送完成,关闭Socket
	closesocket(SendSocket);
	WSACleanup();
	return 0;
}

Server

#include<WINSOCK2.H>
#include<iostream>
#pragma comment(lib,"WS2_32.lib")
using namespace std;
int main()
{
	WSADATA wsaData;//初始化
	//初始化Socket
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	sockaddr_in RecvAddr;        //本地地址
	sockaddr_in SenderAddr;      //服务器地址
	int Port = 4000;             //本地监听地址

	char RecvBuf[1024];//发送数据的缓冲区
	int BufLen = 1024;//缓冲区大小
	//创建接收数据报的socket
	SOCKET RecvSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	//绑定本地监听地址
	RecvAddr.sin_family = AF_INET;
	RecvAddr.sin_port = htons(Port);
	RecvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	bind(RecvSocket, (SOCKADDR*)&RecvAddr, sizeof(RecvAddr));

	int SenderAddrSize = sizeof(SenderAddr);
	printf("服务的接收:\n");
	while (1)
	{
		strcpy_s(RecvBuf, "hello");
		int l_nLen = recvfrom(RecvSocket, RecvBuf,BufLen, 0, (SOCKADDR*)&SenderAddr, &SenderAddrSize);

		if (l_nLen < 0)
		{
			perror("发送失败");
			exit(1);
		}

		printf("\nSend:");
		for (int i = 0; i < strlen(RecvBuf); i++)
		{
			printf("%02x ", (UCHAR)RecvBuf[i]);
		}
		int l_nReadLen = sendto(RecvSocket, RecvBuf, strlen(RecvBuf), 0, (SOCKADDR*)&SenderAddr, SenderAddrSize);
		printf("\nread:");
		for (int i = 0; i < l_nReadLen; i++)
		{
			printf("%02x ", RecvBuf[i]);
		}
		Sleep(1000);

	}
	//调用Recvfrom函数在绑定的socket上接收数据
	//关闭socket,结束接收数据
	closesocket(RecvSocket);
	//释放资源,退出
	WSACleanup();
	return 0;
}

windows下IPv6 UDP单播通信(C++、MFC)

Server

#include <stdio.h>
#include <Ws2tcpip.h>
#include <winsock2.h>
#define HELLO_PORT  7905    
#define HELLO_GROUP "224.0.0.1"    
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
	WSADATA wsaData;//初始化
	//初始化Socket
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	sockaddr_in6 RecvAddr;         //服务器地址
	sockaddr_in6 SenderAddr = { AF_INET6, htons(6060) };       //本地地址

	char RecvBuf[1024];//发送数据的缓冲区
	int BufLen = 1024;//缓冲区大小
	//创建接收数据报的socket
	SOCKET RecvSocket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
 
	bind(RecvSocket, (SOCKADDR*)&SenderAddr, sizeof(SenderAddr));

	int SenderAddrSize = sizeof(RecvAddr);
	printf("服务的接收:\n");
	while (1)
	{
		strcpy_s(RecvBuf, "hello");
		int l_nLen = recvfrom(RecvSocket, RecvBuf,BufLen, 0, (SOCKADDR*)&RecvAddr, &SenderAddrSize);

		if (l_nLen < 0)
		{
			perror("发送失败");
			exit(1);
		}

		printf("\nSend:");
		for (int i = 0; i < strlen(RecvBuf); i++)
		{
			printf("%02x ", (UCHAR)RecvBuf[i]);
		}
		int l_nReadLen = sendto(RecvSocket, RecvBuf, strlen(RecvBuf), 0, (SOCKADDR*)&RecvAddr, SenderAddrSize);
		printf("\nread:");
		for (int i = 0; i < l_nReadLen; i++)
		{
			printf("%02x ", RecvBuf[i]);
		}
		Sleep(1000);

	}
	//调用Recvfrom函数在绑定的socket上接收数据
	//关闭socket,结束接收数据
	closesocket(RecvSocket);
	//释放资源,退出
	WSACleanup();
	return 0;
}

Cilect

#include <stdio.h>
#include <Ws2tcpip.h>
#include <winsock2.h>
#define HELLO_PORT  7905    
#define HELLO_GROUP "224.0.0.1"    
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
	WSADATA wsaData;//初始化
	//初始化Socket
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	sockaddr_in6 RecvAddr = { AF_INET6, htons(6060) };         //服务器地址
	inet_pton(AF_INET6, "fe80::4c09:7105:c377:4bc7", &RecvAddr.sin6_addr);       //服务器IP
	sockaddr_in6 SenderAddr = { AF_INET6, htons(7000) };       //本地地址

	char SendBuf[1024];           //发送数据的缓冲区
	int BufLen = 1024;            //缓冲区大小
	//创建Socket对象
	SOCKET SendSocket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);

	bind(SendSocket, (SOCKADDR*)&SenderAddr, sizeof(SenderAddr));

	int l_naddLen1 = sizeof(SenderAddr);
	printf("客户端发送:\n");
	while (1)
	{
		strcpy_s(SendBuf, "hello");
		int l_nLen = sendto(SendSocket, SendBuf, strlen(SendBuf), 0, (SOCKADDR*)&RecvAddr, sizeof(RecvAddr));
		if (l_nLen < 0)
		{
			perror("发送失败");
			exit(1);
		}

		printf("\nSend:");
		for (int i = 0; i < strlen(SendBuf); i++)
		{
			printf("%02x ", (UCHAR)SendBuf[i]);
		}
		int l_nReadLen = recvfrom(SendSocket, SendBuf, BufLen, 0, (struct sockaddr*)&SenderAddr, &l_naddLen1);
		printf("\nread:");
		for (int i = 0; i < l_nReadLen; i++)
		{
			printf("%02x ", SendBuf[i]);
		}
		Sleep(1000);

	}
	//发送完成,关闭Socket
	closesocket(SendSocket);
	WSACleanup();
	return 0;

}

windows下IPv6 UDP组播通信(C++、MFC)


Server

#include <stdio.h>
#include <Ws2tcpip.h>
#include <winsock2.h>  
#pragma comment(lib,"ws2_32.lib")

#define PORT  6060   
#define IP "ff02::2"    
#define BUF_LEN 256  

int main(int argc, char* argv[])
{
    WSADATA     wsaData;
    WORD wVersionRequested;                   // 版本
    wVersionRequested = MAKEWORD(1, 1);       //版本信息
    WSAStartup(wVersionRequested, &wsaData);  //初始化Windows套接字库

    //使用此结构来指定将套接字连接到的本地或远程端点地址
    struct sockaddr_in6 addr = { AF_INET6, htons(PORT) };
    
    //创建一个UDP套接字
    int l_nServer;
    if ((l_nServer = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
    {
        perror("创建失败");
        return -1;
    }
    bind(l_nServer, (struct sockaddr*)&addr, sizeof(addr));
    //ipv6_mreq结构提供了用于IPv6地址的多播组的信息。
    struct ipv6_mreq group;
    //将接口索引指定为0,则使用默认的多播接口。
    group.ipv6mr_interface = 0;
    //IPv6组播组的地址。
    inet_pton(AF_INET6, IP, &group.ipv6mr_multiaddr);
    //将套接字加入到指定接口上提供的多播组。此选项仅对数据报和原始套接字有效(套接字类>型必须为SOCK_DGRAM或SOCK_RAW)。
    setsockopt(l_nServer, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (char*)&group, sizeof(group));

    int l_naddLen = sizeof(addr);
    int l_nReadLen = 0;
    char msgbuf[BUF_LEN];
    printf("等待接收\n");
    while (1)
    {
        l_nReadLen = recvfrom(l_nServer, msgbuf, BUF_LEN, 0, (struct sockaddr*)&addr, &l_naddLen);
        if (l_nReadLen < 0)
        {
            perror("接收失败");
            exit(1);
        }
        msgbuf[l_nReadLen] = '\0';
        printf("%s\n", msgbuf);

        strcpy_s(msgbuf, "world");
        int l_nLen = sendto(l_nServer, msgbuf, strlen(msgbuf), 0, (struct sockaddr*)&addr, sizeof(addr));
        if (l_nLen < 0)
        {
            perror("发送失败");
            exit(1);
        }
        printf("Send %s\n", msgbuf);
    }
   
    return 0;
}

Cilect

#include <stdio.h>
#include <Ws2tcpip.h>
#include <winsock2.h>
#define HELLO_PORT  7905    
#define HELLO_GROUP "224.0.0.1"    
#pragma comment(lib,"ws2_32.lib")

int main(int argc, char* argv[])
{
    WSADATA     wsaData;
    WORD wVersionRequested;
    wVersionRequested = MAKEWORD(1, 1);
    // Initialize Windows socket library
    WSAStartup(0x0202, &wsaData);
    int l_nCilect;

    struct sockaddr_in6 addr = { AF_INET6, htons(6060) };

    /* 创建一个UDP套接字 */
    if ((l_nCilect = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
    {
        perror("创建失败");
        exit(1);
    }
    inet_pton(AF_INET6, "ff02::2", &addr.sin6_addr);

    char message[128];
    int l_naddLen = sizeof(addr);
    while (1)
    {
        strcpy_s(message, "hello");
        int l_nLen = sendto(l_nCilect, message, strlen(message), 0, (struct sockaddr*)&addr, sizeof(addr));
        if (l_nLen < 0)
        {
            perror("发送失败");
            exit(1);
        }
        printf("Send %s\n", message);
        Sleep(1000);
        int l_nReadLen = recvfrom(l_nCilect, message, strlen(message), 0, (struct sockaddr*)&addr, &l_naddLen);
        if (l_nReadLen < 0)
        {
            perror("接收失败");
            exit(1);
        }
        message[l_nReadLen] = '\0';
        printf("%s\n", message);

    }

    return 0;
}