Linux之select系统调用_2,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的使用问题

select() 函数会修改变量tv的值,你每次调用select()函数前需要再次设置tv。
或者你用pselect()
 

对于Linux下一个小程序中select()函数的问题,这个程序中select到底执行了几次?

原因很简单。

因为,你按下一个键之后,缓冲区里面有数据了,也就是说,stdin已经发生了一个事件,就是有数据来了。
你一直没有对缓冲区做任何操作,因此stdin一直保持着有数据的状态。

如果你在个select返回之后,用fflush清空缓冲区,或者,把缓冲区数据用scanf,getchar等输入函数给拿出来,数据取完了,stdin缓冲区就没东西了,再select,状态就是等待事件发生状态了。

为了保险,你每次重新把tv给赋值一次.
tv.tv_sec=2;
tv.tv_usec=0;
 

相关内容