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 *exceptfds, struct timeval *timeout);

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);
DESCRIPTION
       select() allow  a  program  to  monitor  multiple  file
       descriptors,  waiting  until one or more of the file descriptors become
       "ready" for some class of I/O operation (e.g., input possible).  A file
       descriptor  is considered ready if it is possible to perform the corre-
       sponding I/O operation (e.g., read(2)) without blocking.

select函数对数据结构fd_set进行操作,fd_set是一个bitmap,该集合由打开的文件描述符构成。

注意,对于管道而言,select是通过检查管道是否阻塞,来进行监听的。只要所监听的管道非阻塞,select就能立马获知,并将其在fd_set中的相应为置1。管道非阻塞只有两种情况,一种是客户端向管道写东西,另一种是客户端将管道关闭(读端)。当然,对于非阻塞的情况,select能监听到,但它并不会知道非阻塞是因为客户端关闭管道还是客户端向管道写消息。这需要我们自己来判断。通常是通过read系统调用的返回值来判断。read返回0,说明是客户端关闭了管道,read返回正值,说明是客户端向管道写东西了。

代码

实现多个客户端向服务端发送消息,服务端将消息打印在标准输出上。实现如下:

server.c

/*************************************************************************
  > File Name: server.c
  > Author: KrisChou
  > Mail:[email protected]
  > Created Time: Sat 23 Aug 2014 02:37:58 PM CST
 ************************************************************************/

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>

int main(int argc, char* argv[])
{
    /* 打开管道 */
    int fd1, fd2, fd3 ;
    fd1= open(argv[1], O_RDONLY);
    fd2= open(argv[2], O_RDONLY);
    fd3= open(argv[3], O_RDONLY);

    printf("server: %d \n", getpid());

    /* 设置select需要监听的集合fd_set */
    fd_set readset ,bak_set ;
    FD_ZERO(&bak_set);
    FD_SET(fd1, &bak_set);
    FD_SET(fd2, &bak_set);
    FD_SET(fd3, &bak_set);
    /* 设置select轮巡时间 */
    struct timeval  tm ;
    tm.tv_sec = 10 ;
    tm.tv_usec = 0 ;
    /* 可以在程序开始,使得服务器端同时读到3个消息,此处可有可无 */
    sleep(10);

    /* select是通过检查管道是否阻塞,来进行监听的。如果均为阻塞,select在轮巡时间内返回0。
     * 如果有管道不阻塞,则select返回不阻塞的管道个数。
     * 注意:当某个客户端向管道发送数据,或者关闭管道,均属于非阻塞状况。
     * 我们可以根据read的返回值来区分这两种情况                                           */
    int nret ; /* select返回值 */
    char buf[1024];
    while(1){
        /* 每次select后都会改变select所监听集合的值,因此需要在每次select的开始重新设置监听集合。
         * 如果不进行设置,那么上次select后监听集合置1的描述符仍然为1。
         * 注意:在Linux系统下,时间也需要重新设置。                                            */
        readset = bak_set ;
        tm.tv_sec = 10 ;
        tm.tv_usec = 0 ;
        nret = select(6 , &readset, NULL, NULL, &tm );
        if(nret > 0)
        {
            printf("%d active ! \n", nret);
            if(FD_ISSET(fd1, &readset))       /* select返回值大于0时,必然有管道不阻塞了,此时需要查看是哪个管道*/
            {
                memset(buf, 0 , 1024);
                if(0 == read(fd1, buf, 1024))
                {
                    FD_CLR(fd1, &bak_set);    /* 如果一个用户其写端已经关闭,那么将其描述符从监听集合中去除 */
                }else
                {
                    write(1, buf, strlen(buf));
                }
            }
            if(FD_ISSET(fd2, &readset))
            {
                memset(buf, 0 , 1024);
                if(0 == read(fd2, buf, 1024) )
                {
                    FD_CLR(fd2, &bak_set);
                }else
                {
                write(1, buf, strlen(buf));
                }
            }
            if(FD_ISSET(fd3, &readset))
            {
                memset(buf, 0 , 1024);
                if(read(fd3, buf, 1024) == 0)
                {
                    FD_CLR(fd3, &bak_set);
                }else
                {
                    write(1, buf, strlen(buf));
                }
            }

        }else if(nret == 0)
        {
            printf("time out ! \n");
        }
    }

}

client.c

/*************************************************************************
    > File Name: client.c
    > Author: KrisChou
    > Mail:[email protected]
    > Created Time: Sat 23 Aug 2014 02:30:54 PM CST
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>

int main(int argc, char *argv[])
{
    /* 开启管道 */
    printf("%d start ! \n", getpid());
    int fd_sent;
    fd_sent = open(argv[1],O_WRONLY);
    if(fd_sent == -1)
    {
        perror("open");
        exit(1);
    }
    printf("connect success! \n");

    /* 客户端向管道发送消息,按ctrl+D退出循环 */

    char line[1024]; /* 从标准输入获得要发送的消息 */
    char msg[1024];  /* 给消息头加一个pid,表示身份 */

    while(memset(line,0,1024), fgets(line,1024,stdin) != NULL)
    {
        memset(msg,0,1024);
        sprintf(msg, "%d : %s \n", getpid(), line);
        write(fd_sent, msg, strlen(msg));
    }

    close(fd_sent);
    return 0;
}

执行程序:

mkfifo 1.fifo 2.fifo 3.fifo;
./server.exe 1.fifo 2.fifo 3.fifo
./client 1.fifo
./client 2.fifo
./client 3.fifo

注意1

如果将以下代码去除:

if(0 == read(fd1, buf, 1024))
{
    FD_CLR(fd1, &bak_set);     /* 如果一个用户其写端已经关闭,那么将其描述符从监听集合中去除 */
}

fd2与fd3的也去除,则程序运行,会陷入死循环。原因如下:我们假设客户端关闭了管道1.fifo,那么select就会监听到1.fifo非阻塞,会执行while循环中的if语句,检测到是fd1非阻塞(原因是客户端在写端关闭管道了),那么会执行write(1, buf, strlen(buf)); 因为buf为空,什么也不输出。此时,由于while(1)死循环,select接着监听,由于没有将fd1从监听集合中去除,select立马要监听到其非阻塞…如此循环往复,陷入死循环。当有一个客户端关闭写端,退出后,程序输出结果为:

1 active!
1 active!
1 active!
...

注意2

如果不强制退出,服务端程序永远不会退出去。客户端可以关闭管道退出。也可以在关闭后继续开启管道,与服务器相连。

时间: 2024-10-24 09:54:31

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

Linux之select系统调用_2

在上一篇博文中,我们的程序中我们有3个客户端,因此也事先建立了3个管道,每个客户端分别使用一个管道向服务器发送消息.而在服务器端使用select系统调用,只要监测到某一管道有消息写入,服务器就将其read,并显示在标准输出上. 本篇文章,我们会让服务器拥有一个管道,专门用于从客户端接收消息(上线通知,发送需要服务器转发的消息以及下线通知).服务器需要维护一个列表(使用结构体),记录哪些用户已经连上服务器用于接收消息的管道.当客户端启动,会向服务器发送上线消息,同时将自己的pid发送给server

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