首页 > 代码库 > Linux之select系统调用_2

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 \n", 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 !\n");                    }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!\n", 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 !\n",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\n",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...

Linux之select系统调用_2