c++ 将UDP套接字绑定到地址无效

iezvtpos  于 2023-07-01  发布在  其他
关注(0)|答案(1)|浏览(229)

我有一个程序,它重复地向广播地址(192.168.0.255)或一组IP地址发送UDP数据包。当我省略将套接字绑定到多个地址的代码时,它工作得很好,因为我不希望客户端将数据包发送回服务器,但我热衷于添加绑定代码。添加绑定代码后程序失败;我是一个弱的网络程序员,不是一个很好的C++编码器,所以肯定会有不好的做法。
代码在使用Winsock 2的Windows 10/11上执行。
该代码打算绑定到多个esp 32模块,我将不知道在运行时的IP地址。
我使用UDP是因为我想在忽略数据包丢失的设置中以最小的开销将数据流广播到多个节点。
我试图写一个循环,在主广播线程循环中执行一次,扫描所有指定的IP地址(第93行),并保存它绑定的IP地址列表。然后,主广播循环(行133)发送到这些IP地址。问题是我无法绑定到任何IP地址,我得到错误:10049从对WSAGetLastError()的调用返回。
我似乎在第105行的bind函数上得到了一个SOCKET_ERROR。
在添加绑定代码之前,我希望至少有两个IP地址的绑定成功,我可以将数据包发送到这些地址。
我试过绑定到一个单一的IP地址,但这也失败了。

#include <iostream>
#include <WS2tcpip.h>
#include <WinSock2.h>
#include <windows.h>
#include <thread>
#include <atomic>
#include <string>
#include <random>
#include <conio.h>

#pragma comment(lib, "ws2_32.lib") //obj comment to link winsock to the executable 

#define USE_BROADCAST_ADDRESS       false // use broadcast address in general unless client doesn't support it
#define FREQUENCY_DELAY             1  // delay  =  1 / Freq Delay
#define UPDATE_QUADPART_TICKS       QueryPerformanceCounter(&tick); // update the high-resolution counter
#define TICKS_PER_SEC               ticksPerSecond.QuadPart // retrieve the number of ticks per second
#define SOCKET_PORT                 54000

// Define the subnet range for UDP broadcasting
std::string subnet = "192.168.0"; // Subnet address without the last octet
int startIP = 1;                   // Starting IP address in the subnet
int endIP =255;                   // Ending IP address in the subnet

// Atomic variables for safe multithreading
std::atomic<unsigned int> frequencyDelay(FREQUENCY_DELAY); 
std::atomic<bool> broadcastLoopFlag(false);    // atomic flag to kill the broadcast loopers thread
std::atomic<bool> isAltering(false);   // stop the server loop while we're changing the frequency
std::atomic<bool> breakLoop(false);    // main loop flag

int servLen = sizeof(sockaddr_in); // Define the server address structure length

/*
Clean up winsock resources
*/
void cleanup()
{
    WSACleanup();
}

/*
Random string generator, used to create 'length' long random strings
*/
std::string generateRandomString(int length)
{
    static const std::string charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    static std::random_device rd;
    static std::mt19937 gen(rd());
    static std::uniform_int_distribution<> dis(0, charset.length() - 1);

    std::string randomString;
    randomString.reserve(length);

    for (int i = 0; i < length; ++i)
    {
        randomString += charset[dis(gen)];
    }

    return randomString;
}

