【问题标题】:(C) Using mutex in multithreaded client and server(C) 在多线程客户端和服务器中使用互斥锁
【发布时间】:2016-03-10 02:35:40
【问题描述】:

我需要帮助让mutex 以我想要的方式工作。我正在制作一个带有服务器和多个客户端的简单银行系统。

服务器有两个线程。一个线程监听连接。第二个线程是在客户端连接到服务器时创建的。

客户端有 3 个线程。有主线程使其他两个线程。第二个线程只是从服务器接收消息并输出它们。第三个线程只接受输入并将它们发送到服务器。

我在客户登录后尝试在客户会话中使用mutex。例如,如果一个客户使用他们的帐户登录服务器,我想用mutex 锁定该帐户会话。因此,如果任何其他客户端尝试登录同一个帐户,他们将不得不等待已经登录的客户端。

上述场景与我的代码完美配合。我目前遇到的问题是多个帐户。假设 client1 登录到他们的帐户并开始做事。然后 client2 尝试登录到不同于 client1 的帐户。由于 client1 帐户上 mutex 中的 lock,它不会让 client2 进入。

我想这样做,如果 client1 登录到他们的帐户,在 client1 完成之前,其他客户都不能登录到 client1 的帐户。不过,其他客户端应该能够登录其他帐户并对其进行单独锁定。

这是我的服务器代码(lock 在 curr_acc() 中):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <unistd.h>
#include <pthread.h>

// Mutex var
pthread_mutex_t lock;

int main(int argc, char *argv[])
{
    pthread_mutex_init(&lock, NULL);
    server_listen();
    pthread_mutex_destroy(&lock);
    return 0;
}

int server_listen()
{
    int socket_desc, client_sock, c, *new_sock;
    int portno = 8888;
    struct sockaddr_in server, client;

    socket_desc = socket(AF_INET, SOCK_STREAM, 0);

    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons(portno);

    bind(socket_desc, (struct sockaddr *) &server, sizeof(server))
    listen(socket_desc, 5);
    c = sizeof(struct sockaddr_in);

    // Accept connection
    while((client_sock = accept(socket_desc, (struct sockaddr *) &client, (socklen_t *) &c)))
    {
        // Create new thread and sock
        pthread_t sniffer_thread;
        new_sock = malloc(1);
        *new_sock = client_sock;

        pthread_create(&sniffer_thread, NULL, handle_client, (void *) new_sock)
    }
}

void *handle_client(void *new_sock)
{
    // Convert void
    int sock = *((int *) new_sock);

    // Prepare to receive and send
    int read_size, index;
    char *msg = malloc(10);

    while(recv(sock, msg, 10, 0) > 0)
    {
        if(strcmp(msg, "open") == 0)
            new_acc(sock);
        else if(strcmp(msg, "start") == 0)
            curr_acc(sock);
        else
            write(sock, "Invalid input", 25);

        memset(msg, 0, sizeof(msg));
    }

    free(new_sock);
    free(msg);

    // Exit the thread
    pthread_exit(0);
}

void curr_acc(int sock)
{
    int lock_ret;

    // Get account name here and check if it's in array of accounts.
    // Then try below

    // Try and lock the thread then go inside
    lock_ret = pthread_mutex_trylock(&lock);
    if(!lock_ret)
    {
        // Show customer menu
        write(sock, "\n Welcome!", 20);
        cus_menu(sock);

        // Unlock the thread
        lock_ret = pthread_mutex_unlock(&lock);
    }
    else
    {
        printf("Cannot open account");
        write(sock, "Cannot open account. Try again or wait...", 50);
    }
}

这是我的服务器的精简版,但应该足以理解我在做什么。如果您想要更多,请告诉我。

我的客户端向服务器发送一个char 指针以检查输入。如果您需要查看客户端代码,也请告诉我。但如上所述,它非常简单。

任何帮助将不胜感激。我真的很想更好地理解mutex。谢谢!

