zoukankan      html  css  js  c++  java
  • 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
    ...
  • 相关阅读:
    ArrayList用法
    MessageBox
    将文本文件导入Sql数据库
    在桌面和菜单中添加快捷方式
    泡沫排序
    Making use of localized variables in javascript.
    Remove double empty lines in Visual Studio 2012
    Using Operations Manager Connectors
    Clear SharePoint Designer cache
    Programmatically set navigation settings in SharePoint 2013
  • 原文地址:https://www.cnblogs.com/jianxinzhou/p/3932112.html
Copyright © 2011-2022 走看看