【问题标题】:Cannot bind socket after unbinding it解除绑定后无法绑定套接字
【发布时间】:2021-07-10 15:30:10
【问题描述】:

我正在尝试编写一个客户端连接的类似服务器的程序,您可以下载您想要的任何文件(并且有权)。我制作了一个函数,它能够使客户端使用/dev/TCP/IP/port 设备发送文件(使用 bash,我不想将客户端安装到我的所有 PC)。我可以从客户端获取并保存文件,但是如果我想从同一个端口下载第二个文件(每次下载后端口都应该关闭),我会收到 EADDRINUSE 错误。

功能:

void DownloadFile(int fd, cmd_args cmdargs, arguments args) {

    // check filepaths
    if(cmdargs.lpath.size() == 0 || (exists(cmdargs.lpath.c_str()) && is_directory(cmdargs.lpath.c_str()))) {
        log(/*args.OutputFile*/"file", "Please specify a path to save the downloaded file");
        return;
    }

    if(cmdargs.rpath.size() == 0) {
        log("file", "Please enter a path to a file");
        return;
    }

    // open a second server to bind
    SockInitializer sock = InitializeSocket(cmdargs.ip, cmdargs.port, true);

    // PACKET FORMAT: [f/d][int][\n][data]
    // PACKET EXPLANATION: file/directory size_of_file new_line file_data
    // open connection
    string connect_payload = "exec 3>/dev/tcp/";
    connect_payload += cmdargs.ip;
    connect_payload += '/';
    connect_payload += to_string(cmdargs.port);
    //connect_payload += ';\n';
    // send file/dir type
    string begin_payload = "[ -d \"";
    begin_payload += cmdargs.rpath;
    begin_payload += "\" ] && echo -n d >&3 || echo -n f >&3;";
    // send size number
    begin_payload += "echo -n $(stat --printf='%s' ";
    begin_payload += cmdargs.rpath;
    begin_payload += ") >&3;";
    // new line
    begin_payload += "echo >&3;";
    // send file
    begin_payload += "cat ";
    begin_payload += cmdargs.rpath;
    begin_payload += " >&3\n\n";

    // accept connection
    SendString(fd, connect_payload);
    int cfd = accept(sock.fd, &sock.addr, &sock.addrlen);
    SendString(fd, begin_payload);

    // get type
    string str = ListenSocket(cfd, 1, true);
    bool isFile = (str == "f");

    // get file size
    string ToDownloadSTRING = "";
    char buf = '\0';
    do {
        if(buf != '\0')
            ToDownloadSTRING += buf;
        buf = ListenSocket(cfd, 1, true)[0];
    } while(buf != '\n');

    // parse file size
    int ToDownload = atoi(ToDownloadSTRING.c_str());

    /* download file */
    if(isFile) {
        int downloaded = 0;
        // open file [write binary]
        fstream file;
        file.open(cmdargs.lpath, ios::out | ios::binary);
        // loop to fetch data
        while(downloaded < ToDownload) {
            string tmp = ListenSocket(cfd, DOWNLOAD_BUF_SIZE);
            // write to file
            file.write(tmp.c_str(), tmp.size());
            // flush to file
            file.flush();
            // add size to downloaded
            downloaded += tmp.size();
        }
        // close file
        file.close();
    }

    string disconnect_payload = "exec 3>&-";
    SendString(fd, disconnect_payload);

    // terminate connection
    shutdown(cfd, SHUT_RDWR);
    close(cfd);
    shutdown(sock.fd, SHUT_RDWR);
    close(sock.fd);
}

以上是整个功能,以防您需要整个功能。 以下是相同的功能,但只有(我认为)您需要了解会发生什么的东西

