绑定UDP套接字到地址不起作用。

huangapple go评论83阅读模式
英文:

Binding UDP socket to addresses not working

问题

我有一个程序,它会重复地向广播地址(192.168.0.255)或一组特定的IP地址发送UDP数据包。如果我省略绑定套接字到多个地址的代码,它可以正常工作,因为我不希望客户端向服务器发送数据包,但我很想添加绑定代码。在添加绑定代码后,程序失败了;我在网络编程方面不是很强,也不是很擅长C++编程,所以可能存在不良实践。

代码在Windows 10/11上使用Winsock2执行。

这段代码意图绑定到多个esp32模块,但我在运行时不会知道IP地址。

我使用UDP是因为我想向多个节点广播数据流,以在忽略数据包丢失的情况下尽量减少开销。

我正在尝试编写一个循环,它在主广播线程循环中执行一次,扫描所有指定的IP地址(第93行),并保存绑定到的IP地址列表。然后,主广播循环(第133行)向这些IP地址发送数据。问题是,我无法绑定到任何IP地址,我得到了从调用WSAGetLastError()返回的错误10049。

在第105行的bind函数上,我似乎得到了一个SOCKET_ERROR。

我期望在添加绑定代码之前,至少可以成功绑定到两个IP地址,然后再发送数据包。我已尝试绑定到单个IP地址,但也失败了。

英文:

I have a program that repeatedly sends UDP packets to either a broadcast address (192.168.0.255) or to a set number of IP addresses. It works well when I omit code that binds the socket to multiple addresses as I don't expect the clients to send packets back to the server however I'm keen to add binding code. After adding the binding code the program fails; I am a weak networks programmer and not a very good C++ coder so there may definitely be bad practices.

Code executes on windows 10/11 using Winsock2.

The code intends to bind to multiple esp32 modules, I will not know the IP addresses at run time.

I am using UDP because I want to broadcast a datastream to multiple nodes with minimal overhead in a setup that ignores packet loss.

I'm attempting to write a loop that executes once in the main broadcasting thread loop that scans all specified IP addresses (line 93), and saves a list of IP addresses that it bound to. Then the main broadcasting loop (line 133) sendto's to those IP addresses. The issue is I am unable to bind to any IP address and I get error: 10049 returned from a call to WSAGetLastError().

I seem to get a SOCKET_ERROR on the bind function on line 105.

I am expecting binding to succeed for at least two IP addresses that I could send packets to before i added the binding code.

I've tried binding to a single IP address however this also fails.

#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;
}

答案1

得分: 1

我期望绑定至少两个IP地址成功,这些地址在我添加绑定代码之前,我可以发送数据包到这些地址。

你似乎完全误解了 bind()。它用于控制套接字的本地地址,数据包应该寻址到该地址以便您的套接字接收它们。它还成为您发送的数据包的发送地址。它与您要发送到的远程地址无关。

如果您传递给 bind() 的IP地址不属于本地网络接口(网络卡或VPN端点),它预计会失败。

单个套接字也只有一个本地地址,因此多次调用 bind() 是失败的方式。

英文:

> I am expecting binding to succeed for at least two IP addresses that I could send packets to before i added the binding code.

You seem to be totally misunderstanding bind(). It is used to control the local address of your socket, the address packets should be addressed to in order for your socket to receive them. It also becomes the sender address for packets you send out. It has nothing to do with the remote addresses you are sending to.

bind() is expected to fail if you pass it an IP address that doesn't belong to a local network interface (network card or VPN endpoint).

A single socket also has only a single local address, so calling bind() multiple times is a recipe for failure.

huangapple
  • 本文由 发表于 2023年6月27日 21:37:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76565457.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定