如何在线程之间共享变量?

时间:2020-10-12 19:37:04

标签: c multithreading sockets pthreads shared-variable

我正在处理一个服务器-客户端项目,该项目允许服务器交换消息 与客户单独联系。但是,我必须修改服务器,以便在服务器发送消息时将其发送到所有连接的客户端。

我知道这涉及在线程之间共享变量,但是我对如何执行此操作感到困惑?

任何提示/建议将不胜感激!

服务器代码:

#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);
}

1 个答案:

答案 0 :(得分:1)

不幸的是,这里有很多错误……

错误的线程函数原型

我在最高注释中提到的connfd的种族条件(将connfd传递给func作为指针)。

执行getchar会破坏读取sockfd的消息数据

我在下面制作了一个版本来说明这一点。

但是,实际上要使代码更接近于您所声明的目的,需要进行大量的重构。进一步的第二个版本说明了我对此的看法。


这是一个带注释的版本,其中显示了错误和一些修复程序(主要是为了使其能够干净地编译而花费的时间)。

它用#if ORIG包装原始代码,并用#if FIX包装新代码,并且每个地方都对其修复的错误发表评论

#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;
}

这是重构版本,可实现您想要的多客户端消息回显。

它使用每个线程的任务块来控制事物。每个客户端收到消息后,都会将其发送给所有其他客户端。

它还使用互斥锁进行了一些线程间锁定。

具有给定的客户端线程会执行对其他客户端的所有回显,但这只是一种方法。还有其他(例如,所有客户端线程将消息排队到主线程/主线程,并且 it 执行回显)

距离更近,但是在func中您需要做更多的工作来接收/发送客户消息。

无论如何,这是代码:

#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;
}
相关问题