Windows下udp广播【C++】

  最近学习了下Windows下的Socket使用,在这里记录一下。

  代码及说明注释都是从微软文档里总结概括出来的,代码跑起来没问题,如有谬误不足,还请大佬们指正。

   官方文档:https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket

前置准备

  在使用api前,需要做一些必要的准备。

头文件包含

  

//包括 Winsock 2 头文件使用 Winsock API,包含大多数 Winsock 函数、结构和定义。
#include <winsock2.h>
//Ws2tcpip.h 头文件包含 WinSock 2 Protocol-Specific TCP/IP 的附件文档中引入的定义,其中包含用于检索 IP 地址的较新的函数和结构
#include <ws2tcpip.h>
//确保生成环境链接到 Winsock 库文件Ws2_32.lib。 使用 Winsock 的应用程序必须与 Ws2_32.lib 库文件链接。 #pragma注释指示链接器需要 Ws2_32.lib 文件。
#pragma comment(lib, "Ws2_32.lib")

 

启用套接字

WSADATA data;
//启用windows异步套接字2.2版本
if (WSAStartup(MAKEWORD(2, 2), &data))
{
   //进到这里说明启用失败了
}

创建套接字

//AF_INET : 表示IP v4地址族
//SOCK_DGRAM : 支持无连接数据报传输,使用固定最大长度的缓冲区;这类socket将为AF_INET或AF_INET6使用udp
//IPPROTO_UDP : 使用udp协议
SOCKET udpSocket_sender = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //如果得到的套接字无效,可以清除启用的套接字 if (INVALID_SOCKET == udpSocket_sender) {//下面代码功能是终止Winsock 2 DLL (Ws2_32.dll) 的使用 WSACleanup(); }

 

 

广播发送端

   广播发送方在做完上面准备后,还需要一步准备:`设置广播权限` 。

//设置广播权限
BOOL bBroadcast = TRUE;
setsockopt(udpSocket_sender, SOL_SOCKET, SO_BROADCAST, (const char*)&bBroadcast, sizeof(bBroadcast));
//设定目标sock地址信息
SOCKADDR_IN addr_dst;
//sin_family指代协议族,在socket编程中只能是AF_INET(即tcp/ip)。 addr_dst.sin_family = AF_INET; addr_dst.sin_port = htons(8080);//htons函数把数据类型转化成我们需要的u_short addr_dst.sin_addr.S_un.S_addr = inet_addr("192.168.1.255");//我这子网掩码是255.255.255.0,所以把最后一段改成255就是广播地址了

  发送消息逻辑:

char buf[7] = { '1','1','4','5','1','4' };
//如果发送成功,sendto会返回发送完成的字节数
if (sendto(udpSocket_sender, buf, 7, 0, (SOCKADDR*)&addr_dst,
               sizeof(addr_dst)) == SOCKET_ERROR)
{
    //发送失败会进这里
}
//sendto会隐式地绑定一个随机端口(发送消息的端口),
//也可以在发送之前显示地进行端口绑定再发送,
//所以之后我们可以不必绑定端口从这个端口接收消息
//不过我这是把广播发送到指定端口(8080),而这里隐式绑定的不一定是8080,所以接收消息在后面显示绑定端口

广播接收端

  接收端在做完前置准备部分工作后,绑定到接收信息的端口前,还需要设定下来源sock的部分信息。

SOCKADDR_IN addr_src;
addr_src.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addr_src.sin_family = AF_INET;
addr_src.sin_port = htons(8080);

  绑定端口

if (bind(udpSocket_receiver, (SOCKADDR*)&addr_src, sizeof(SOCKADDR)) == SOCKET_ERROR)
{
    //绑定失败会到这
}

  最后就是接收消息了

char recvBuf[128] = { 0 };
SOCKADDR_IN addr_cli;
int len = sizeof(addr_cli);
//等待并数据,成功时返回值为接收数据字节数
int rs = recvfrom(udpSocket_receiver, recvBuf, 128, 0, (SOCKADDR*)&addr_cli, &len);

 完整代码

  我这里是都放一个类里熟悉api了,项目是qt项目,在主窗口类里开了俩定时器,一个0.9s发一次广播(调transmission()方法),一个1s收一次(调listenUdp()方法)。(这好像是同一个线程处理的,收太快会在收的时候阻塞住,程序就会一直卡住)

  按前面的步骤搞俩线程啥的也完全能达到目的看到效果,我用qt只是因为在学qt。。。

