c-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("nnhint: ./tftps Port-Number Top-Directorynn""ttftps is a small and very safe mini ftp servern""tExample: ./tftps 8181 ./fileDir nn");
        exit(0);
    }
    if (chdir(argv[2]) == -1) {
        printf("ERROR: Can't Change to directory %sn", argv[2]);
        exit(4);
    }
    printf("LOG tftps starting %s - pid %dn", argv[1], getpid());
    /* setup the network socket */
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        printf("ERROR system call - setup the socketn");
    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 errorn");
    if (listen(listenfd, 64) < 0)
        printf("ERROR system call - listen errorn");

    // 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 errorn");
        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: %dnn", sock);
        ftp(sock, args->hit);
        free(args);
        //printf("Thread executounn");
        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: %dnn", 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 %dn", 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: %dnn", 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 %sn", fileName);
        printf("Err: %dnn",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;
}

这是来自服务器的日志示例

日志tftps启动8181-pid 15416 FD SOCK:4

FD:4

日志请求ls.-点击1 LS->LOG标头。FD袜子:5

FD:5

LOG请求管理点击2 MGET COUNT->LOG Header。FD袜子:4

FD:4

FD袜子:5

FD:5

日志请求获取/.gitconfig-点击4 FD获取:5

获取->日志发送.gitconfig日志请求获取/

ERROR无法打开文件

FD袜子:4

FD:4

日志请求获取/重影驱动程序.LOG-命中6 FD获取:4

获取->日志发送ghostdriver.LOG FD SOCK:8

FD:8

日志请求获取/.bash_history-命中7 FD获取:8

获取->日志发送。bash_history FD SOCK:6

FD:6

日志请求获取/.profile-点击5 FD获取:6

获取->日志发送。配置文件FD SOCK:10

FD:10

日志请求获取/.ICEauthority-命中8 FD获取:10

获取->日志发送。ICEauthority FD SOCK:13

FD:13

FD袜子:14

FD袜子:22

FD:22

FD袜子:16

FD袜子:27

FD:27

FD袜子:18

FD:18

FD袜子:19

日志请求获取/.gtk书签-点击14 FD SOCK:25

FD:25

FD袜子:15

LOG请求获取/.bashrc-命中20 LOG请求获取/.bashrc–命中9 FD获取:18

FD获取:25

FD获取:13

获取->日志发送。bashrc获取->日志传输。bashrcFD SOCK:23

FD:23

日志请求获取/.zshrc-命中18 FD SOCK:26

FD:26

FD获取:23

FD袜子:29

FD:29

获取->日志发送.bash_logoutks获取->日志传输.bash_logout FD SOCK:17

FD:17

日志请求获取/.zsh更新-点击13日志请求获取/.zsh升级-命中22 FD SOCK:28

FD获取:27

FD:14

日志请求获取/.nano_history-命中10日志请求获取/.nano_hestory-hit 24 LOG请求get/.nano_history-hit 21 FD:16

日志请求获取/.zsh_history-命中12 FD SOCK:21

FD:21

FD获取:16

FD:19

日志请求获取/.selected_editor-点击15 FD SOCK:24

FD:24

FD:15

FD获取:14

FD获取:17

FD获取:29

获取->日志发送.zcompdump-MacPearl-5.0.2日志请求获取/.zcompdump-MacPearl-5.0.2-命中17 FD:28

日志请求获取/.Xauthority-点击23获取->日志发送.Xauthority获取->日志发送。Xauthority FD获取:28

日志请求获取/.Xauthority-点击11获取->日志发送.Xauthority FD获取:15

GET->LOG SEND.Xauthority GET->LOG SEND.Xaauthority FD GET:19

FD获取:26

获取->日志发送.Xauthority日志请求获取/.Xauthority-命中16 FD获取:21

获取->日志发送。Xauthority FD获取:22

日志请求获取/.Xauthority-点击19获取->日志发送.Xauthority获取->日志发送.Xauthority GET->日志发送.Xauthority FD GET:24

获取->日志发送。Xauthority FD SOCK:30

FD:30

FD袜子:31

FD:31

日志请求获取/.zcompdumpy-命中25日志请求获取/.zcompumpy-命中26 FD获取:30

FD获取:31

GET->LOG SEND.zcompdump GET->LOG SEND.zcompdump

我可以看到,有时他在多个线程中使用相同的文件描述符。我只是不知道如何解决这个同步问题。

取消声明上的"static"存储限定符,例如:

静态字符缓冲区[BUFSIZE+1];

您不能在受多个线程访问的数据上自由使用静态限定符-全局只有一个此类数据的实例,除非应用额外的显式同步,否则线程会将数据切碎。

如果使用自动存储,所有常见的C编译器都会将数据放在当前堆栈中。对于多个线程,即多个堆栈,这意味着每个线程有一个数据项,因此没有切片和划片。

请注意,代码中的静态缓冲区有一个注释:"/*static so zero-filled*/"。删除静态自动变量的其余代码/数据可能会受到影响——自动变量没有初始化为零,任何依赖于此的代码都会受到影响。

还要注意,代码还有其他问题,与多线程无关。"ftp"函数无法正确处理TCP的八位字节流性质,并错误地假设,在默认字节流模式下,只需调用一次read(),就可以完整接收到大于一个字节的消息。

这段代码很糟糕,即使是非库代码,作者也应该被解雇:)

最新更新