繁体   English   中英

管理客户端在C套接字中突然断开连接的好方法

[英]A good way to manage client abruptly disconnecting in C sockets

在我目前正在小组中进行的一个项目中,我们必须从头开始使用套接字(Linux)构建纸牌游戏。 我们还必须建立一个聊天室,每个玩家都可以使用。

到现在为止还挺好。 聊天是通过三个独立的线程实现的,一个线程接收传入的连接(最多50个)并将其存储在客户端列表中,一个线程不断等待来自所有已连接客户端的消息,而一个线程则在每次客户端发送消息时创建,将该消息发送到客户端列表中的所有客户端。 所有这些工作正常,除非单个客户端断开连接。

当客户端断开连接时,我设法使服务器保持活动状态(使用SIGPIPE的sig处理程序),但是现在,当客户端断开连接时,我一直收到错误错误文件描述符。 但这不是唯一的问题,因为服务器不断接收空消息并将其发送给其余的客户端,从而在数毫秒内有效地将整个聊天内容淹没了整个闲聊。

我相信,如果我可以在服务器端解决问题,那么客户端就不会有任何问题。

所以我的问题是:在我的情况下,什么是管理Bad文件描述符的正确方法(或任何方法)。 我已经尝试过关闭套接字FD并将客户端列表中的值设置为-1,但这创建了更多问题,并且没有解决最初的问题。

如果需要,这里是代码。 (对于聊天而言)最重要的功能是客户端上的receive_thread,chat_thread,receive_string,send_string和connect_to_chat。

这是客户:

//includes

const int PORT = 2477;
const int CHAT_PORT = 2478;
#define DEBUG

//error()

// Sets up the connection to the server.

//connect_to_server()

int connect_to_chat(char * hostname)
{

#ifdef DEBUG
    printf("[DEBUG] Initiating connection to chat server.\n");
#endif 


    struct sockaddr_in serv_addr;
    struct hostent *server;

    // Get a socket.
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    if (sockfd < 0)
        error("Error opening socket for server.");

    // Get the address of the server.
    server = gethostbyname(hostname);

    if (server == NULL) {
        fprintf(stderr, "ERROR, no such host\n");
        exit(0);
    }

    // Zero out memory for server info.
    memset(&serv_addr, 0, sizeof (serv_addr));

    // Set up the server info.
    serv_addr.sin_family = AF_INET;
    memmove(server->h_addr, &serv_addr.sin_addr.s_addr, server->h_length);
    serv_addr.sin_port = htons(CHAT_PORT);

    // Make the connection.
    if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0)
        error("Error connecting to chat server");

#ifdef DEBUG
    printf("[DEBUG] Connected to server.\n");
#endif 

    return sockfd;
}

//-------------------------------- Messages ------------------------------------

// Bunch of send/recv functions that are not important to chat

int send_string(int sockfd, std::string myString)
{
#ifdef DEBUG
    printf("[DEBUG] Sending string: %s.\n", myString.c_str());
#endif 

    //send size
    uint32_t stringLen = myString.size();
    uint32_t sendLen = htonl(stringLen);
    int n = send(sockfd, &sendLen, sizeof (uint32_t), 0);
    if (n < 0) {
        error("Error sending message (string size). Removing client from list.");
        return -1;
    }
    //send string
    n = send(sockfd, myString.c_str(), stringLen, 0);
    if (n < 0) {
        error("Error sending message (string). Removing client from list.");
        return -1;
    }
    return 0;
}

std::string receive_string(int sockfd)
{
    //get string length
    uint32_t stringLen;
    int n = recv(sockfd, &stringLen, sizeof (uint32_t), 0);
    if (n < 0) {
        perror("Error receiving message(string size).");
    }
    stringLen = ntohl(stringLen);
    std::vector<uint8_t> buffer;
    buffer.resize(stringLen, 0x00);

    //get string
    n = recv(sockfd, &(buffer[0]), stringLen, 0);
    if (n < 0) {
        perror("Error receiving message(string).");
    }


    std::string returnString;
    returnString.assign(reinterpret_cast<const char*> (&(buffer[0])), buffer.size()); //might be a bad idea, but it works

#ifdef DEBUG
    printf("[DEBUG] Received message: %s\n", returnString.c_str());
#endif

    return returnString;
}


//----------------------------- Printing functions------------------------------

void print_menu_guest()
{
    // some visual function
}

void print_menu_user()
{
    // some visual function
}

void print_info()
{
    std::cout << " No information available on the game yet." << std::endl;
}


//---------------------------- Account functions -------------------------------

// Not necessary for chat functions

//--------------------------- Chat thread functions ----------------------------

void reception_thread(int sockfd)
{
#ifdef DEBUG
    printf("[DEBUG] Reception thread started.\n");
#endif 
    std::string stringToPrint;
    while (1) {
        stringToPrint = receive_string(sockfd);
        std::cout << stringToPrint << std::endl;
    }
}

