如何通过 select 系统调用使用 FIFO 从服务器与客户端聊天?

How to chat with client from a server using FIFO with select system call?

你好,我正在尝试制作这样一个程序,其中两个 fds stdinfifoselect() 监控并相互通信。 select() 将监视 fifo 准备好读取或 stdin.

server.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/select.h>
int main(int argc,char *argv[]){
    int f,fifo_read,fifo_write,status;
    fd_set readset;
    FD_ZERO(&readset);
    char str[512]="start";
    if(argc !=2)
    if(argc != 2){
    printf("\nError: %s required argument [Fifo Name]\n\n",argv[0]);
            exit(EXIT_FAILURE);
    }

    if ((open(argv[1], O_RDWR)) < 0){
        f = mkfifo(argv[1],S_IRWXU);
        if(f<0){
            perror("Error While Creating FIFO ");
            exit(EXIT_FAILURE);
        }
        else
            printf("FIFO Created Successfully...\n");
    }

    while(strcmp(str,"end")!=0){
        fifo_write= open(argv[1],O_WRONLY);
        FD_SET(fifo_read, &readset);
        FD_SET(STDIN_FILENO, &readset);
        status = select(fifo_read+1, &readset, NULL, NULL, NULL);
        if(status==-1){
            perror("Error While Calling select() system call ");
            //exit(EXIT_FAILURE);   
        }
        if(FD_ISSET(STDIN_FILENO,&readset)){
            if(fifo_write<0)
                perror("\nError while writing on pipe ");
            else{
                printf("\nServer>> ");
                scanf("%s",str);
                write(fifo_write,str,strlen(str));
                close(fifo_write);
            }
        }
        fifo_read=open(argv[1],O_RDONLY);
        if(FD_ISSET(fifo_read,&readset)){
            if(fifo_read<0)
                perror("\nError while reading from pipe ");
            else{
                read(fifo_read,str,strlen(str));
                close(fifo_read);
                printf("\nJiya%s",str);
            }
        }
    }
    return 0;
}

client.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/select.h>
int main(int argc,char *argv[]){
    int f,fifo_read,fifo_write,status;
    fd_set readset;
    FD_ZERO(&readset);
    char str[512]="start";
    
    while(strcmp(str,"end")!=0){
        fifo_write= open(argv[1],O_WRONLY);
        FD_SET(fifo_read, &readset);
        FD_SET(STDIN_FILENO, &readset);
        status = select(fifo_read+1, &readset, NULL, NULL, NULL);
        if(status==-1){
            perror("Error While Calling select() system call ");
            //exit(EXIT_FAILURE);   
        }
        if(FD_ISSET(fifo_read,&readset)){
            if(fifo_read<0)
                printf("\nError opening read pipe");
            else{
                read(fifo_read,str,strlen(str));
                close(fifo_read);
                printf("\n%s",str);
            }
        }
        fifo_read=open(argv[1],O_RDONLY);
        if(FD_ISSET(STDIN_FILENO,&readset)){
            if(fifo_write<0)
                printf("\nError opening write pipe");
            else{
                printf("\nClient>> ");
                scanf("%s",str);
                write(fifo_write,str,strlen(str));
                close(fifo_write);
            }
        }
    }
    return 0;
}

我在下面发布了您的代码的一个有效的、稍微简化的版本。此代码从客户端中的 STDIN 读取并通过 mkfifo 创建的管道将该输入发送到服务器。服务器从管道的末端循环读取并将读取的内容回显到 STDOUT。

客户端输出:

CLIENT > Message1
CLIENT > Message2
CLIENT > Yet another message.
CLIENT > A fourth message, but this one is quite a bit longer than the first messages.
CLIENT > end

服务器输出:

SERVER: Got 8 byte message: 'Message1'                                                                      
SERVER: Got 8 byte message: 'Message2'                                                                      
SERVER: Got 20 byte message: 'Yet another message.'                                                         
SERVER: Got 77 byte message: 'A fourth message, but this one is quite a bit longer than the first messages.'
EOF encountered...                                                                                          

