pthread отсутствие синхронизации

У меня есть следующий код.

Этот код предназначен для TFTP-сервера, который создает ответвление или поток для каждого полученного запроса. Моя проблема в методах потока.

Например, я запрашиваю 30 файлов с сервера, он должен создать 30 потоков и передать запрошенные файлы клиенту, каждый поток будет отправлять каждый файл. Код работает отлично, если я использую pthread_join (который прокомментирован), но если у меня нет pthread_join, он правильно обслуживает некоторые файлы, но некоторые из них повреждены или пусты.

Я считаю, что это проблема синхронизации, поэтому я попытался выделить malloc кусок памяти для каждого файлового дескриптора клиента, чтобы поток мог изменять только свой собственный файловый дескриптор, но безуспешно. Следующий код, как есть, работает и служит некоторым

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <dirent.h>
#include <pthread.h>

#define BUFSIZE 8096
#define OperationMode 1

#if OperationMode
    typedef struct {
        int * fd;
        int hit;
    } THREAD_ARGS;

    void *attendFTP(void *);
#endif

int ftp(int fd, int hit);

void getFunction(int fd, char * fileName);

void putFunction(int fd, char * fileName);

char * listFilesDir(char * dirName);

void lsFunction(int fd, char * dirName);

void mgetFunction(int fd, char *dirName);

/* just checks command line arguments, setup a listening socket and block on accept waiting for clients */