void DownloadFile(int fd, cmd_args cmdargs, arguments args) {

    //check the filepaths

    // open a second socket to bind
    SockInitializer sock = InitializeSocket(cmdargs.ip, cmdargs.port, true);

    // PACKET FORMAT: [f/d][int][\n][data]
    // PACKET EXPLAN: file/directory size_of_file new_line file_data

    // Payload to connect here and send the packet with the file
    string connect_payload = "exec 3>/dev/tcp/IP/PORT";
    string begin_payload = "[ -d \"REMOTE_PATH\" ] && echo -n d >&3 || echo -n f >&3; echo -n $(stat --printf='%s' REMOTE_PATH) >&3; echo >&3; cat REMOTE_PATH >&3\n\n";

    // accept connection on the new socket we initialized earlier in the function
    SendString(fd, connect_payload);
    int cfd = accept(sock.fd, &sock.addr, &sock.addrlen);
    SendString(fd, begin_payload);

    // is REMOTE_PATH referring to a file or a directory?
    string str = ListenSocket(cfd, 1, true);
    bool isFile = (str == "f");

    // get file size
    string ToDownloadSTRING = "";
    char buf = '\0';
    do {
        if(buf != '\0')
            ToDownloadSTRING += buf;
        buf = ListenSocket(cfd, 1, true)[0];
    } while(buf != '\n');

    // parse file size to integer
    int ToDownload = atoi(ToDownloadSTRING.c_str());

    /* download file */
    if(isFile) {
        int downloaded = 0;
        // open file [write binary]
        fstream file;
        file.open(LOCAL_PATH, ios::out | ios::binary);
        // loop to fetch data
        while(downloaded < ToDownload) {
            // get data
            string tmp = ListenSocket(cfd, DOWNLOAD_BUF_SIZE);
            // write to file
            file.write(tmp.c_str(), tmp.size());
            file.flush();
            // add size to downloaded
            downloaded += tmp.size();
        }
        // close file
        file.close();
    }

    // make the client close the file descriptor referring to the tcp connection
    string disconnect_payload = "exec 3>&-";
    SendString(fd, disconnect_payload);

    // terminate connection from the server
    shutdown(cfd, SHUT_RDWR);
    close(cfd);
    shutdown(sock.fd, SHUT_RDWR);
    close(sock.fd);

}

注意事项

问题中缺少很多功能,如果您需要任何功能,请询问,我只是认为您可以从名称中了解它们的作用。

客户端应该使用这个 bash connect 连接到服务器(我知道这实际上并不安全):/bin/bash -i &gt;&amp; /dev/tcp/IP/PORT 0&gt;&amp;1

我正在使用parrotOS:Linux

函数应该被调用两次而不终止程序,或者断开客户端与主套接字(传递给函数的fd)

【问题讨论】:

标签: c++ sockets download connection bind


【解决方案1】:

正如@Ted Lyngmo 所指出的,我需要在我的套接字上使用 SO_REUSEADDR 和 SO_REUSEPORT 选项。

如果您有同样的问题,请按照以下步骤操作:

//get file descriptor
int fd = socket.socket(/*options*/);

// set the options
int _enable = 1;
if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &_enable, sizeof(_enable)) < 0) {
    /*Do stuff */
}
if(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &_enable, sizeof(_enable)) < 0) {
    /* Do stuff */
}
bind(fd, /*blah blah*/);

注意 SO_REUSEPORT 和 SO_REUSEADDR 选项需要从第一个套接字开始设置

不要尝试做类似的事情:

int sock = socket.socket(/*blah blah*/);
int bound = bind(sock, /*blah blah*/);
if(bound < 0 && errno == EADDRINUSE) {
    int _enable = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &_enable, sizeof(_enable));
    setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &_enable, sizeof(_enable));
    bind(sock, /*blah blah*/);
}

【讨论】:

  • 另请注意,套接字再次可用之前的延迟是有充分理由的。如果来自最后一个连接的任何数据包延迟发送,通过 Pluto 或类似的东西路由,您可能会破坏流。谨慎禁用延迟。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-25
相关资源
最近更新 更多