Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing Client-Side Operations for a Cluster Chat Server

Tech Jun 7 1

A command table is defined to map available operations to thier usage syntax for user assistance.

unordered_map<string, string> AvailableCommands{
    {"help", "Show all available commands. Format: help"},
    {"chat", "Private messaging. Format: chat:recipient_id:message"},
    {"addfriend", "Add a friend. Format: addfriend:friend_id"},
    {"creategroup", "Create a group. Format: creategroup:group_name:group_description"},
    {"addgroup", "Join a group. Format: addgroup:group_id"},
    {"groupchat", "Group messaging. Format: groupchat:group_id:message"},
    {"loginout", "Logout. Format: loginout"},
};

After successful login, this list is displayed to guide user interaction. The following function are implemented to handle these commands.

Displaying Help Information

The displayHelpInfo function prints all supported commands and their formats.

void displayHelpInfo(int, string)
{
    cout << "--- Available Commands ---" << endl;
    for (const auto &cmd : AvailableCommands)
    {
        cout << cmd.first << " : " << cmd.second << endl;
    }
    cout << endl;
}

Initiating a Friend Request

This function constructs a JSON request to add a friend.

void requestAddFriend(int socket_fd, string input_str)
{
    int friend_uid = stoi(input_str);
    json request_data;
    request_data["type"] = ADD_FRIEND_REQ;
    request_data["uid"] = active_user.getId();
    request_data["target_id"] = friend_uid;

    string serialized_msg = request_data.dump();
    int send_result = send(socket_fd, serialized_msg.c_str(), strlen(serialized_msg.c_str()) + 1, 0);
    if (send_result == -1)
    {
        cerr << "Failed to send friend request ->" << serialized_msg << endl;
    }
}

Sending a Private Message

This function parses a friend_id:message string and sends a private chat message.

void sendPrivateMessage(int socket_fd, string command_str)
{
    size_t separator_pos = command_str.find(':');
    if (separator_pos == string::npos)
    {
        cerr << "Invalid chat command syntax." << endl;
        return;
    }
    int recipient_uid = stoi(command_str.substr(0, separator_pos));
    string chat_content = command_str.substr(separator_pos + 1);

    json msg_payload;
    msg_payload["type"] = PRIVATE_CHAT_MSG;
    msg_payload["uid"] = active_user.getId();
    msg_payload["username"] = active_user.getName();
    msg_payload["receiver_id"] = recipient_uid;
    msg_payload["content"] = chat_content;
    msg_payload["timestamp"] = fetchCurrentTimestamp();

    string serialized_data = msg_payload.dump();
    int send_result = send(socket_fd, serialized_data.c_str(), strlen(serialized_data.c_str()) + 1, 0);
    if (send_result == -1)
    {
        cerr << "Failed to send chat message ->" << serialized_data << endl;
    }
}

Creating a New Group

This function handles group creation by parsing group_name:group_description.

void createNewGroup(int socket_fd, string command_str)
{
    size_t separator_pos = command_str.find(':');
    if (separator_pos == string::npos)
    {
        cerr << "Invalid group creation command." << endl;
        return;
    }
    string new_group_name = command_str.substr(0, separator_pos);
    string new_group_desc = command_str.substr(separator_pos + 1);

    json group_request;
    group_request["type"] = CREATE_GROUP_REQ;
    group_request["uid"] = active_user.getId();
    group_request["new_group_name"] = new_group_name;
    group_request["new_group_desc"] = new_group_desc;

    string serialized_req = group_request.dump();
    int send_result = send(socket_fd, serialized_req.c_str(), strlen(serialized_req.c_str()) + 1, 0);
    if (send_result == -1)
    {
        cerr << "Failed to send group creation request ->" << serialized_req << endl;
    }
}

Joining an Existing Group

This function sends a request to join a specific group.

void joinGroup(int socket_fd, string input_str)
{
    int target_group_id = stoi(input_str);
    json join_request;
    join_request["type"] = JOIN_GROUP_REQ;
    join_request["uid"] = active_user.getId();
    join_request["target_group_id"] = target_group_id;

    string serialized_data = join_request.dump();
    int send_result = send(socket_fd, serialized_data.c_str(), strlen(serialized_data.c_str()) + 1, 0);
    if (send_result == -1)
    {
        cerr << "Failed to send join group request ->" << serialized_data << endl;
    }
}

Sending a Group Message

