如何在线程之间共享变量? [英] How to share variables among threads?
问题描述
我正在开发一个服务器-客户端项目,该项目允许服务器交换消息与客户单独.但是,我必须修改服务器,以便在服务器发送消息时,将其发送给所有连接的客户端.
I am working on a server-client project which allows the server to exchange messages with a client individually. However, I have to modify the server so that when the server sends a message, it is sent to all connected clients.
我知道这涉及在线程之间共享变量,但我对如何去做这件事感到困惑?
I know this involves sharing variables among threads but I am confused on how to go about do this?
任何提示/指导将不胜感激!
Any tips/guidence will be appreciated!
服务器代码:
#include<stdio.h>
#include<string.h> //strlen
#include<stdlib.h> //strlen
#include<sys/socket.h>
#include<arpa/inet.h> //inet_addr
#include<unistd.h> //write
#include<pthread.h> //for threading , link with lpthread
#define MAX 80
#define PORT 6543
#define SA struct sockaddr
// Function designed for chat between client and server.
void join( pthread_t *thread_id)
{
if(!pthread_join( *thread_id , NULL))
printf("thread %ld complted\n",*thread_id);
pthread_exit(0);//to exit the current thread
}
void func(int *sockfd)
{
char buff[MAX];
int n;
// infinite loop for chat
for (;;) {
bzero(buff, MAX);
// read the message from client and copy it in buffer
read(*sockfd, buff, sizeof(buff));
// print buffer which contains the client contents
printf("From client: %s\t To client : ", buff);
bzero(buff, MAX);
n = 0;
// copy server message in the buffer
while ((buff[n++] = getchar()) != '\n');
// and send that buffer to client
write(*sockfd, buff, sizeof(buff));
// if msg contains "Exit" then server exit and chat ended.
if (strncmp("exit", buff, 4) == 0) {
printf("Server Exit...\n");
break;
}
}
}
// Driver function
int main()
{
int sockfd, connfd, len;
struct sockaddr_in servaddr, cli;
pthread_t thread_id,jointhread_id;
// socket create and verification
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket creation failed...\n");
exit(0);
}
else
printf("Socket successfully created..\n");
bzero(&servaddr, sizeof(servaddr));
// assign IP, PORT
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(PORT);
// Binding newly created socket to given IP and verification
if ((bind(sockfd, (SA*)&servaddr, sizeof(servaddr))) != 0) {
printf("socket bind failed...\n");
exit(0);
}
else
printf("Socket successfully binded..\n");
// Now server is ready to listen and verification
if ((listen(sockfd, 5)) != 0) {
printf("Listen failed...\n");
exit(0);
}
else
printf("Server listening..\n");
len = sizeof(cli);
// Accept the data packet from client and verification
while(connfd = accept(sockfd, (SA*)&cli, &len))
{
if (connfd < 0) {
printf("server acccept failed...\n");
exit(0);
}
else
printf("server acccept the client..%d.\n",connfd);
if( pthread_create( &thread_id , NULL , func , (void*) &connfd) < 0)
{
perror("could not create thread");
return 1;
}
if( pthread_create( &jointhread_id , NULL , join , (void*) &thread_id) < 0)
{
perror("could not create thread");
return 1;
}
}
// After chatting close the socket
close(sockfd);
}
推荐答案
不幸的是,有许多错误......
Unfortunately, there were a number of bugs ...
错误的线程函数原型
connfd
的竞争条件,如我在顶部评论中提到的(将 connfd
作为指针传递给 func
).
Race condition for connfd
as mentioned in my top comments (passing connfd
to func
as a pointer).
执行getchar
会破坏sockfd
我在下面制作了一个版本来说明这一点.
I've produced a version below that illustrates this.
但是,要真正使代码更接近于您声明的目的所需的代码,需要进行大量的重构.下面还有第二个版本说明了我对此的看法.
But, to actually get the code closer to what is required for your stated purpose, required a fair amount of refactoring. There's a second version further down that illustrates my take on that.
这是一个带注释的版本,显示了错误和一些修复[主要是让它干净地编译所需的内容].
Here's an annotated version that shows the bugs and some fixes [mostly what it took to get it to compile cleanly].
它用 #if ORIG
包装原始代码,用 #if FIX
包装新代码,每个地方都有关于它正在修复的错误的评论
It wraps original code with #if ORIG
and new code with #if FIX
and each place has a comment about the bug it's fixing
#include <stdio.h>
#include <string.h> // strlen
#include <stdlib.h> // strlen
#include <sys/socket.h>
#include <arpa/inet.h> // inet_addr
#include <unistd.h> // write
#include <pthread.h> // for threading , link with lpthread
#define MAX 80
#define PORT 6543
#define SA struct sockaddr
#define ORIG 0
#define FIX 1
// Function designed for chat between client and server.
// NOTE/BUG -- the main thread has to join the thread
#if ORIG
void
join(pthread_t *thread_id)
{
if (!pthread_join(*thread_id, NULL))
printf("thread %ld complted\n", *thread_id);
// to exit the current thread
pthread_exit(0);
}
#endif
// NOTE/BUG: this is the _wrong_ signature for a thread function
#if ORIG
void
func(int *sockfd)
#else
void *
func(void *ptr)
#endif
{
#if FIX
int sockfd = (long) ptr;
#endif
char buff[MAX];
int n;
// infinite loop for chat
for (;;) {
bzero(buff, MAX);
// read the message from client and copy it in buffer
// NOTE/BUG: this has a race condition
// NOTE/BUG: we need the actual length
#if ORIG
read(*sockfd, buff, sizeof(buff));
#else
int rlen = read(sockfd, buff, sizeof(buff));
#endif
// print buffer which contains the client contents
printf("From client: %s\t To client : ", buff);
bzero(buff, MAX);
// copy server message in the buffer
// NOTE/BUG: this is destroying the data that was
#if ORIG
n = 0;
while ((buff[n++] = getchar()) != '\n');
#endif
// and send that buffer to client
#if ORIG
write(*sockfd, buff, sizeof(buff));
#else
write(sockfd, buff, rlen);
#endif
// if msg contains "Exit" then server exit and chat ended.
if (strncmp("exit", buff, 4) == 0) {
printf("Server Exit...\n");
break;
}
}
// NOTE/BUG: we must return the error code
#if FIX
return (void *) 0;
#endif
}
// Driver function
int
main()
{
int sockfd, connfd, len;
struct sockaddr_in servaddr, cli;
pthread_t thread_id, jointhread_id;
// socket create and verification
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket creation failed...\n");
exit(0);
}
else
printf("Socket successfully created..\n");
// assign IP, PORT
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(PORT);
// Binding newly created socket to given IP and verification
if ((bind(sockfd, (SA *) & servaddr, sizeof(servaddr))) != 0) {
printf("socket bind failed...\n");
exit(0);
}
else
printf("Socket successfully binded..\n");
// Now server is ready to listen and verification
if ((listen(sockfd, 5)) != 0) {
printf("Listen failed...\n");
exit(0);
}
else
printf("Server listening..\n");
// Accept the data packet from client and verification
len = sizeof(cli);
// NOTE/BUG: connfd can be zero for a valid connection
#if ORIG
while (connfd = accept(sockfd, (SA *) &cli, &len)) {
#else
while (1) {
connfd = accept(sockfd, (SA *) &cli, &len);
#endif
if (connfd < 0) {
printf("server acccept failed...\n");
exit(0);
}
else
printf("server acccept the client..%d.\n", connfd);
#if ORIG
if (pthread_create(&thread_id, NULL, func, (void *) &connfd) < 0) {
perror("could not create thread");
return 1;
}
#else
if (pthread_create(&thread_id, NULL, func, (void *) ((long) connfd)) < 0) {
perror("could not create thread");
return 1;
}
#endif
// NOTE/BUG -- creating a separate thread just to join the above thread does
// not help
#if ORIG
if (pthread_create(&jointhread_id, NULL, join, (void *) &thread_id) < 0) {
perror("could not create thread");
return 1;
}
#endif
}
// After chatting close the socket
close(sockfd);
return 0;
}
这是一个重构版本,它实现了您想要的多客户端消息回显.
Here's a refactored version that implements the multiple client message echo that you wanted.
它使用每线程任务块来控制事物.每个客户端收到消息后,会将其发送给所有其他客户端.
It uses a per-thread task block to control things. Each client, when it receives a message, sends it all other clients.
它还使用互斥锁进行一些线程间锁定.
It also does a bit of interthread locking using mutexes.
让一个给定的客户端线程完成对其他客户端的所有回显只是一种的方法.还有其他的(例如,所有客户端线程都将消息排队到主线程/主线程并且它进行回显)
Having a given client thread do all the echo to the other clients is but one way to do this. There are others (e.g. all client threads queue the message to the master/main thread and it does the echo)
这更接近,但您将在 func
中做更多的工作来接收/发送客户端消息.
This is a lot closer but you'll have more work to do in func
to receive/send the client message.
无论如何,这是代码:
#include <stdio.h>
#include <string.h> // strlen
#include <stdlib.h> // strlen
#include <sys/socket.h>
#include <arpa/inet.h> // inet_addr
#include <unistd.h> // write
#include <pthread.h> // for threading , link with lpthread
#define MAX 80
#define PORT 6543
#define SA struct sockaddr
#define ORIG 0
#define FIX 1
enum {
TSKSTATE_IDLE, // task slot free/available
TSKSTATE_PENDING, // task is being created
TSKSTATE_RUNNING, // task is alive and running
TSKSTATE_DONE // task has completed (but not reaped)
};
typedef struct tsk tsk_t;
struct tsk {
tsk_t *tsk_next; // chain pointer
pthread_t tsk_tid; // thread id
long tsk_xid; // sequential task id
int tsk_sockfd; // client socket descriptor
int tsk_state; // current task state
pthread_mutex_t tsk_mutex; // per-thread mutex
void *tsk_rtn; // thread's return value
};
// NOTE: using an array obviates the need for a master lock if we used a
// linked list here instead -- by passing TSKMAX to listen below [in main],
// we guarantee that main can always find a free task slot when it needs one
#define TSKMAX 5
tsk_t tsklist[TSKMAX]; // active task list
#define TSKFORALL(_tsk) \
_tsk = &tsklist[0]; _tsk < &tsklist[TSKMAX]; ++_tsk
long tskxid; // sequential task id
__thread tsk_t *tskcur; // current thread's tsk block
pthread_mutex_t master_lock = PTHREAD_MUTEX_INITIALIZER;
// lockall -- lock all threads
void
lockall(void)
{
pthread_mutex_lock(&master_lock);
}
// unlockall -- lock all threads
void
unlockall(void)
{
pthread_mutex_unlock(&master_lock);
}
// tsklock -- lock single thread
void
tsklock(tsk_t *tsk)
{
pthread_mutex_lock(&tsk->tsk_mutex);
}
// tskunlock -- unlock single thread
void
tskunlock(tsk_t *tsk)
{
pthread_mutex_unlock(&tsk->tsk_mutex);
}
// tskreapall -- release all completed threads
void
tskreapall(void)
{
tsk_t *tsk;
lockall();
for (TSKFORALL(tsk)) {
tsklock(tsk);
if (tsk->tsk_state == TSKSTATE_DONE) {
pthread_join(tsk->tsk_tid,&tsk->tsk_rtn);
tsk->tsk_state = TSKSTATE_IDLE;
}
tskunlock(tsk);
}
}
// tsksendall -- send message to all other clients
void
tsksendall(char *msg,int len)
{
tsk_t *tsk;
lockall();
for (TSKFORALL(tsk)) {
if (tsk == tskcur)
continue;
tsklock(tsk);
if (tsk->tsk_state == TSKSTATE_RUNNING)
write(tsk->tsk_sockfd,msg,len);
tskunlock(tsk);
}
}
void *
func(void *ptr)
{
tskcur = ptr;
char buff[MAX];
#if ORIG
int n;
#endif
tsklock(tskcur);
tskcur->tsk_state = TSKSTATE_RUNNING;
tskunlock(tskcur);
// infinite loop for chat
// NOTE: this loop still needs work ...
for (;;) {
bzero(buff, MAX);
// read the message from client and copy it in buffer
int rlen = read(tskcur->tsk_sockfd, buff, sizeof(buff));
// print buffer which contains the client contents
printf("From client: %s\t To client : ", buff);
bzero(buff, MAX);
// copy server message in the buffer
// NOTE/BUG: this is destroying the data that was
#if ORIG
n = 0;
while ((buff[n++] = getchar()) != '\n');
#endif
// and send that buffer to client
write(tskcur->tsk_sockfd, buff, rlen);
// if msg contains "Exit" then server exit and chat ended.
if (strncmp("exit", buff, 4) == 0) {
printf("Server Exit...\n");
break;
}
// echo message to all other clients
tsksendall(buff,rlen);
}
tsklock(tskcur);
tskcur->tsk_state = TSKSTATE_DONE;
close(tskcur->tsk_sockfd);
tskcur->tsk_sockfd = -1;
tskunlock(tskcur);
return (void *) 0;
}
// Driver function
int
main(void)
{
int sockfd, connfd;
socklen_t len;
struct sockaddr_in servaddr, cli;
int state;
tsk_t *tsktry;
tsk_t *tsknew;
// socket create and verification
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket creation failed...\n");
exit(0);
}
else
printf("Socket successfully created..\n");
// assign IP, PORT
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(PORT);
// Binding newly created socket to given IP and verification
if ((bind(sockfd, (SA *) & servaddr, sizeof(servaddr))) != 0) {
printf("socket bind failed...\n");
exit(0);
}
else
printf("Socket successfully binded..\n");
// Now server is ready to listen and verification
if ((listen(sockfd, TSKMAX)) != 0) {
printf("Listen failed...\n");
exit(0);
}
else
printf("Server listening..\n");
// Accept the data packet from client and verification
len = sizeof(cli);
for (TSKFORALL(tsktry)) {
pthread_mutex_init(&tsktry->tsk_mutex,NULL);
tsktry->tsk_state = TSKSTATE_IDLE;
}
while (1) {
connfd = accept(sockfd, (SA *) &cli, &len);
if (connfd < 0) {
printf("server acccept failed...\n");
exit(0);
}
else
printf("server acccept the client..%d.\n", connfd);
// reap all completed threads
tskreapall();
// find an idle slot
tsknew = NULL;
for (TSKFORALL(tsktry)) {
tsklock(tsktry);
state = tsktry->tsk_state;
if (state == TSKSTATE_IDLE) {
tsknew = tsktry;
tsknew->tsk_state = TSKSTATE_PENDING;
}
tskunlock(tsktry);
if (tsknew != NULL)
break;
}
tsknew->tsk_xid = ++tskxid;
tsknew->tsk_sockfd = connfd;
if (pthread_create(&tsknew->tsk_tid, NULL, func, tsknew) < 0) {
perror("could not create thread");
return 1;
}
}
// After chatting close the socket
close(sockfd);
return 0;
}
这篇关于如何在线程之间共享变量?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!