头文件:WinsocketTest.h

#pragma once
//包括 Winsock 2 头文件使用 Winsock API,包含大多数 Winsock 函数、结构和定义。
#include <winsock2.h>
//Ws2tcpip.h 头文件包含 WinSock 2 Protocol-Specific TCP/IP 的附件文档中引入的定义,其中包含用于检索 IP 地址的较新的函数和结构
#include <ws2tcpip.h>
//确保生成环境链接到 Winsock 库文件Ws2_32.lib。 使用 Winsock 的应用程序必须与 Ws2_32.lib 库文件链接。 #pragma注释指示链接器需要 Ws2_32.lib 文件。
#pragma comment(lib, "Ws2_32.lib")
class WinsocketTest
{
    //
    SOCKADDR_IN addr_dst;
    SOCKADDR_IN addr_src;
    SOCKET udpSocket_sender;
    SOCKET udpSocket_receiver;
    void initTransmission(const u_short& port);
public:
    //
    bool available;
    WinsocketTest();
    ~WinsocketTest();void transmission();
    void listenUdp();
};

源文件:WinsocketTest.cpp

#include "WinsocketTest.h"

#include <qdebug.h>

void WinsocketTest::initTransmission(const u_short& port)
{
    WSADATA data;
    //启用windows异步套接字2.2版本
    if (WSAStartup(MAKEWORD(2, 2), &data))
    {
        qDebug() << "udpSocket start failed !" << endl;
        available = false;
    }

    udpSocket_sender = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    //关闭无效udp套接字
    if (INVALID_SOCKET == udpSocket_sender)
    {
        qDebug() << "socket is INVALID_SOCKET !" << endl;
        //功能是终止Winsock 2 DLL (Ws2_32.dll) 的使用
        WSACleanup();
        available = false;
    }
    //设置广播权限
    BOOL bBroadcast = TRUE;
    setsockopt(udpSocket_sender, SOL_SOCKET, SO_BROADCAST, (const char*)&bBroadcast, sizeof(bBroadcast));
    addr_dst.sin_family = AF_INET;
    addr_dst.sin_port = port;
    addr_dst.sin_addr.S_un.S_addr = inet_addr("255.255.255.255");
    available = true;
}//使用udp 广播
//初始化
WinsocketTest::WinsocketTest()
{
    u_short port = htons(8080);
    //广播部分初始化
    initTransmission(port);

    //udp收听部分初始化

    udpSocket_receiver = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    if (INVALID_SOCKET == udpSocket_receiver)
    {
        qDebug() << "receiverSocket is INVALID_SOCKET !" << endl;
        return;
    }
    addr_src.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    addr_src.sin_family = AF_INET;
    addr_src.sin_port = port;

    if (bind(udpSocket_receiver, (SOCKADDR*)&addr_src, sizeof(SOCKADDR)) == SOCKET_ERROR)
    {
        qDebug() << "udp receiver: socket_error !" << endl;
    }
}
WinsocketTest::~WinsocketTest()
{
    if (available)
        closesocket(udpSocket_sender);

    closesocket(udpSocket_receiver);
}
void WinsocketTest::transmission()
{
    if (!available)
    {
        qDebug() << "udp is not available !";
        return;
    }

    char buf[7] = { '1','1','4','5','1','4' };
    if (sendto(udpSocket_sender, buf, 7, 0, (SOCKADDR*)&addr_dst,
               sizeof(addr_dst)) == SOCKET_ERROR)
    {
        qDebug() << "socket error !" << endl;
    }
    qDebug() << "a transmission has been sent" << endl;
}

void WinsocketTest::listenUdp()
{
    char recvBuf[128] = { 0 };
    SOCKADDR_IN addr_cli;
    int len = sizeof(addr_cli);
    //等待并数据
    recvfrom(udpSocket_receiver, recvBuf, 128, 0, (SOCKADDR*)&addr_cli, &len);

    qDebug() << "[" << inet_ntoa(addr_cli.sin_addr) << "\b_" << addr_cli.sin_port << "]:" << recvBuf << endl;
}

 

posted @ 2023-01-20 11:22  Nefure  阅读(1726)  评论(0)    收藏  举报