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

时间:2018-03-19 19:01:46

标签: c++ sockets tcp

出于学习目的,我正在创建自己的TCP Socket类。 该类旨在处理多个客户端。每个客户端都存储在vector中。我遇到了在断开连接时从向量中正确删除客户端的问题。 如何在与vector的断开连接上正确删除客户端,以及如何相应地处理传入数据? (见其他分支)。 目前,控制台因断开连接的其他情况的std::cout而被垃圾邮件发送。

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 {
    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 个答案:

答案 0 :(得分:3)

您的select()调用仅询问可读套接字,因此退出时它将修改您的readfds以删除所有不可读的套接字。因此,您只需遍历每个套接字上调用clients的{​​{1}}列表,就像使用FD_ISSET()一样。并且您不应该在master_socket块中进行该迭代,因为侦听套接字可能在已建立的客户端也在接收数据的同时接收新的入站客户端。

一旦确定给定客户端是否可读,您就可以else来自该客户端的数据,如果recv()调用返回-1(错误)或0(对等端断开连接), recv该客户端并将其从close()列表中删除。否则,根据需要对数据采取行动。

需要考虑的其他事项:

  1. 您的clients列表中绝不应包含值为-1的项目。如果是这样,那么您需要解决的代码存在更大的问题。

  2. 不要在你的循环中使用clients,只是浪费了开销。请改为使用列表clients.at()

  3. 如果要在循环时修改operator[]列表,请不要在每次循环迭代时增加clients,否则每当擦除客户端时都会跳过客户端。否则,请使用迭代器而不是索引,因为j会将迭代器返回到列表中的下一个元素。无论如何,请考虑使用迭代器,因为您正在擦除迭代器而不是索引。

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

  5. 您假设close()返回以空值终止的数据,即使发件人实际发送以空值终止的数据,也无法保证。 TCP是一种流传输,任何给定的读取都可以返回比请求的更少的字节。您必须注意recv()的返回值才能知道实际接收的字节数,否则您可能会超出缓冲区的范围。

  6. 尝试更像这样的事情:

    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; } 一次可以处理的最大套接字数。如果最终有比select()更多的客户端可以处理,则必须将列表拆分为多个select()调用(可能在工作线程中调用它们进行并行处理),或者切换到{{ 1}}代替:

    select()