void chat_thread(int sockfd, char* host)
{
#ifdef DEBUG
    printf("[DEBUG] Chat thread started.\n");
#endif 
    std::string myString, myUsername, blank;

    std::cout << "Enter your username (NO SPACES): ";
    std::cin >> myUsername;
    myUsername += ": ";

    int chat_sockfd = connect_to_chat(host);
    std::thread reception_thr(reception_thread, chat_sockfd);
    reception_thr.detach();

    while (1) {
        getline(std::cin, myString);
        if (!myString.empty()) {
            if (myString != "/quit") {
                send_string(chat_sockfd, (myUsername + myString));
            }
            else {
                printf("On peut pas encore quitter :( ");
            }
        }
    }


}

//---------------------- Menu management functions -----------------------------

// Main menu function

//---------------------------- Main function -----------------------------------

int main(int argc, char** argv)
{

    /* Make sure host and port are specified. */
    if (true) {

        char* hostname = "localhost";

        /* Connect to the server. */
        int sockfd = connect_to_server(hostname);


#ifdef DEBUG
        printf("[DEBUG] Client ID: Not yet implemented. ");
#endif

        login_prompt(sockfd);
        user_menu_loop(sockfd);
    }

    return 0;
}

这是服务器:它最重要的功能(用于聊天)是setup_user_fetcher,message_receiver,send_string_to_all,receive_string,send_string,get_chat_user,setup_chat_listener。

// Bunch of includes

const int PORT = 2477;
const int CHAT_PORT = 2478;
const int BACKLOG = 10;
const int MAX_CLIENTS = 20;

int clients_list[50] = {-1};

#define DEBUG

void error(const char *msg)
{
    perror(msg);
}

/* Catch Signal Handler functio */
void signal_callback_handler(int signum){

        printf("Caught signal SIGPIPE %d\n",signum);
}



//-------------------------- Server set-up functions ---------------------------

// Not necessary for chat

//--------------------------- Chat server functions  ---------------------------

int setup_chat_listener()
{
    int sockfd;
    struct sockaddr_in serv_addr;

    // Get a socket to listen on
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
        error("ERROR opening listener socket.");

    // Zero out the memory for the server information
    memset(&serv_addr, 0, sizeof (serv_addr));

    // set up the server info
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(CHAT_PORT);

    // Bind the server info to the listener socket.
    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0)
        error("Error binding listener socket.");

#ifdef DEBUG
    printf("[DEBUG] Chat listener set.\n");
#endif 

    // Return the socket number.
    return sockfd;
}

int get_chat_user(int sockfd)
{

#ifdef DEBUG
    printf("[DEBUG] Getting chat user.\n");
#endif 

    struct sockaddr_in their_addr;
    socklen_t sin_size;

    if (listen(sockfd, BACKLOG) < 0) {
        perror("Error while listening.");
        exit(EXIT_FAILURE);
    }

    sin_size = sizeof (struct sockaddr_in);

    // Mise a zero de la memoire pour le client.
    memset(&their_addr, 0, sin_size);

    int new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &sin_size);

    if (new_fd < 0)
        error("Error while accepting.");

    printf("Chat server: Connection received from: %s\n",
           inet_ntoa(their_addr.sin_addr));


    return new_fd;
}

int send_string(int sockfd, std::string myString)
{
#ifdef DEBUG
    printf("[DEBUG] Sending string to client %d.\n", sockfd);
#endif 
    uint32_t stringLen = myString.size();
    uint32_t sendLen = htonl(stringLen);
    int n = send(sockfd, &sendLen, sizeof (uint32_t), 0);
    if (n < 0) {
        error("Error sending message (string size). Removing client from list.");
        return -1;
    }
    //send string
    n = send(sockfd, myString.c_str(), stringLen, 0);
    if (n < 0) {
        error("Error sending message (string). Removing client from list.");
        return -1;
    }
    return 0;
}

std::string receive_string(int sockfd)
{
#ifdef DEBUG
    printf("[DEBUG] Receiving string.\n");
    printf("Current chat user sockfd: %d\n", sockfd);
#endif 
    uint32_t stringLen;
    int n = recv(sockfd, &stringLen, sizeof (uint32_t), 0);
    #ifdef DEBUG
    printf("[DEBUG] String size received: %d.\n", stringLen);
#endif 
    if (n < 0) {
        perror("Error receiving message(string size).");
    }
    stringLen = ntohl(stringLen);
    std::vector<uint8_t> buffer;
    buffer.resize(stringLen, 0x00);

    //get string
    n = recv(sockfd, &(buffer[0]), stringLen, 0);
    if (n < 0) {
        perror("Error receiving message(string).");
        close(sockfd);
    }


    std::string returnString;
    returnString.assign(reinterpret_cast<const char*> (&(buffer[0])), buffer.size()); //might be a bad idea, but it works

#ifdef DEBUG
    printf("[DEBUG] Received message: %s\n", returnString.c_str());
#endif

    return returnString;
}