/*
 Broadcasting happens here in seperate thread.
 It continually sends UDP packets to all IP addresses in the subnet until `whileFlag` is set to `true`.
 The frequency of the broadcasting is controlled by `frequencyDelay` (1/n).
*/
void threadLoop()
{
    std::vector<std::string> boundIPAddresses;     // Collection of successfully bound IP addresses
    /*
    Create a UDP Socket

    First Param = socket family, style and type of address  (ipv4)
    AF_INET means IPv4
    Second Param = socket type, determine kind of packet it can receive
    SOCK_DGRAM means datagrams since udp deals with datagrams
    third param = protocol, related closely to socket type than family,
    since udp protocol must be used with datagram socket but either ipv4 or 6
    */
    SOCKET serverSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); // scope of whole thread
    if (serverSocket == INVALID_SOCKET)
    {
        std::cerr << "Can't create a socket! Quitting" << std::endl;
        closesocket(serverSocket);
        return;
    }
    sockaddr_in serverAddr; // scope of whole thread
    serverAddr.sin_family = AF_INET; // ipv4 family
    serverAddr.sin_port = htons(SOCKET_PORT); //take host byte order (big endian) and return 16 bit network byte; ip port host to network byte                                                              
    serverAddr.sin_addr.s_addr = INADDR_ANY;
                                              
                                              //bind ip addresses once at the start of this loop, only send data to the bound ip addresses
    std::cout << "              Attempting to bind IP Addresses" << std::endl;
    for (int ip = startIP; ip <= endIP; ++ip)
    {
        std::string ipAddress = subnet + "." + std::to_string(ip); // set the ip string with the final octet
        //std::string ipAddress = "127.0.0.1"; //for debugging as this should always work
        /*
        inet_pton: ip string to binary representation conversion
        AF_INET: ipv4
        2nd param: returns pointer of char array of string
        3rd param: result store in memory
        */
        inet_pton(AF_INET, ipAddress.c_str(), &(serverAddr.sin_addr));

        if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
        {
            std::cout << "bind failed with " << ipAddress << "; Error: " << (int)WSAGetLastError() << std::endl;
            continue;
        }
        else
        {
            std::cout << "              Bound to: " << ipAddress << std::endl;
            boundIPAddresses.push_back(ipAddress);
        }
    }
    if (boundIPAddresses.empty())
    {
        std::cout << "No IP addresses bound to" << std::endl;
        broadcastLoopFlag = true; // Stop the broadcasting thread
        breakLoop = true; // Exit the main loop
    }

    std::string buf(300, '\0'); // Message to be sent; Initialize buf with 300 null characters
    LARGE_INTEGER ticksPerSecond;
    LARGE_INTEGER tick; // a point in time
    long long lastEnteredTick = 0;
    QueryPerformanceFrequency(&ticksPerSecond);    // get the high-resolution counter's accuracy
    Sleep(500);

    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);

    // This loop continues until whileFlag is set to true
    while (!broadcastLoopFlag.load())
    {
        UPDATE_QUADPART_TICKS; // update the current tick count
        long long elapsedTime = tick.QuadPart - lastEnteredTick;
        long long delay = TICKS_PER_SEC / frequencyDelay.load();
        if (!isAltering && elapsedTime >= delay )
        {
            lastEnteredTick = tick.QuadPart; 

            std::cout << "Broadcasting" << std::endl;
            // Iterate over each IP address in the subnet

            for (const std::string& ipAddress : boundIPAddresses)
            {
                // Generate a random alphanumeric string for buf
                std::string randomString = generateRandomString(300);
                std::copy(randomString.begin(), randomString.end(), buf.begin());

                std::cout << "Sending UDP packet to: " << ipAddress << std::endl;
                sendto(serverSocket, buf.c_str(), buf.length(), 0, (const sockaddr*)&serverAddr, servLen);//send the UDP packet
            }
        }
    }
    closesocket(serverSocket);
}

/*
initializes Winsock, starts the broadcasting thread, and then enters a loop where it waits for user input to either change the broadcast frequency or exit the program.

*/
int main()
{
    SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
    // Initialize winsock
    WSADATA wsData; 
    WORD ver = MAKEWORD(2, 2);
    int wsOk = WSAStartup(ver, &wsData); //responsible for retrieving details of the winsock implementation that got linked into the executable
    // Check for winsock initialization failure
    if (wsOk != 0)
    {
        std::cerr << "Can't initialize winsock! Quitting program" << std::endl;
        return 1;
    }

    // use broadcast address in general unless client doesn't support it
    if (USE_BROADCAST_ADDRESS) 
    { 
        startIP = 255;
        endIP = 255;
    } 

    // Create and start the broadcasting thread
    std::thread myThread(threadLoop);//execute threadLoop function in new thread

    // Instructions for the user
    std::cout << "c: change frequency (1/n)\ne: exit program\n" << std::endl;

    // This loop waits for user commands
    while (!breakLoop)
    {
        if (_kbhit()) // function checks if a key has been pressed
        {
            char ch = _getch(); //retrieves the pressed key without requiring the Enter key
            switch (ch)
            {
            case 'c':
                isAltering = true; // exit the broadcast threads inner loop
                std::cout << "Enter your desired frequency (1/n): " << std::endl;
                unsigned int userInput; // store the user input for frequency delay
                std::cin >> userInput;
                frequencyDelay.store(userInput); // change the frequency here
                isAltering = false;
                break;
            case 'e':
                isAltering = true;
                std::cout << "Exiting." << std::endl;
                broadcastLoopFlag = true; // Stop the broadcasting thread
                breakLoop = true; // Exit the main loop
            }
        }
    }
    myThread.join();  // Wait for the broadcasting thread to finish
    // Properly clean up resources
    cleanup();

    return 0;
}
x6yk4ghg

x6yk4ghg1#

在添加绑定代码之前,我希望至少有两个IP地址的绑定成功,我可以将数据包发送到这些地址。
你似乎完全误解了bind()。它用于控制套接字的本地地址,地址包应该被寻址到以便套接字接收它们。它也会成为您发送的数据包的发送方地址。它与您要发送到的远程地址无关。
如果您向bind()传递不属于本地网络接口(网卡或VPN端点)的IP地址,则bind()预计会失败。
一个套接字也只有一个本地地址,所以多次调用bind()会导致失败。

相关问题