代码之家  ›  专栏  ›  技术社区  ›  Kyu96

C++套接字客户端断开连接

  •  1
  • Kyu96  · 技术社区  · 7 年前

    出于学习目的,我正在制作自己的TCP套接字类。 该类用于处理多个客户端。每个客户端都存储在 vector . 我遇到了在向量断开连接时从向量中正确删除客户端的问题。 如何在断开与的连接时正确删除客户端 矢量 我如何相应地处理传入的数据?(参见else分支)。 当前控制台收到垃圾邮件 std::cout 断开连接时的else情况。

    bool socks::start() {
        if (listen(this->master_socket, this->backlog) !=0){
            std::cerr << "Failed to start listening." << std::endl;
            return false;
        }
    
        std::cout << "Listening for connections on port " << this->listening_port << std::endl;
    
        int max_sd;
        addrlen = sizeof(address);
    
        while (true) {
    
            //clear the socket set
            FD_ZERO( & readfds);
    
            //add master socket to set
            FD_SET(master_socket, & readfds);
            max_sd = master_socket;
    
            // Add child sockets to set
            for (int i = 0; i < this->clients.size();
            i++){
    
                //socket descriptor
                int sd = clients[i];
    
                // If valid socket descriptor then add to read list
                if (sd > 0)
                    FD_SET(sd, & readfds);
    
                //highest file descriptor number, need it for the select function
                if (sd > max_sd)
                    max_sd = sd;
            }
    
            // Wait indefinitely for an activity on one of the sockets
            int activity = select(max_sd + 1, & readfds, NULL, NULL, NULL);
            if ((activity < 0) && (errno != EINTR)) {
                std::cerr << "select() failed" << std::endl;
                return false;
            }
    
            // Handle incoming connections
            if (FD_ISSET(master_socket, & readfds)){
                if ((new_socket = accept(master_socket, (struct sockaddr *) & address,(socklen_t *) & addrlen)) <0){
                    std::cerr << "Failed to accept incoming connection." << std::endl;
                    return false;
                }
    
                // Information about the new connection
                std::cout << "New connection : "
                    << "[SOCKET_FD : " << new_socket
                    << " , IP : " << inet_ntoa(address.sin_addr)
                    << " , PORT : " << ntohs(address.sin_port)
                    << "]" << std::endl;
                // Add connection to vector
                this->clients.push_back(new_socket);
            }
            // Hande client disconnections / incoming data?
            else{
                std::cout << "Disconnect??? Or what happens here?" << std::endl;
            }
        }
    }
    

    编辑:我将此添加到else案例中:

    else {
        for (int j = 0; j < this->clients.size(); ++j) {
            if (this->clients.at(j) == -1) {
                continue; // eventually vector.erase() ?
            }
            if (FD_ISSET(this->clients.at(j), &this->readfds)) {
                char buf[256];
                ssize_t rc = recv(this->clients.at(j), buf, 256, 0);
                if (rc == 0) {
                    std::cout << "Client disconnected! [SOCKET_FD: "
                        << this->clients.at(j) << "]"
                        << std::endl;
                    close(this->clients.at(j));
                    this->clients.erase(this->clients.begin() + j);
                } else {
                    std::cout << "Client " << this->clients.at(j)
                        << " sent: " << buf << std::endl;
                }
            }
        }
    }
    
    1 回复  |  直到 7 年前
        1
  •  3
  •   omahena Remy Lebeau    6 年前

    你的 select() 调用仅要求可读套接字,因此退出时它将修改 readfds 删除所有不可读的套接字。因此,您只需遍历 clients 列表调用 FD_ISSET() 在每个插座上,就像您使用 master_socket 。你不应该在 else 阻止,因为侦听套接字可能正在接收新的入站客户端,而已建立的客户端也在接收数据。

    一旦确定给定的客户端是否可读,就可以 recv() 来自该客户端的数据,如果 recv 调用返回-1(错误)或0(对等端正常断开), close() 并将其从 客户 列表否则,根据需要对数据进行操作。

    其他需要考虑的事项:

    1. 你的 客户 列表中不应包含值为-1的项。如果是这样,那么您的代码就有更大的问题需要解决。

    2. 不使用 clients.at() 在您的循环中,这只是浪费了开销。使用列表的 operator[] 相反

    3. 如果要修改 客户 循环时列出,不要递增 j 在每次循环迭代中,或者在每次擦除客户机时跳过客户机。否则,请使用迭代器而不是索引,如 erase() 将迭代器返回给列表中的下一个元素。无论如何,请考虑使用迭代器,因为您要删除的是迭代器,而不是索引。

    4. 您没有处理以下情况: recv() 错误时可能返回-1。你需要 关闭() 并删除失败的客户端,而不仅仅是断开连接的客户端。

    5. 你在假设 recv() 返回以null结尾的数据,即使发送方实际发送以null结尾的数据,也不能保证返回。TCP是一种流式传输,任何给定的读取返回的字节数都可能少于请求的字节数。您必须注意 recv() 要知道实际接收了多少字节,否则可能会超出缓冲区的界限。

    请尝试以下操作:

    bool socks::start() {
        if (listen(master_socket, backlog) < 0) {
            std::cerr << "Failed to start listening." << std::endl;
            return false;
        }
    
        std::cout << "Listening for connections on port " << listening_port << std::endl;
    
        fd_set readfds;
        char buf[256];
    
        while (true) {
    
            //clear the socket set
            FD_ZERO(&readfds);
    
            //add master socket to set
            FD_SET(master_socket, &readfds);
            int max_sd = master_socket;
    
            // Add child sockets to set
            for (size_t i = 0; i < clients.size(); ++i) {
                //socket descriptor
                int sd = clients[i];    
                FD_SET(sd, &readfds);
    
                //highest file descriptor number, need it for the select function
                if (sd > max_sd)
                    max_sd = sd;
            }
    
            // Wait indefinitely for an activity on one of the sockets
            int activity = select(max_sd + 1, &readfds, NULL, NULL, NULL);
            if (activity < 0) {
                if (errno == EINTR) continue;
                std::cerr << "select() failed" << std::endl;
                return false;
            }
    
            // Handle incoming connections
            if (FD_ISSET(master_socket, &readfds)) {
                sockaddr_in address;
                socklen_t addrlen = sizeof(address);
                int new_socket = accept(master_socket, (sockaddr *) &address, &addrlen);
                if (new_socket < 0) {
                    std::cerr << "Failed to accept incoming connection." << std::endl;
                    return false;
                }
    
                // Information about the new connection
                std::cout << "New connection : "
                          << "[SOCKET_FD : " << new_socket
                          << " , IP : " << inet_ntoa(address.sin_addr)
                          << " , PORT : " << ntohs(address.sin_port)
                          << "]" << std::endl;
    
                // Add connection to vector
                clients.push_back(new_socket);    
            }
    
            // Handle client disconnections / incoming data?
            size_t j = 0;
            while (j < clients.size()) {
                int sd = clients[j];
                if (FD_ISSET(sd, &readfds)) {
                    ssize_t rc = recv(sd, buf, sizeof(buf), 0);
                    if (rc <= 0) {
                        std::cout << "Client " << (rc < 0) ? "read error" : "disconnected" << "! [SOCKET_FD: " << sd << "]" << std::endl;
                        close(sd);
                        clients.erase(clients.begin() + j);
                        continue;
                    }
                    std::cout << "Client " << sd << " sent: ";
                    std::cout.write(buf, rc);
                    std::cout << std::endl;
                }
                ++j;
            }
        }
    
        return true;
    }
    

    请注意 选择() 具有一次可以处理的最大套接字数。如果你的客户数量超过 选择() 如果无法处理,则必须将列表拆分为多个调用 选择() (可能在工作线程中调用它们以进行并行处理),或切换到 (e)poll() 而是:

    bool socks::start() {
        if (listen(master_socket, backlog) < 0) {
            std::cerr << "Failed to start listening." << std::endl;
            return false;
        }
    
        std::cout << "Listening for connections on port " << listening_port << std::endl;
    
        std::vector<pollfd> readfds;
        char buf[256];
        pollfd pfd;
    
        //add master socket to set
        pfd.fd = master_socket;
        pfd.events = POLLIN;
        pfd.revents = 0;
        readfds.push_back(pfd);
    
        while (true) {
    
            // Wait indefinitely for an activity on one of the sockets
            int activity = poll(&readfds[0], readfds.size(), -1);
            if (activity < 0) {
                if (errno == EINTR) continue;
                std::cerr << "poll() failed" << std::endl;
                return false;
            }
    
            // Handle incoming connections, client disconnections, and incoming data
            size_t j = 0;
            while (j < readfds.size()) {
                if (readfds[j].revents == 0) {
                    ++j;
                    continue;
                }
    
                int sd = readfds[j].fd;
    
                if (readfds[j].revents & POLLIN) {
                    if (sd == master_socket) {
                        sockaddr_in address;
                        socklen_t addrlen = sizeof(address);
                        int new_socket = accept(master_socket, (struct sockaddr *) &address, &addrlen);
                        if (new_socket < 0) {
                            std::cerr << "Failed to accept incoming connection." << std::endl;
                            return false;
                        }
    
                        // Information about the new connection
                        std::cout << "New connection : "
                                  << "[SOCKET_FD : " << new_socket
                                  << " , IP : " << inet_ntoa(address.sin_addr)
                                  << " , PORT : " << ntohs(address.sin_port)
                                  << "]" << std::endl;
    
                        // Add connection to vectors
    
                        clients.push_back(new_socket);    
    
                        pfd.fd = new_socket;
                        pfd.events = POLLIN | POLLRDHUP;
                        pfd.revents = 0;
                        readfds.push_back(pfd);
                    }
                    else {
                        ssize_t rc = recv(sd, buf, sizeof(buf), 0);
                        if (rc > 0) {
                            std::cout << "Client " << sd << " sent: ";
                            std::cout.write(buf, rc);
                            std::cout << std::endl;
                        }
                        else if (rc == 0) {
                            readfds[j].revents |= POLLHUP;
                        } else {
                            readfds[j].revents |= POLLERR;
                        }
                    }
                }
    
                if (readfds[j].revents != POLLIN) {
                    if (sd == master_socket) {
                        ...
                    }
                    else {
                        std::cout << "Client " << (readfds[j].revents & POLLERR) ? "read error" : "disconnected" << "! [SOCKET_FD: " << sd << "]" << std::endl;
                        close(sd);
                        clients.erase(std::find(clients.begin(), clients.end(), sd));
                        readfds.erase(readfds.begin() + j);
                        continue;
                    }
                }
    
                ++j;
            }
        }
    
        return true;
    }