如何使用带有选择系统调用的 FIFO 从服务器与客户端聊天?

Posted

技术标签:

【中文标题】如何使用带有选择系统调用的 FIFO 从服务器与客户端聊天?【英文标题】:How to chat with client from a server using FIFO with select system call? 【发布时间】:2021-02-16 15:56:00 【问题描述】:

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

服务器.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;


【问题讨论】:

【参考方案1】:

我在下面发布了您的代码的一个工作的、稍微简化的版本。此代码从客户端中的 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 page: Pipes 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 https://***.com/questions/2693776/removing-trailing-newline-character-from-fgets-input
            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);

【讨论】:

【参考方案2】:

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

服务器.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;
        
    


输出是:

【讨论】:

以上是关于如何使用带有选择系统调用的 FIFO 从服务器与客户端聊天?的主要内容,如果未能解决你的问题,请参考以下文章

httpsS是如何实现服务器与客服机之间的数据加密

如何查询b站之前的客服聊天记录

带有 fifo 的线程

FIFO页面置换模拟

如何从剑道窗口外调用 jquery 选择器?

verilog中如何调用fifo做50个信号延迟