为什么我的管道在多线程客户端服务器程序中不起作用?

时间:2018-12-21 14:28:39

标签: c multithreading sockets pipe

我实现了带有线程的客户端服务器程序。服务器程序必须使用套接字从两个客户端读取100条消息,并且必须在管道上写入此数据以使第四个程序读取该消息。我成功地从套接字读取了数据,但无法在管道上写;我的“写入”系统调用不起作用:我该怎么办?

>>> text = '''
    ...     foo
    ...     my.domain1
    ...     batman.my.domain1
    ...     superman.my.domain2 foo bar wonderwoman.my.domain1
    ... '''
    >>> data = [x for x in text.split() if x.count('.') ==2 and x.endswith(('2','1'))]
    >>> data
    ['batman.my.domain1', 'superman.my.domain2', 'wonderwoman.my.domain1']

感谢关注:)

2 个答案:

答案 0 :(得分:2)

您的代码中至少有三个race conditions,其中一个线程使用数据,而另一个线程可能会修改数据。

此代码创建竞争条件:

struct message m1[DIM];
struct message m2;

void *func_thread(void *p)
{
    int nfd;
    nfd= *(int*) p;
    int n;  //for reading

    while(read(nfd,&m2,sizeof(m2))!=0) { //reading

        printf("Here is the message: %d from process %d at time %ld     %d\n",m2.x, m2.g, m2.timestamp, j);
        fflush(stdout);
        m1[j]=m2;
        j++;
    }
 pthread_exit(NULL);
}

每个线程共享相同的m1m2数据结构,当它们读入m2时会覆盖彼此的数据。他们还同时更新j,因此在任何一个线程中都无法信任其值。

此外,您也不知道实际读取了多少字节。

此代码创建另一个数据竞争:

while(i<2) {
    clilen = sizeof(cli_addr);
    newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
    if (newsockfd < 0) 
        error("ERROR on accept");


    iret1 = pthread_create(&id[i], NULL, func_thread, &newsockfd);
    if (iret1) {
        perror("pthread_create");
        return -1;
    }

    i++;

}

组合
void *func_thread(void *p)
{
    int nfd;
    nfd= *(int*) p;

并且子线程正在从主线程访问newsockfd,但是到子线程访问newsockfd时,其值可能会有所不同。

更好的方法:

struct message m1[DIM];
int j = 0
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *func_thread(void *p)
{
    // each thread needs its own struct
    struct message m2;

    // pass the socket by **value**
    int nfd = ( intptr_t ) p;

    for ( ;; )
    {
        // don't put code that needs error checks inside conditions
        // because you can't handle errors, nor in this case partial
        // read results
        ssize_t bytes_read = read( nfd, &m2, sizeof( m2 ) );
        if ( bytes_read == 0 )
        {
            break;
        }
        // really should put code here to handle a partial read()

        printf("Here is the message: %d from process %d at time %ld     %d\n",
            m2.x, m2.g, m2.timestamp, j);
        fflush(stdout);

        // another race condition if this isn't mutex'd
        pthread_mutex_lock( &mutex );

        // get a local copy of the current value of j so
        // the structure assignment can be moved outside
        // the mutex-locked critical section
        int my_j = j;
        j++;
        pthread_mutex_unlock( &mutex );

        // stay within the bounds of the array
        if ( my_j >= DIM )
        {
            break;
        }
        m1[my_j]=m2;
    }

    pthread_exit(NULL);
}

请注意,newsockfd现在是通过而不是地址传递的,因此pthread_create()调用需要为:

    iret1 = pthread_create(&id[i], NULL, func_thread, ( void * )( intptr_t ) newsockfd);

这有点hack,它依赖于您的平台能够将int之类的newsockfd值传递为void *,但是几乎可以肯定,无论您当前使用什么系统可以做到。

答案 1 :(得分:1)

我认为您是在谈论代码的这一部分(修改了空格)

    int fd[2]; //file descriptor pipe1

    pipeState = pipe(fd);  //creation first pipe
    if (pipeState < 0) {
        perror("Pipe error");
        return -1;
    }

    close(fd[0]);
    for (t=0; t<DIM; t++) {
        printf("%d\n", m1[t].x); 
    }

    data = write(fd[1], &m1, sizeof(m1));

    if (data < 0) { //if written value is not valid
        perror("Write error\n");
        return -1;
    }

    printf("Data written on pipe\n");
    close(fd[1]);
    printf("Data written on pipe\n");
    fflush(stdout);

我无法确定预期的行为。我观察到,成功创建管道后,管道的读取端立即关闭,这使管道无用。这不应引起后续write()到写端的无限期阻塞,而仅仅是因为它们应该以{{1​​}}失败。

如果要通过管道与另一个进程进行通信,则应在创建管道之后但关闭任一端之前EPIPE fork()。然后,父母和孩子各自关闭他们不打算使用的结尾的副本。