基于Sun RPC的聊天系统离线用户消息存储实现方法问询
Hey there! Let's tackle how to implement offline message storage for your Sun RPC-based chat system on Ubuntu. Here's a practical, step-by-step breakdown aligned with your workflow:
You'll need to add structures and RPC calls to handle offline messages. Update your .x file (e.g., chat.x) with these additions:
/* chat.x */ struct Message { string sender<>; string receiver<>; string content<>; unsigned int timestamp; // Track when the message was sent }; // RPC call to store a message for an offline user bool store_offline_msg(Message msg); // RPC call to fetch all offline messages when a user logs in struct MessageList { unsigned int count; Message messages<>; }; MessageList fetch_offline_msgs(string username); // Optional: Add a call to check if a user is online bool is_user_online(string username);
Run this command in your Ubuntu terminal to generate the base RPC code:
rpcgen chat.x
This will create chat.h, chat_clnt.c, chat_svc.c, and chat_xdr.c — we'll modify the server-side chat_svc.c to implement the offline logic.
You have two main options for storage: in-memory (simple, but loses data on server restart) or persistent (e.g., SQLite, for durability). Let's cover both.
Option 1: In-Memory Storage (Fast, Simple)
Use a thread-safe hash table (we'll use GLib's GHashTable for this) to map usernames to their message lists.
Setup Dependencies
First, install GLib on Ubuntu:
sudo apt-get install libglib2.0-dev
Server-Side Code Modifications
Add this to chat_svc.c:
#include <pthread.h> #include <glib.h> #include <time.h> #include <string.h> #include <stdlib.h> // Thread-safe hash table: key = username, value = GList of Message* static GHashTable *offline_msg_store; static pthread_mutex_t msg_mutex = PTHREAD_MUTEX_INITIALIZER; // Initialize storage when the server starts void init_offline_storage() { offline_msg_store = g_hash_table_new(g_str_hash, g_str_equal); } // Helper to free Message structs void free_message(Message *msg) { free(msg->sender); free(msg->receiver); free(msg->content); free(msg); } // Implement store_offline_msg RPC bool *store_offline_msg_1_svc(Message *msg, struct svc_req *rqstp) { static bool success = FALSE; pthread_mutex_lock(&msg_mutex); // Create or fetch the receiver's message list GList *msg_list = g_hash_table_lookup(offline_msg_store, msg->receiver); if (!msg_list) msg_list = g_list_new(); // Copy the message (XDR data is temporary) Message *new_msg = malloc(sizeof(Message)); *new_msg = *msg; new_msg->sender = strdup(msg->sender); new_msg->receiver = strdup(msg->receiver); new_msg->content = strdup(msg->content); new_msg->timestamp = time(NULL); // Add to list and update hash table msg_list = g_list_append(msg_list, new_msg); g_hash_table_insert(offline_msg_store, strdup(msg->receiver), msg_list); success = TRUE; pthread_mutex_unlock(&msg_mutex); return &success; } // Implement fetch_offline_msgs RPC MessageList *fetch_offline_msgs_1_svc(char **username, struct svc_req *rqstp) { static MessageList result; result.count = 0; result.messages = NULL; pthread_mutex_lock(&msg_mutex); GList *msg_list = g_hash_table_lookup(offline_msg_store, *username); if (msg_list) { // Populate the result struct result.count = g_list_length(msg_list); result.messages = malloc(sizeof(Message) * result.count); int i = 0; for (GList *iter = msg_list; iter; iter = iter->next) { Message *src = (Message*)iter->data; result.messages[i] = *src; result.messages[i].sender = strdup(src->sender); result.messages[i].receiver = strdup(src->receiver); result.messages[i].content = strdup(src->content); i++; } // Clean up: remove messages from storage after fetch g_hash_table_remove(offline_msg_store, *username); g_list_free_full(msg_list, (GDestroyNotify)free_message); } pthread_mutex_unlock(&msg_mutex); return &result; }
Don't forget to call init_offline_storage() at the start of your server main function.
Option 2: Persistent Storage with SQLite (Durable)
If you want messages to survive server restarts, use SQLite.
Setup Dependencies
Install SQLite on Ubuntu:
sudo apt-get install libsqlite3-dev
Server-Side Code Modifications
Add this to chat_svc.c:
#include <sqlite3.h> #include <time.h> #include <string.h> #include <stdlib.h> sqlite3 *db; // Initialize database on server start void init_db() { int rc = sqlite3_open("chat_offline.db", &db); if (rc != SQLITE_OK) { fprintf(stderr, "Failed to open DB: %s\n", sqlite3_errmsg(db)); exit(1); } // Create messages table if it doesn't exist const char *create_table = "CREATE TABLE IF NOT EXISTS offline_msgs (" "id INTEGER PRIMARY KEY AUTOINCREMENT," "sender TEXT NOT NULL," "receiver TEXT NOT NULL," "content TEXT NOT NULL," "timestamp INTEGER NOT NULL);"; char *err_msg; rc = sqlite3_exec(db, create_table, NULL, 0, &err_msg); if (rc != SQLITE_OK) { fprintf(stderr, "SQL Error: %s\n", err_msg); sqlite3_free(err_msg); sqlite3_close(db); exit(1); } } // Callback for SQLite fetch queries static int fetch_msg_callback(void *data, int argc, char **argv, char **azColName) { MessageList *result = (MessageList*)data; result->messages = realloc(result->messages, sizeof(Message) * (result->count + 1)); Message *msg = &result->messages[result->count]; msg->sender = strdup(argv[1]); msg->receiver = strdup(argv[2]); msg->content = strdup(argv[3]); msg->timestamp = atoi(argv[4]); result->count++; return 0; } // Implement store_offline_msg RPC bool *store_offline_msg_1_svc(Message *msg, struct svc_req *rqstp) { static bool success = FALSE; char insert_sql[512]; snprintf(insert_sql, sizeof(insert_sql), "INSERT INTO offline_msgs (sender, receiver, content, timestamp) " "VALUES ('%s', '%s', '%s', %u);", msg->sender, msg->receiver, msg->content, (unsigned int)time(NULL)); char *err_msg; int rc = sqlite3_exec(db, insert_sql, NULL, 0, &err_msg); if (rc == SQLITE_OK) success = TRUE; else fprintf(stderr, "SQL Error: %s\n", err_msg); sqlite3_free(err_msg); return &success; } // Implement fetch_offline_msgs RPC MessageList *fetch_offline_msgs_1_svc(char **username, struct svc_req *rqstp) { static MessageList result; result.count = 0; result.messages = NULL; // Fetch messages char select_sql[256]; snprintf(select_sql, sizeof(select_sql), "SELECT * FROM offline_msgs WHERE receiver = '%s';", *username); char *err_msg; int rc = sqlite3_exec(db, select_sql, fetch_msg_callback, &result, &err_msg); if (rc != SQLITE_OK) fprintf(stderr, "SQL Error: %s\n", err_msg); sqlite3_free(err_msg); // Delete fetched messages char delete_sql[256]; snprintf(delete_sql, sizeof(delete_sql), "DELETE FROM offline_msgs WHERE receiver = '%s';", *username); sqlite3_exec(db, delete_sql, NULL, 0, &err_msg); sqlite3_free(err_msg); return &result; }
Call init_db() at the start of your server main function.
- When sending a message: First check if the receiver is online via the
is_user_onlineRPC. If they're offline, callstore_offline_msginstead of sending a real-time message. - When a user logs in: Immediately call
fetch_offline_msgsto retrieve and display all stored messages.
Example client code snippet for sending messages:
void send_msg(char *sender, char *receiver, char *content) { CLIENT *clnt = clnt_create("your_server_ip", CHAT_PROG, CHAT_VERS, "tcp"); if (!clnt) { clnt_pcreateerror("your_server_ip"); exit(1); } bool *is_online = is_user_online_1(&receiver, clnt); if (*is_online) { // Send real-time message (your existing logic) } else { Message msg = {sender, receiver, content, 0}; bool *stored = store_offline_msg_1(&msg, clnt); printf(stored ? "Message stored for offline user\n" : "Failed to store message\n"); } clnt_destroy(clnt); }
- For in-memory storage:
gcc chat_svc.c chat_xdr.c -o chat_server -lrpcsvc -lglib-2.0 -pthread gcc chat_clnt.c chat_xdr.c client.c -o chat_client -lrpcsvc - For SQLite storage:
gcc chat_svc.c chat_xdr.c -o chat_server -lrpcsvc -lsqlite3 gcc chat_clnt.c chat_xdr.c client.c -o chat_client -lrpcsvc - Start
rpcbindif it's not running:sudo systemctl start rpcbind - Launch the server first, then run clients.
Hope this helps you get the offline message feature up and running smoothly! If you hit snags with concurrency or XDR serialization, feel free to share more details.
内容的提问来源于stack exchange,提问作者grakatine97

