• Linux之select系统调用_2


    在上一篇博文中,我们的程序中我们有3个客户端,因此也事先建立了3个管道,每个客户端分别使用一个管道向服务器发送消息。而在服务器端使用select系统调用,只要监测到某一管道有消息写入,服务器就将其read,并显示在标准输出上。

    本篇文章,我们会让服务器拥有一个管道,专门用于从客户端接收消息(上线通知,发送需要服务器转发的消息以及下线通知)。服务器需要维护一个列表(使用结构体),记录哪些用户已经连上服务器用于接收消息的管道。当客户端启动,会向服务器发送上线消息,同时将自己的pid发送给server,server会将其添加到列表,以后会转发消息给在列表上的客户端。与此同时,客户端需要创建一个管道,用于接收服务器转发的消息;注意,要将其创建的管道名称告知服务器,以便server打开管道写端,告知管道名称可以在客户端向server发送上线消息时一并发送。 当客户端下线时也要告诉server,以便服务器将其从列表删除,这样以后不会再转发消息给它。如果不删,服务器向一个关闭读端的管道发送消息,会使服务器挂掉!(PIPE信号)

    注意

    我们假设现在有3个客户端,服务器用于接收消息的管道称为A。由于服务器只拥有一个管道A用于接收从客户端发送的消息,那么所有的客户端都会在管道A的另一端开启写端。也就是说所有客户端的3个写端对服务器的1个读端。通过上一篇博文,我们已经知道,select是通过阻塞与否来监听管道的。只有当管道非阻塞时,select才能获得消息,将fd_set中的相应文件描述符置1。当3个写端对1个读端时,非阻塞的情况如下:

    1.当任意一个客户端的写端向管道发送消息时,该管道非阻塞,select可以监听到,read返回读到的字数。

    2.所有3个写端都关闭,该管道非阻塞,select可以监听到,read返回0。注意,仅仅关闭某个客户端的写端,select是检测不到的,原因就是因为select只能检测到非阻塞的状况。

    到底非阻塞是属于1或者2那种情况,select并不会知道,需要我们自己判断,通常通过read的返回值判断。当read返回值为0时,我们会在if语句中使用continue,因为服务器不能挂,客户端关闭的读端,可以在之后开启。

    server.c

    /*************************************************************************
        > File Name: server.c
        > Author: KrisChou
        > Mail:zhoujx0219@163.com 
        > Created Time: Sat 23 Aug 2014 05:08:48 PM CST
     ************************************************************************/
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <dirent.h>
    #include <unistd.h>
    #include <sys/time.h>
    #include <sys/select.h>
    
    typedef struct tag
    {
        int s_id;   /* 进程ID */
        int s_fd;   /* 进程描述符 */
        int s_flag; /* server列表里用户是否有效 */
    }USR,*pUSR;
    int main(int argc, char *argv[])
    {
        /* 打开管道 */
        int fd_server;
        fd_server = open(argv[1], O_RDONLY);
        if(fd_server == -1)
        {
            perror("error");
            exit(1);
        }
        /* 初始化server用户列表 */
        USR ulist[1024];
        memset(ulist,0,sizeof(ulist));
        
        /* 定义select参数各项参数 */
        fd_set read_set,ready_set;     /* ready_set是read_set的备份 */
        FD_ZERO(&read_set);            /* 清空fd_set */
        FD_SET(fd_server, &read_set);  /* 将服务器用于接收消息的管道添加到监听集合中 */
        struct timeval tm;             /* select轮巡时间*/
        
        int nret;                      /* 记录select返回值 */
        char buf[1024];                /* 存放从管道中读取的消息 */ 
        while(1)
        {
            /* 重设select各项参数 */
            tm.tv_sec = 0;
            tm.tv_usec = 1000;
            ready_set = read_set;
            nret = select(fd_server + 1, &ready_set, NULL, NULL, &tm);
            /* 在select的轮巡时间内,管道阻塞,则nret返回0 */
            if(nret == 0)
            {
                continue;
            }
            if(FD_ISSET(fd_server, &ready_set))  //实际上此处if可以省略,因为只监听了一个管道
            {                                     //nret不为0,一定是该管道非阻塞             
                memset(buf, 0, 1024);
                if(0 == read(fd_server, buf,1024)) 
                {
                    continue;
                }else
                {
                    if(strncmp(buf,"on",2) == 0)  //on pid
                    {
                        int pid;
                        char pipename[32] = "";  //存放管道名
                        sscanf(buf+3, "%d", &pid);  //管道名义pid.pipe命名
                        printf("%d on 
    ", pid);    //将客户上线消息输出在屏幕上
                        sprintf(pipename,"%d.fifo", pid);
                        /* 从用户列表中,找一个无效的结构体将其存入 */
                        int index;
                        for(index = 0; index < 1024; index++)
                        {
                            if(ulist[index].s_flag == 0)
                            {
                                break;
                            }
                        }
                        if(index == 1024)
                        {
                            printf("full !
    ");
                        }else
                        {
                            ulist[index].s_id = pid;
                            ulist[index].s_fd = open(pipename,O_WRONLY); /* 打开服务器端的写端,用于转发消息给客户端 */
                            ulist[index].s_flag = 1;
                        }
                    }else if(strncmp(buf,"off",3) == 0) //off pid
                    {
                        int pid;
                        sscanf(buf+4,"%d",&pid);
                        printf("%d off!
    ", pid);
                        int index;
                        for(index = 0;index < 1024; index++)
                        {
                            if(ulist[index].s_id == pid)
                            {
                                ulist[index].s_flag = 0;
                                close(ulist[index].s_fd);
                                break;
                            }
                        }
                    }else
                    {
                        int index;
                        for(index = 0; index < 1024; index++)
                        {
                            if(ulist[index].s_flag == 1)
                            {
                                write(ulist[index].s_fd, buf, strlen(buf));
                            }
                        }
                    }
                }
            }
        }
        
    }

    client.c

    /*************************************************************************
        > File Name: client.c
        > Author: KrisChou
        > Mail:zhoujx0219@163.com 
        > Created Time: Sat 23 Aug 2014 09:21:02 AM PDT
     ************************************************************************/
    
    #include<stdio.h>
    #include<sys/types.h>
    #include<sys/stat.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<string.h>
    #include<fcntl.h>
    int main(int argc,char *argv[])
    {
        /* 打开上传消息给服务器的管道 */
        int fd_send;
        fd_send=open(argv[1],O_WRONLY);
        if(fd_send==-1)
        {
            perror("open");
            exit(1);
        }
        
        /* 注意一定要在向服务器发送上线消息之前创建好客户端自己的管道,不然服务端找不到该管道*/
        /* pipename存放客户端自己所创建的管道,命名统一为pid.fifo */
        char pipename[32]="";
        sprintf(pipename,"%d.fifo",getpid());
        /* 客户端创建接受消息的管道 */
        if(-1==mkfifo(pipename,0666))
        {
            perror("mkfifo");
            exit(1);
        }
        
        /* 将上线消息写入管道 */
        char msg[1024]="";
        sprintf(msg,"on %d !
    ",getpid());
        write(fd_send,msg,strlen(msg));
        
        
        
        /* 打开客户端自己的管道 */
        int fd_rcv; 
        fd_rcv=open(pipename,O_RDONLY);
        if(fd_rcv==-1)
        {
            perror("open client");
            exit(1);
        }
        
        /* 子进程用于接收服务器转发的消息 */
        if(fork()==0)
        {
            close(fd_send);
            while(memset(msg,0,1024),read(fd_rcv,msg,1024)>0)
            {
                printf("msg>>:");
                fflush(stdout);
                write(1,msg,strlen(msg));
            }
            /* 当客户端下线,服务器将其从列表中删除,并关闭客户端管道的写端。 
               当服务器关闭该管道的写端时,即退出while循环                  */
            close(fd_rcv);
            exit(1);
        }
        /* 父进程用于发送消息 */
        close(fd_rcv);
        while(memset(msg,0,1024),fgets(msg,1024,stdin)!=NULL)
        {
            write(fd_send,msg,strlen(msg));
        }
        /* 按ctrl+D退出循环,之后客户端下线 */
        memset(msg,0,1024);
        sprintf(msg,"off %d
    ",getpid());
        write(fd_send,msg,strlen(msg));
        close(fd_send);
        wait(NULL);
    }
    运行程序,输入:
    make 1.fifo
    
    ./server.exe 1.fifo
    
    ./client.exe 1.fifo
    ./client.exe 1.fifo
    ...
  • 相关阅读:
    总结在Visual Studio Code创建Node.js+Express+handlebars项目
    【转】nodeJs学习之项目结构
    jquery新版本旧版本之间的坑
    bootstrap使用中遇到的坑
    dataTables基础函数变量
    tabales1.10版参数详解
    【转】自定义(滑动条)input[type="range"]样式
    dataTables工作总结
    转载---SpringBoot+Maven多模块项目(创建、依赖、打包可执行jar包部署测试)完整流程
    MVC模式下的跨域问题
  • 原文地址:https://www.cnblogs.com/jianxinzhou/p/3932112.html
Copyright © 2020-2023  润新知