int main(int argc, char **argv) {
    int i, port, pid, listenfd, socketfd, hit;
    socklen_t length;
    static struct sockaddr_in cli_addr; /* static = initialised to zeros */
    static struct sockaddr_in serv_addr; /* static = initialised to zeros */

    if (argc < 3 || argc > 3 || !strcmp(argv[1], "-?")) {
        printf("\n\nhint: ./tftps Port-Number Top-Directory\n\n""\ttftps is a small and very safe mini ftp server\n""\tExample: ./tftps 8181 ./fileDir \n\n");
        exit(0);
    }

    if (chdir(argv[2]) == -1) {
        printf("ERROR: Can't Change to directory %s\n", argv[2]);
        exit(4);
    }

    printf("LOG tftps starting %s - pid %d\n", argv[1], getpid());

    /* setup the network socket */
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        printf("ERROR system call - setup the socket\n");
    port = atoi(argv[1]);
    if (port < 0 || port > 60000)
        printf("ERROR Invalid port number (try 1->60000)\n");


    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(port);

    if (bind(listenfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
        printf("ERROR system call - bind error\n");
    if (listen(listenfd, 64) < 0)
        printf("ERROR system call - listen error\n");


    // Main LOOP
    for (hit = 1 ;; hit++) {
        length = sizeof(cli_addr);

        /* block waiting for clients */
        socketfd = accept(listenfd, (struct sockaddr *) &cli_addr, &length);
        if (socketfd < 0)
            printf("ERROR system call - accept error\n");
        else
        {
            #if OperationMode            
                pthread_t thread_id;

                THREAD_ARGS *args = malloc(sizeof(THREAD_ARGS));

                int * sockAUX = (int *) malloc(sizeof(int *));

                *sockAUX = socketfd;

                args->fd = sockAUX;
                args->hit = hit;

                if (args != NULL) {
                    if (pthread_create(&thread_id, NULL, &attendFTP, args)) {
                        perror("could not create thread");
                        return 1;
                    }
                }

                //pthread_join(thread_id,NULL);
            #else
                pid = fork();
                if(pid==0) {
                    ftp(socketfd, hit);
                } else {
                    //Temos de fechar o socketfd para que seja apenas a child a tratar dos pedidos, caso contrário iria ficar aqui pendurado
                    close(socketfd);
                    kill(pid, SIGCHLD);
                }
            #endif
        }
    }
}

#if OperationMode
    void *attendFTP(void *argp) {
        THREAD_ARGS *args = argp;

        int sock = *args->fd;

        printf("FD SOCK: %d\n\n", sock);

        ftp(sock, args->hit);

        free(args);
        //printf("Thread executou\n\n");
        pthread_exit(NULL);
        return NULL;
    }
#endif

/* this is the ftp server function */
int ftp(int fd, int hit) {
    int j, file_fd, filedesc;
    long i, ret, len;
    char * fstr;
    static char buffer[BUFSIZE + 1]; /* static so zero filled */

    printf("FD: %d\n\n", fd);

    ret = read(fd, buffer, BUFSIZE); // read FTP request

    if (ret == 0 || ret == -1) { /* read failure stop now */
        close(fd);
        return 1;
    }
    if (ret > 0 && ret < BUFSIZE) /* return code is valid chars */
        buffer[ret] = 0; /* terminate the buffer */
    else
        buffer[0] = 0;

    for (i = 0; i < ret; i++) /* remove CF and LF characters */
        if (buffer[i] == '\r' || buffer[i] == '\n')
            buffer[i] = '*';

    printf("LOG request %s - hit %d\n", buffer, hit);

    /* null terminate after the second space to ignore extra stuff */
    for (i = 4; i < BUFSIZE; i++) {
        if (buffer[i] == ' ') { /* string is "GET URL " +lots of other stuff */
            buffer[i] = 0;
            break;
        }
    }

    if (!strncmp(buffer, "get ", 4)) {
        // GET
        getFunction(fd, &buffer[5]);
    } else if (!strncmp(buffer, "ls ", 3)) {
        // LS
        lsFunction(fd,&buffer[3]);
    } else if (!strncmp(buffer, "mget ", 4)) {
        // MGET
        mgetFunction(fd, &buffer[5]);
    }

    sleep(1); /* allow socket to drain before signalling the socket is closed */
    close(fd);
    return 0;
}

void getFunction(int fd, char * fileName){
    int file_fd;
    long ret;

    printf("FD GET: %d\n\n", fd);

    static char buffer[BUFSIZE + 1]; /* static so zero filled */

    if ((file_fd = open(fileName, O_RDONLY)) == -1) { /* open the file for reading */
        printf("ERROR failed to open file %s\n", fileName);
        printf("Err: %d\n\n",errno);
        sprintf(buffer, "%s", "erro");
        write(fd,buffer,BUFSIZE);
        close(fd);
        return;
    }

    printf("GET -> LOG SEND %s \n", fileName);

    /* send file in 8KB block - last block may be smaller */
    while ((ret = read(file_fd, buffer, BUFSIZE)) > 0) {
        write(fd, buffer, ret);
    }
}

void lsFunction(int fd, char * dirName){
    printf("LS -> LOG Header %s \n", dirName);

    static char buffer[BUFSIZE + 1];

    sprintf(buffer, "%s", listFilesDir(dirName));
    write(fd,buffer,BUFSIZE);
}

void mgetFunction(int fd, char *dirName)
{
    FILE *fp;
    char path[255];

    static char buffer[BUFSIZE + 1];

    printf("MGET COUNT -> LOG Header %s \n", dirName);

    sprintf(buffer, "%s", listFilesDir(dirName));
    write(fd,buffer,BUFSIZE);
}

char * listFilesDir(char * dirName)
{
    DIR *midir;
    struct dirent* info_archivo;
    struct stat fileStat;
    char fullpath[256];
    char *files = malloc (sizeof (char) * BUFSIZE);

    if ((midir=opendir(dirName)) == NULL)
    {
        return "\nO directorio pedido não existe.\n";
    }

    while ((info_archivo = readdir(midir)) != 0)
    {
        strcpy (fullpath, dirName);
        strcat (fullpath, "/");
        strcat (fullpath, info_archivo->d_name);
        if (!stat(fullpath, &fileStat))
        {
            if(!S_ISDIR(fileStat.st_mode))
            {
                strcat (files, info_archivo->d_name);
                strcat (files, "$$");
            }
        } else {
            return "\nErro ao ler o directório.\n";
        }
    }
    closedir(midir);

    return files;
}

Это пример лога с сервера

LOG tftps начиная с 8181 - pid 15416 FD SOCK: 4

FD: 4

Запрос журнала ls . - нажмите 1 LS -> Заголовок журнала. ФД НОСОК: 5

FD: 5

LOG запрос mget . - нажмите 2 MGET COUNT -> Заголовок журнала. ФД НОСОК: 4

FD: 4

ФД НОСОК: 5

FD: 5

Запрос LOG получить /.gitconfig - нажмите 4 FD GET: 5

GET -> LOG SEND .gitconfig LOG request get /

ERROR failed to open file

FD SOCK: 4

FD: 4

Запрос LOG получить /ghostdriver.log - нажмите 6 FD GET: 4

ПОЛУЧИТЬ -> ОТПРАВИТЬ ЖУРНАЛ ghostdriver.log FD SOCK: 8

FD: 8

Запрос LOG получить /.bash_history - нажмите 7 FD GET: 8

ПОЛУЧИТЬ -> ОТПРАВИТЬ ЖУРНАЛ .bash_history FD SOCK: 6

FD: 6

Запрос LOG получить /.profile - нажмите 5 FD GET: 6

ПОЛУЧИТЬ -> ОТПРАВИТЬ ЖУРНАЛ .profile FD SOCK: 10

FD: 10

Запрос LOG get /.ICEauthority — нажмите 8 FD GET: 10

ПОЛУЧИТЬ -> ОТПРАВИТЬ ЖУРНАЛ .ICEauthority FD SOCK: 13

FD: 13

ФД НОСОК: 14

ФД НОСОК: 22

FD: 22

ФД НОСОК: 16

ФД НОСОК: 27

FD: 27

ФД НОСОК: 18

FD: 18

ФД НОСОК: 19

Запрос LOG get /.gtk-bookmarks — попадание 14 FD SOCK: 25

FD: 25

ФД НОСОК: 15

Запрос LOG получить /.bashrc — нажать 20 Запрос LOG получить /.bashrc — нажать 9 FD GET: 18

ФД ПОЛУЧИТЬ: 25

ФД ПОЛУЧИТЬ: 13

ПОЛУЧИТЬ -> ОТПРАВИТЬ ЖУРНАЛ .bashrc ПОЛУЧИТЬ -> ОТПРАВИТЬ ЖУРНАЛ .bashrc FD SOCK: 23

FD: 23

Запрос LOG get /.zshrc — попадание 18 FD SOCK: 26

FD: 26

ФД ПОЛУЧИТЬ: 23

ФД СОК: 29

FD: 29

ПОЛУЧИТЬ -> ОТПРАВИТЬ ЖУРНАЛ .bash_logoutks ПОЛУЧИТЬ -> ОТПРАВИТЬ ЖУРНАЛ .bash_logout FD SOCK: 17

FD: 17

Запрос LOG get /.zsh-update — попадание 13 Запрос LOG get /.zsh-update — попадание 22 FD SOCK: 28

ФД ПОЛУЧИТЬ: 27

FD: 14

Запрос LOG get /.nano_history — попадание 10 Запрос LOG get /.nano_history — попадание 24 Запрос LOG get /.nano_history — попадание 21 FD: 16

Запрос LOG get /.zsh_history — попадание 12 FD SOCK: 21

FD: 21

ФД ПОЛУЧИТЬ: 16

FD: 19

Запрос LOG получить /.selected_editor - нажмите 15 FD SOCK: 24

FD: 24

FD: 15

ФД ПОЛУЧИТЬ: 14

ФД ПОЛУЧИТЬ: 17

ФД ПОЛУЧИТЬ: 29

GET -> LOG SEND .zcompdump-MacPearl-5.0.2 Запрос LOG get /.zcompdump-MacPearl-5.0.2 - hit 17 FD: 28

Запрос LOG get /.Xauthority - нажмите 23 GET -> LOG SEND .Xauthority GET -> LOG SEND .Xauthority FD GET: 28

Запрос LOG get /.Xauthority — нажмите 11 GET -> LOG SEND .Xauthority FD GET: 15

ПОЛУЧИТЬ -> ОТПРАВИТЬ ЖУРНАЛ .Xauthority ПОЛУЧИТЬ -> ОТПРАВИТЬ ЖУРНАЛ .Xauthority FD ПОЛУЧИТЬ: 19

ФД ПОЛУЧИТЬ: 26

ПОЛУЧИТЬ -> ЖУРНАЛ ОТПРАВИТЬ .Xauthority Запрос ЖУРНАЛА получить /.Xauthority — нажать 16 FD GET: 21

ПОЛУЧИТЬ -> ОТПРАВИТЬ ЖУРНАЛ .Xauthority FD ПОЛУЧИТЬ: 22

Запрос LOG get /.Xauthority - нажмите 19 GET -> LOG SEND .Xauthority GET -> LOG SEND .Xauthority GET -> LOG SEND .Xauthority FD GET: 24

ПОЛУЧИТЬ -> ОТПРАВИТЬ ЖУРНАЛ .Xauthority FD SOCK: 30

FD: 30

ФД СОК: 31

FD: 31

Запрос LOG получить /.zcompdumpy — нажать 25 Запрос LOG получить /.zcompdump — нажать 26 FD GET: 30

ФД ПОЛУЧИТЬ: 31

ПОЛУЧИТЬ -> ОТПРАВИТЬ ЖУРНАЛ .zcompdump ПОЛУЧИТЬ -> ОТПРАВИТЬ ЖУРНАЛ .zcompdump

Я вижу, что иногда он использует один и тот же файловый дескриптор более чем в одном потоке. Я просто не могу понять, как я могу решить эту проблему с синхронизацией.


person rafaelcpalmeida    schedule 07.04.2016    source источник
comment
Что это: 'статический буфер символов [BUFSIZE + 1];' ? Пожалуйста, объясните «статический» в многопоточном коде :((   -  person Martin James    schedule 07.04.2016
comment
Иными словами, для многопоточных функций «статический» — это поцелуй смерти.   -  person Martin James    schedule 07.04.2016
comment
Базовый код был предоставлен, и мы должны улучшить код от однопроцессорного до многопроцессорного и многопоточного. Эта часть статики уже была сделана   -  person rafaelcpalmeida    schedule 07.04.2016
comment
Избавиться от этого! Мерзкая, злая статика! Внимательно проверьте остальную часть кода - любой, кто достаточно глуп, чтобы использовать static в библиотеке, вероятно, напортачил еще больше :(   -  person Martin James    schedule 07.04.2016
comment
Достаточно снять статику?   -  person rafaelcpalmeida    schedule 07.04.2016
comment
Без понятия - не я писал. Беглый взгляд показывает, что многие другие функции тоже имеют статический мусор :( Вам придется проанализировать функции, выяснить, почему буферы были сделаны статическими в первую очередь, а затем избавиться от них соответствующим образом. Просто замените их. с автосохранением может работать, а может и нет..   -  person Martin James    schedule 07.04.2016
comment
Неважно - я только что просмотрел остальную часть кода библиотеки FTP. Мой лучший совет - бросай. У него слишком много серьезных проблем, и, впервые увидев «статику», я не сильно удивлен :(   -  person Martin James    schedule 07.04.2016
comment
Небезопасно использовать даже с одним потоком.   -  person Martin James    schedule 07.04.2016
comment
@MartinJames, не могли бы вы объяснить мне, почему так плохо использовать «статический»? Этот код был предоставлен моими учителями, и когда я удаляю ключевое слово «статический», он работает правильно.   -  person rafaelcpalmeida    schedule 07.04.2016


Ответы (1)


Избавьтесь от квалификатора хранения static в таких объявлениях, как:

статический буфер символов [BUFSIZE + 1];

Вы не должны свободно использовать квалификатор static для данных, к которым есть доступ из нескольких потоков — в глобальном масштабе существует только один экземпляр таких данных, и, если не применяется дополнительная явная синхронизация, потоки будут нарезать данные на части.

Если вы используете автоматическое хранение, все распространенные компиляторы C помещают данные в текущий стек. С несколькими потоками, т.е. несколько стеков, что означает один элемент данных на поток и, следовательно, без нарезки и нарезки.

Обратите внимание, что статические буферы в вашем коде имеют комментарий: '/* статический, поэтому заполненный нулями */'. Для остальной части кода/данных могут быть последствия удаления статики - автоматические переменные НЕ инициализируются нулем, и любой код, который полагается на них, будет скомпрометирован.

Также обратите внимание, что в коде есть и другие проблемы, не связанные с многопоточностью. Функция 'ftp' не может правильно обработать характер потока октетов TCP и ошибочно предполагает, что сообщения размером более одного байта будут получены полностью с одним вызовом read() в режиме байтового потока по умолчанию.

Этот код отстой, даже для небиблиотечного кода, и автора следует уволить :)

person Martin James    schedule 07.04.2016
comment
Я перешел с static char buffer[BUFSIZE + 1]; на char buffer[BUFSIZE + 1] = {0};, и теперь все работает правильно. Я не могу просто так избавиться от этого кода, так как это для университетского проекта, а основной код написали наши преподаватели :-) - person rafaelcpalmeida; 07.04.2016
comment
«основной код был написан нашими учителями» — лол, да, звучит примерно так :) - person Martin James; 07.04.2016