This function sends a message to a group by parsing group_id:message.

void sendGroupMessage(int socket_fd, string command_str)
{
    size_t separator_pos = command_str.find(':');
    if (separator_pos == string::npos)
    {
        cerr << "Invalid group chat command." << endl;
        return;
    }
    int target_group_id = stoi(command_str.substr(0, separator_pos));
    string group_msg = command_str.substr(separator_pos + 1);

    json group_msg_data;
    group_msg_data["type"] = GROUP_CHAT_MSG;
    group_msg_data["uid"] = active_user.getId();
    group_msg_data["username"] = active_user.getName();
    group_msg_data["target_group_id"] = target_group_id;
    group_msg_data["content"] = group_msg;
    group_msg_data["timestamp"] = fetchCurrentTimestamp();

    string serialized_msg = group_msg_data.dump();
    int send_result = send(socket_fd, serialized_msg.c_str(), strlen(serialized_msg.c_str()) + 1, 0);
    if (send_result == -1)
    {
        cerr << "Failed to send group message ->" << serialized_msg << endl;
    }
}

Logout Functionality

To support user logout, a new message type is added to the public header.

#ifndef PUBLIC_HEADER_H
#define PUBLIC_HEADER_H

enum MessageType {
    LOGIN_REQUEST = 1,
    LOGIN_RESPONSE,
    REGISTER_REQUEST,
    REGISTER_RESPONSE,
    PRIVATE_CHAT_MSG,
    ADD_FRIEND_REQ,
    ADD_FRIEND_RESPONSE,
    CREATE_GROUP_REQ,
    CREATE_GROUP_RESPONSE,
    JOIN_GROUP_REQ,
    JOIN_GROUP_RESPONSE,
    GROUP_CHAT_MSG,
    LOGOUT_REQUEST, // Added for logout
};

#endif

The client-side logout function sends a logout request.

void logoutUser(int socket_fd, string){
    json logout_msg;
    logout_msg["type"] = LOGOUT_REQUEST;
    logout_msg["uid"] = active_user.getId();
    string serialized_data = logout_msg.dump();
    int send_result = send(socket_fd, serialized_data.c_str(), strlen(serialized_data.c_str()) + 1, 0);
    if (send_result == -1)
    {
        cerr << "Failed to send logout message ->" << serialized_data << endl;
    } else {
        main_menu_active = false;
    }
}

The server-side handler removes the user's connection and updates their status.

void handleLogout(const TcpConnectionPtr &conn, json &req_json, Timestamp time)
{
    int user_id = req_json["uid"].get<int>();
    {
        lock_guard<mutex> conn_lock(connection_mutex);
        auto conn_entry = active_connections.find(user_id);
        if (conn_entry != active_connections.end())
        {
            active_connections.erase(conn_entry);
        }
    }
    User user_record(user_id, "", "", "offline");
    user_data_model.updateStatus(user_record);
}

The handler is bound to the message router.

_msgRouter.insert({LOGOUT_REQUEST, std::bind(&ChatServer::handleLogout, this, _1, _2, _3)});

Command Dispatcher

A command handler map dispatches user input to the appropriate function.

unordered_map<string, function<void(int, string)>> CommandDispatcher = {
    {"help", displayHelpInfo},
    {"chat", sendPrivateMessage},
    {"addfriend", requestAddFriend},
    {"creategroup", createNewGroup},
    {"addgroup", joinGroup},
    {"groupchat", sendGroupMessage},
    {"loginout", logoutUser},
};

The main menu loop processes user commands.

void runMainMenu(int socket_fd)
{
    displayHelpInfo();
    char input_buffer[1024] = {0};
    while (main_menu_active)
    {
        cin.getline(input_buffer, 1024);
        string user_cmd(input_buffer);
        string cmd_key;
        size_t delim_pos = user_cmd.find(':');
        if (delim_pos == string::npos)
        {
            cmd_key = user_cmd;
        }
        else
        {
            cmd_key = user_cmd.substr(0, delim_pos);
        }
        auto handler_iter = CommandDispatcher.find(cmd_key);
        if (handler_iter == CommandDispatcher.end())
        {
            cerr << "Unrecognized command entered!" << endl;
            continue;
        }
        string cmd_args = (delim_pos == string::npos) ? "" : user_cmd.substr(delim_pos + 1);
        handler_iter->second(socket_fd, cmd_args);
    }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.