我将强调一些主要区别:

  • 我首先在服务器中通过 mkfifo 创建管道专用文件。然后我在服务器和客户端中打开特殊文件。这发生在循环之前,文件描述符在循环期间保持打开状态,而不是反复打开和关闭。
  • 客户端只写入管道,服务器只从管道读取。至少在 Linux 上,管道是单向的(pipe man pagePipes and FIFOs (also known as named pipes) provide a unidirectional interprocess communication channel. A pipe has a read end and a write end...)在 Linux 上,两个管道可以用于双向通信,甚至在支持双向管道的系统上,使用仍然支持两个管道,并且更便携。
  • 调用read时,您应该使用str缓冲区的总大小,而不是strlen(str)。此外,str 应在读取之间清除,以免包含旧数据(新代码中的 memset)。
  • 我用 fgets 而不是 scanf
  • 服务器打开FIFO后,我unlink这个文件,这样就自动从文件系统中删除了。底层文件对象将仍然存在,直到使用它的进程终止。这是可选的。

服务器:

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    int fifo_read = -1;
    char str[512]= "";

    fd_set readset;
    FD_ZERO(&readset);

    if (argc != 2) {
        printf("Usage: %s FIFO_NAME\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    /* Server will make the FIFO, both sides will open it */
    if (mkfifo(argv[1], S_IRWXU) == -1) {
        perror("mkfifo()");
        exit(EXIT_FAILURE);
    }

    if ((fifo_read = open(argv[1], O_RDONLY)) == -1) {
        perror("open()");
        exit(EXIT_FAILURE);
    }

    if (unlink(argv[1]) == -1) {
        perror("unlink()");
        exit(EXIT_FAILURE);
    }

    while (1) {
        FD_SET(fifo_read, &readset);
        if (select(fifo_read + 1, &readset, NULL, NULL, NULL) == -1) {
            perror("select()");
            exit(EXIT_FAILURE);
        }

        if (FD_ISSET(fifo_read, &readset)) {
            ssize_t bytes_read;
            memset(str, 0, sizeof(str));
            bytes_read = read(fifo_read, str, sizeof(str));
            if (bytes_read == -1) {
                perror("read()");
                exit(EXIT_FAILURE);
            }
            else if (bytes_read == 0) {
                printf("EOF encountered...\n");
                break;
            }
            printf("SERVER: Got %ld byte message: '%s'\n", bytes_read, str);
        }
    }

    if (close(fifo_read) == -1) {
        perror("close()");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}

客户:

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    int fifo_write = -1;
    char str[512] = "";

    fd_set readset;
    FD_ZERO(&readset);

    if (argc != 2) {
        printf("Usage: %s FIFO_NAME\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    if ((fifo_write = open(argv[1], O_WRONLY)) == -1) {
        perror("open()");
        exit(EXIT_FAILURE);
    }

    while (1) {
        printf("CLIENT > ");
        fflush(stdout);

        FD_SET(STDIN_FILENO, &readset);
        if (select(STDIN_FILENO + 1, &readset, NULL, NULL, NULL) == -1) {
            perror("select()");
            exit(EXIT_FAILURE);
        }
        if (FD_ISSET(STDIN_FILENO, &readset)) {
            memset(str, 0, sizeof(str));
            if (!fgets(str, sizeof(str), stdin)) {
                printf("fgets() failed to read a line.\n");
                exit(EXIT_FAILURE);
            }

            // See 
            str[strcspn(str, "\r\n")] = 0;

            if (strcmp(str, "end") == 0) {
                break;
            }
            if (write(fifo_write, str, strlen(str)) == -1) {
                perror("write()");
                exit(EXIT_FAILURE);
            }
        }
    }

    if (close(fifo_write) == -1) {
        perror("close()");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}

这里我找到了解决方案:)

server.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/select.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>

int main(int argc, char *argv[])
{
    if(argc != 2){
        printf("\nError: %s required argument [Fifo Name]\n\n",argv[0]);
        exit(EXIT_FAILURE);
    }
    int fd,wr,rd,ret;
    fd_set readset;
    if(mkfifo(argv[1],S_IRWXU)==-1){
        if(errno!=EEXIST)
            perror("Error unable to create FIFO ");
        else
            perror("Error unable to create FIFO ");
        exit(EXIT_FAILURE);
    }
    else
        printf("FIFO created Successfully!\n\n");
    fd = open(argv[1],O_RDWR);
    if(fd==-1){
        perror("Error Failed to open fifo\n");
        exit(EXIT_FAILURE);
    }
    while(!0){
        FD_ZERO(&readset);
        FD_SET(fd,&readset);
        FD_SET(STDIN_FILENO,&readset);
        sleep(1);
        ret = select(fd+1,&readset,NULL,NULL,NULL);
        if(ret==-1){
            perror("Error select() ");
            exit(EXIT_FAILURE);
        }
        char str[512]="";
        if(FD_ISSET(STDIN_FILENO,&readset)){
            //fprintf(stderr, ">> ");
            rd = read(STDIN_FILENO,str,sizeof(str));
            if(rd==-1){
                perror("Error while reading from fifo");
                exit(EXIT_FAILURE);
            }
            char temp[512]="Server :: ";
            strcat(temp,str);
            wr = write(fd,temp,sizeof(temp));
            if(wr==-1){
                perror("Error while writing to fifo ");
                exit(EXIT_FAILURE);
            }
            continue;
        }
        if(FD_ISSET(fd,&readset)){
            rd = read(fd,str,sizeof(str));
            if(rd==-1){
                perror("Error while reading from fifo");
                exit(EXIT_FAILURE);
            }else if(rd==0)
                continue;
            //fprintf(stderr,"P2: %s\n",str);
            printf("%s\n",str);
            //write(STDOUT_FILENO,str,sizeof(str));
        }
    }
}

client.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/select.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>

int main(int argc, char *argv[])
{
    if(argc != 2){
        printf("\nError: %s required argument [Fifo Name]\n\n",argv[0]);
        exit(EXIT_FAILURE);
    }
    int fd,wr,rd,ret;
    fd_set readset;
    fd = open(argv[1],O_RDWR);
    if(fd==-1){
        perror("Error Failed to open fifo\n");
        exit(EXIT_FAILURE);
    }
    while(!0){
        FD_ZERO(&readset);
        FD_SET(fd,&readset);
        FD_SET(STDIN_FILENO,&readset);
        sleep(2);
        ret = select(fd+1,&readset,NULL,NULL,NULL);
        if(ret==-1){
            perror("Error select() ");
            exit(EXIT_FAILURE);
        }
        char str[512]="";
        if(FD_ISSET(fd,&readset)){
            rd = read(fd,str,sizeof(str));
            if(rd==-1){
                perror("Error while reading from fifo");
                exit(EXIT_FAILURE);
            }else if(rd==0)
                continue;
            //fprintf(stderr,"P2: %s\n",str);
            printf("%s\n",str);
            //write(STDOUT_FILENO,str,sizeof(str));
        }
        if(FD_ISSET(STDIN_FILENO,&readset)){
            //fprintf(stderr, ">> ");
            rd = read(STDIN_FILENO,str,sizeof(str));
            if(rd==-1){
                perror("Error while reading from fifo");
                exit(EXIT_FAILURE);
            }
            char temp[512]="Client :: ";
            strcat(temp,str);
            wr = write(fd,temp,sizeof(temp));
            if(wr==-1){
                perror("Error while writing to fifo ");
                exit(EXIT_FAILURE);
            }
            continue;
        }
    }
}

输出为: