(C) 在多线程客户端和服务器中使用互斥锁
(C) Using mutex in multithreaded client and server
我需要帮助 mutex
以我想要的方式工作。我正在制作一个带有服务器和多个客户端的简单银行系统。
服务器有两个线程。一个线程监听连接。第二个线程是在客户端连接到服务器时创建的。
客户端有3个线程。有一个主线程,使其他两个。第二个线程只是从服务器接收消息并输出它们。第三个线程只接受输入并将它们发送到服务器。
我正在尝试在客户登录后在他们的会话中使用 mutex
。例如,如果一个客户使用他们的帐户登录服务器,我想用 mutex
锁定该帐户会话。因此,如果任何其他客户端尝试登录同一帐户,他们将不得不等待已经登录的客户端。
以上场景非常适合我的代码。我目前遇到的问题是多个帐户。假设 client1 登录到他们的帐户并开始做事。然后 client2 尝试登录到与 client1 不同的帐户。它不会让 client2 进入,因为 mutex
来自 client1 帐户的 lock
。
我想做到这一点,如果 client1 登录到他们的帐户,在 client1 完成之前,没有其他客户可以登录到 client1 的帐户。不过,其他客户应该能够登录到其他帐户并对其进行单独锁定。
这是我的服务器代码(lock
inside 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
。谢谢!
哪家银行? 我有点担心您的某些代码可能会转移我的钱。例如:
char *msg = malloc(10);
while(recv(sock, msg, 10, 0) > 0)
{
if(strcmp(msg, "open") == 0)
明显的安全漏洞 - 你不能保证 msg
在执行 strcmp
.
之前是空终止的
还有这一行:
write(sock, "Invalid input", 25);
完全错误。客户端应该如何解释该行发送的 11 个垃圾字符?如果您从客户端收到无效协议,只需终止连接。
哦不...
new_sock = malloc(1);
*new_sock = client_sock;
你的 client_sock 值写在我的银行余额之上,因为你应该说:new_sock = malloc(sizeof(socket_t))
说真的,如果您的代码在银行 运行 并管理客户数据,请在部署之前让尽可能多的经验丰富的开发人员和安全专家对其进行审查。
现在,回答你原来的问题。
有很多方法可以做到这一点。数据库更适合这个,但我怀疑除了套接字和帐户之间的映射之外,您还需要客户端帐户和相应互斥锁之间的映射。
我真的要走了,但我会在今晚晚些时候为您写完一个正确的答案,解决您最初的管理同时访问的问题。待续...
更新
好的,现在回答你原来的问题。
首先,让套接字线程无限阻塞依赖于另一个客户端行为的互斥体不是一个好主意。我可以编写一个 rouge 客户端不断地为同一个客户端 id 建立连接和请求交易。这将导致您的服务器不断地启动在同一互斥体上被阻塞的线程。更好的方法是考虑 "logging out" 较旧的客户端连接,以便新连接可以继续。或者,如果你想变得非常健壮,考虑允许同一个帐户的多个客户端登录的可能性——然后有一个支持线程安全事务操作的数据库(或对象模型)。
现在,如果您想为每个客户帐户设置一个互斥体,我建议您使用这种模式。首先定义这个结构:
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 实例,它就永远不会消失。这将使下面的示例更简单一些。
然后有一个全局 table 的 ClientAccount 实例。可以是数组或链表,但最好是可以增长的散列table。但无论如何,这个包含所有 ClientAccount 实例的 table 都有自己的互斥体。保持简单,它可能看起来像这样:
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;
您必须在此 table 上定义操作,但在对 table 执行某些操作之前,每个操作都将获取 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
。这样,如果锁定获取失败,您可以告诉远程客户端服务器正在为另一个客户端提供服务。
我需要帮助 mutex
以我想要的方式工作。我正在制作一个带有服务器和多个客户端的简单银行系统。
服务器有两个线程。一个线程监听连接。第二个线程是在客户端连接到服务器时创建的。
客户端有3个线程。有一个主线程,使其他两个。第二个线程只是从服务器接收消息并输出它们。第三个线程只接受输入并将它们发送到服务器。
我正在尝试在客户登录后在他们的会话中使用 mutex
。例如,如果一个客户使用他们的帐户登录服务器,我想用 mutex
锁定该帐户会话。因此,如果任何其他客户端尝试登录同一帐户,他们将不得不等待已经登录的客户端。
以上场景非常适合我的代码。我目前遇到的问题是多个帐户。假设 client1 登录到他们的帐户并开始做事。然后 client2 尝试登录到与 client1 不同的帐户。它不会让 client2 进入,因为 mutex
来自 client1 帐户的 lock
。
我想做到这一点,如果 client1 登录到他们的帐户,在 client1 完成之前,没有其他客户可以登录到 client1 的帐户。不过,其他客户应该能够登录到其他帐户并对其进行单独锁定。
这是我的服务器代码(lock
inside 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
。谢谢!
哪家银行? 我有点担心您的某些代码可能会转移我的钱。例如:
char *msg = malloc(10);
while(recv(sock, msg, 10, 0) > 0)
{
if(strcmp(msg, "open") == 0)
明显的安全漏洞 - 你不能保证 msg
在执行 strcmp
.
还有这一行:
write(sock, "Invalid input", 25);
完全错误。客户端应该如何解释该行发送的 11 个垃圾字符?如果您从客户端收到无效协议,只需终止连接。
哦不...
new_sock = malloc(1);
*new_sock = client_sock;
你的 client_sock 值写在我的银行余额之上,因为你应该说:new_sock = malloc(sizeof(socket_t))
说真的,如果您的代码在银行 运行 并管理客户数据,请在部署之前让尽可能多的经验丰富的开发人员和安全专家对其进行审查。
现在,回答你原来的问题。
有很多方法可以做到这一点。数据库更适合这个,但我怀疑除了套接字和帐户之间的映射之外,您还需要客户端帐户和相应互斥锁之间的映射。
我真的要走了,但我会在今晚晚些时候为您写完一个正确的答案,解决您最初的管理同时访问的问题。待续...
更新
好的,现在回答你原来的问题。
首先,让套接字线程无限阻塞依赖于另一个客户端行为的互斥体不是一个好主意。我可以编写一个 rouge 客户端不断地为同一个客户端 id 建立连接和请求交易。这将导致您的服务器不断地启动在同一互斥体上被阻塞的线程。更好的方法是考虑 "logging out" 较旧的客户端连接,以便新连接可以继续。或者,如果你想变得非常健壮,考虑允许同一个帐户的多个客户端登录的可能性——然后有一个支持线程安全事务操作的数据库(或对象模型)。
现在,如果您想为每个客户帐户设置一个互斥体,我建议您使用这种模式。首先定义这个结构:
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 实例,它就永远不会消失。这将使下面的示例更简单一些。
然后有一个全局 table 的 ClientAccount 实例。可以是数组或链表,但最好是可以增长的散列table。但无论如何,这个包含所有 ClientAccount 实例的 table 都有自己的互斥体。保持简单,它可能看起来像这样:
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;
您必须在此 table 上定义操作,但在对 table 执行某些操作之前,每个操作都将获取 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
。这样,如果锁定获取失败,您可以告诉远程客户端服务器正在为另一个客户端提供服务。