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:[email protected]
    > 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:[email protected]
    > 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
...
时间: 2024-10-27 17:58:38

Linux之select系统调用_2的相关文章

Linux之select系统调用_1

SYNOPSIS /* According to POSIX.1-2001 */ #include <sys/select.h> /* According to earlier standards */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *

Linux中select函数

转载自:http://blog.163.com/henry_hlh/blog/static/17039507420124211841298/ Unix中的函数select和poll用来,支持Unix中I/O复用的功能,在Unix中I/O模型可以分为以一几种: (1)阻塞I/O (2)非阻塞I/O (3)I/O复用(select和poll) (4)信号驱动I/O(SIGIO) (5)异步I/O 其中,现在比较流行的I/O模型是阻塞I/O模型.阻塞I/O是当应用程序和内核交换数据时,由于内核还没有准

Linux内核(四)系统调用

转载请注明出处:jiq?钦's technical Blog 什么是系统调用? 系统调用--内核和用户应用程序的桥梁,中间人. 系统调用就是内核实现的一系列函数,这些函数提供了一套固定的接口,通过这套接口,用户程序可以访问系统硬件和操作系统的资源,即内核提供的服务. 为什么提供系统调用? 用户空间只能通过系统调用来访问内核提供的服务的根本原因是为了对系统进行"保护",因为我们知道Linux的运行空间分为内核空间与用户空间,它们各自运行在不同的级别中,逻辑上相互隔离.用户进程在通常情况下

linux下select,poll,epoll的使用与重点分析

好久没用I/O复用了,感觉差不多都快忘完了,记得当初刚学I/O复用的时候花了好多时间,但是由于那会不太爱写博客,导致花很多时间搞明白的东西,依然很容易忘记.俗话说眼过千遍不如手过一遍,的确,在以后的学习中,无论知识的难易亦或是重要程度如何,我都会尽量义博客的形式记录下来,这样即能用博客来督促自己学习,也能加深对知识的理解俩全其美,好了废话不说了. I/O复用的基本概述 I/O复用技术主要是用来同时监听多个套接字描述符,使得我们的程序大幅度的提高性能,一般如下情况会用到I/O复用技术 (1)程序需

Linux下select, poll和epoll IO模型的详解(转)

http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll 介绍 Epoll 可是当前在 Linux 下开发大规模并发网络程序的热门人选, Epoll 在 Linux2.6 内核中正式引入,和 select 相似,其实都 I/O 多路复用技术而已 ,并没有什么神秘的.其实在 Linux 下设计并发网络程序,向来不缺少方法,比如典型的 Apache 模型( Process Per Connection ,简称 PPC ), TP

linux下select函数详解及实例

一.概述: 系统提供select函数来实现I/O复用输入/输出模型.select系统调用是用来让我们的程序监视多个文件句柄的状态变化的.程序会停在select这里等待,直到被监视的文件句柄中有一个或多个发生生了状态改变. 二.select函数: 以下为man文本中的解释:  /* According to POSIX.1-2001 */        #include <sys/select.h>        /* According to earlier standards */     

基于int的Linux的经典系统调用实现

 先说明两个概念:中断和系统调用 一 系统调用: 是应用程序(运行库也是应用程序的一部分)与操作系统内核之间的接口,它决定了应用程序是如何和内核打交道的. 1,  Linux系统调用:2.6.19版内核提供了319个系统调用.比如 exit fork read open close …… 2,  对Windows来说,操作系统提供给应用程序的接口不是系统调用,而是API.比如:ReadFile.我们暂时把API和系统调用等同起来 3,  Linux中,每个系统调用对应一个系统调用号,内核维护了一

linux中mmap系统调用原理分析与实现

参考文章:http://blog.csdn.net/shaoguangleo/article/details/5822110 linux中mmap系统调用原理分析与实现 1.mmap系统调用(功能)      void* mmap ( void * addr , size_t len , int prot , int flags ,int fd , off_t offset )      内存映射函数mmap, 负责把文件内容映射到进程的虚拟内存空间, 通过对这段内存的读取和修改,来实现对文件的

高性能网络编程 - select系统调用

IO复用使得程序能够同时监听多个文件描述符,比如客户端需要同时处理用户输入和网络连接,服务器端需要同时处理监听套接字和连接套接字,select系统调用可以使得我们监听自己感兴趣描述符,可读,可写,异常等事件.select能处理的异常只有带外数据.能同时处理描述符的数量受限于FD_SETSIZE的大小(一般1024).下面这个程序展示了它的一般用法. #include <stdio.h> #include <stdlib.h> #include <unistd.h> #i