【问题讨论】:

    标签: c multithreading server pthreads mutex


    【解决方案1】:

    哪家银行?我有点担心你的一些代码可能会转移我的钱。例如:

    char *msg = malloc(10);
    
    while(recv(sock, msg, 10, 0) > 0)
    {
        if(strcmp(msg, "open") == 0)
    

    明显的安全漏洞 - 在执行 strcmp 之前,您不能保证 msg 以空值终止。

    还有这一行:

    write(sock, "Invalid input", 25);
    

    完全是错误的。客户端应该如何解释该行发送的 11 个垃圾字符?如果您从客户端获得无效协议,只需终止连接即可。

    哦不...

        new_sock = malloc(1);
        *new_sock = client_sock;
    

    您的 client_sock 值写在我的银行余额之上,因为您应该说:new_sock = malloc(sizeof(socket_t))

    说真的,如果您的代码在银行中运行并管理客户数据,请在部署之前让尽可能多的经验丰富的开发人员和安全专家对其进行审核。

    现在,回答你原来的问题。

    有很多方法可以做到这一点。数据库会更适合于此,但我怀疑除了套接字和帐户之间的映射之外,您还需要客户帐户和相应互斥锁之间的映射。

    我实际上必须起飞,但我将在今晚晚些时候为您完成正确的答案,以解决您管理同时访问的原始问题。待续……

    更新

    好的,现在回答你原来的问题。

    首先,套接字线程在依赖于另一个客户端行为的互斥体上无限阻塞并不是一个好主意。我可以编写一个 rouge 客户端不断地为相同的客户端 ID 建立连接并请求事务。这将导致您的服务器不断启动在同一个互斥锁上阻塞的线程。更好的方法是考虑“注销”旧客户端连接,以便新连接可以继续。或者,如果您想真正健壮,请考虑允许同一帐户的多个客户端登录的可能性 - 然后拥有一个支持线程安全事务操作的数据库(或对象模型)。

    现在,如果您想为每个客户帐户设置一个互斥锁,我建议您采用这种模式。首先定义这个结构:

    struct ClientAccount
    {
        int account_number; // could be any type, I chose "int" for brevity
        pthread_mutex_t mutex; // the mutex that guards access to this client account  
    };
    

    在我们的简单示例中,我将声明 ClientAccount 实例始终由指针引用,并且一旦创建 ClientAccount 实例,它就永远不会消失。这将使下面的示例更简单一些。

    然后有一个 ClientAccount 实例的全局表。可以是数组或链表,但最好是可以增长的哈希表。但无论如何,这个包含所有 ClientAccount 实例的表都有它自己的互斥锁。保持简单,它可能看起来像这样:

    struct ClientAccountTable
    {
        ClientAccount* accounts[MAX_NUMBER_OF_ACCOUNTS]; // an array of pointers - not an array of instances!
        int accounts_size; // number of items in accounts
    };
    

    然后是具有全局互斥锁的全局实例;

    ClientAccountTable g_account_table;
    pthread_mutex_t g_table_mutex; // guards access to g_account_table;
    

    您必须在此表上定义操作,但每个操作都将在对表执行某些操作之前获取表的互斥锁。例如:

    ClientAccount* find_or_create_account(int account_number)
    {
        ClientAccount* account = NULL;
    
        // acquire the global lock before accessing the global table
        pthread_mutex_lock(&g_table_mutex);
    
        for (int i = 0; i < g_account_table; i++)
        {
            if (account_number == g_account_table.accounts[i].account_number)
            {
                 account = g_account_table.accounts[i];
            }
        }
    
        if (account == NULL)
        {
           // not found in the table, so create a new one
           pAccount = malloc(sizeof(ClientAccount));
           g_account_table.accounts[g_account_table.accounts_size] = account;
           g_account_table.accounts_size++;
        }
    
        // release the lock
        pthread_mutex_unlock(&g_table_mutex);
        return account;
    }
    

    然后,在客户端确定要使用哪个帐户后,服务器上的每个线程都可以执行此操作:

     void* handle_client(void* )
     {
        // socket code to read client's account number
        ClientAccount* account = find_or_create_account(account_number);
    
        // lock the mutex for this client account
        pthread_mutex_lock(&account->mutex);
    
        // not shown:  socket code and the code to do transactions for this account
    
    
        // unlock the account's mutex after this client session is done
        pthread_mutex_unlock(&account->mutex);
    
     }
    

    您也可以尝试使用pthread_mutex_trylock 代替pthread_mutex_lock。这样,如果锁获取失败,您可以告诉远程客户端服务器正在为另一个客户端提供服务。

    【讨论】:

    • 大声笑,这只是我班级的一个项目。实际上不是为银行做的哈哈。我知道那会有多不安全。我会修复您指出的所有内容并等待您的答复。谢谢!
    • @CeeC - 我在您的套接字代码中看到的另一个错误是您没有验证来自recv 的返回值。您检查以确保它不是错误,但recv 返回接收到的字节数。在 TCP 中,仅仅因为您说recv(sock, msg, 10, 0) &gt; 0 并不意味着您实际上收到了 10 个字节。 TCP 分段、IP 分段和其他因素可能会以不可预知的方式拆分 TCP 流字节。循环调用recv,直到您读取了预期的字节数。或使用MSG_WAITALL 标志。
    • 哦,这就是为什么我有时会得到奇怪的输出。谢谢,我也解决了。
    猜你喜欢
    • 2019-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-09
    • 2019-04-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多