void send_string_to_all(std::string myString)
{
#ifdef DEBUG
    printf("[DEBUG] Sending string to all clients.\n");
#endif 
    int n;
    for (int i = 0; i < 50; ++i) {
        if (clients_list[i] != -1) {
            n = send_string(clients_list[i], myString);
            if (n < 0) {
                close(clients_list[i]);
                clients_list[i] = -1;
            }
        }
    }
}

void message_receiver(int sockfd)
{
#ifdef DEBUG
    printf("[DEBUG] Setting up message receiver.\n");
    printf("Current chat user sockfd: %d", sockfd);
#endif 
    std::string message;
    int n;
    while (1) {
        message = receive_string(sockfd);
        std::thread t1(send_string_to_all, message);
        t1.detach();
    }
}




//------------------------------------------------------------------------------

// Bunch of send/recv functions, not necessary to chat


//----------------------------Account Functions---------------------------------

// Not necessary to chat

//------------------------------------------------------------------------------

// Main menu function

void setup_user_fetcher(int lis_chat_sockfd)
{
#ifdef DEBUG
    printf("[DEBUG] Gotta catch'em all.\n");
#endif 
    while (1) {
        int chat_user_sockfd = get_chat_user(lis_chat_sockfd);
        for (int i = 0; i < 50; ++i)
            if (clients_list[i] == -1) {
                clients_list[i] = chat_user_sockfd;
                break;
            }
        std::thread message_receiver_thread(message_receiver, chat_user_sockfd);
        message_receiver_thread.detach();
    }
}

int main(int argc, char** argv)
{
    signal(SIGPIPE, signal_callback_handler);

    int lis_sockfd = setup_listener();
    int lis_chat_sockfd = setup_chat_listener();

    std::thread chat_thread(setup_user_fetcher, lis_chat_sockfd);
    chat_thread.detach();
    while (1) {
        int user_sockfd = get_user(lis_sockfd);

        int* user_sockfd_ptr = (int*) malloc(sizeof (int));
        memset(user_sockfd_ptr, 0, sizeof (int));

        user_sockfd_ptr[0] = user_sockfd;

#ifdef DEBUG
        printf("[DEBUG] Starting main menu...\n");
#endif

        pthread_t thread;
        int result = pthread_create(&thread, NULL, main_menu,
                                    (void *) user_sockfd_ptr);
        if (result) {
            printf("Thread creation failed with return code %d\n", result);
            exit(-1);
        }

#ifdef DEBUG
        printf("[DEBUG] New main menu thread started.\n");
#endif
    }

    close(lis_sockfd);
    pthread_exit(NULL);

    return 0;
}

如果希望重现该错误,则可以使用以下几行代码进行编译

g++ client.cpp -o client -std=c++14 -pthread
g++ server.cpp -o server -std=c++14 -pthread

并在没有任何参数的情况下运行两者。 客户端设置为在“ localhost”上连接。

如果有人可以帮助我,我将非常高兴。

我建议摆脱SIGPIPE信号本身。

signal(SIGPIPE, SIG_IGN);

现在,在终止的套接字上的write()s将仅返回-1。 它应该比异步信号更容易处理。

如果出于其他原因需要SIGPIPE ,请使用MSG_NOSIGNAL选项将write()替换为sendto() 有关更多信息,请参见sendto(2)手册页。

你有UB。 如果读取的字节数为0 ,则&(buffer[0])将失败(我相信如果客户端断开连接,该错误将发生)。 您应该测试0并在构建字符串之前尽早返回。

同样,您在发现错误后不会返回,因此在出现错误的情况下会根据错误的数据构建字符串。

也许更像是:

std::string receive_string(int sockfd)
{
    uint32_t stringLen;
    int n = recv(sockfd, &stringLen, sizeof (uint32_t), 0);

    if (n < 0) {
        close(sockfd);
        // exit early
        throw std::runtime_error("Error receiving message(string size): "
            + std::string(std::strerror(errno)));
    }

    // test for zero
    if(!n)
        return {}; // empty string

    stringLen = ntohl(stringLen);
    std::vector<uint8_t> buffer(stringLen);
    // buffer.resize(stringLen, 0x00);

    //get string
    n = recv(sockfd, &(buffer[0]), stringLen, 0);
    if (n < 0) {
        close(sockfd);
        // exit early
        throw std::runtime_error("Error receiving message(string): "
            + std::string(std::strerror(errno)));
   }

    // only build string if no errors
    return {buffer.begin(), buffer.begin() + n};
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM