select的使用

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

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

也许你会产生这样的疑问,如果有n个客户端,那么我们不是也可以fork出n-1个子进程,共n个进程,用于接收客户端消息吗?实际上这是不可行的,因为我们一个程序最多只能打开1024个进程。因此,此处使用select是很合理的。

实现多个客户端向服务端发送消息,服务端将消息打印在标准输出上。如果有3个客户端,就事先建立3个管道,每个客户端分别使用一个管道向服务器发送消息。而在服务器端使用select系统调用,只要监测到某一管道有消息写入,服务器就将其read,并显示在标准输出上。

//server.c#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
#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

如果将以下代码去除:
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!
...

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

时间: 2024-11-10 08:16:13

select的使用的相关文章

MySQL(九)之数据表的查询详解(SELECT语法)二

上一篇讲了比较简单的单表查询以及MySQL的组函数,这一篇给大家分享一点比较难得知识了,关于多表查询,子查询,左连接,外连接等等.希望大家能都得到帮助! 在开始之前因为要多表查询,所以搭建好环境: 1)创建数据表suppliers 前面已经有一张表是book表,我们在建立一张suppliers(供应商)表和前面的book表对应. 也就是说 让book中s_id字段值指向suppliers的主键值,创建一个外键约束关系. 其实这里并没有达到真正的外键约束关系,只是模拟,让fruits中的s_id中

微信开发 select选择框

最近在该企业微信的功能,要做一个微信界面,要使用select来做下拉选择框 部分前台HTML代码: 在选择分享组的时候,要从后台查询数据来做选择项 1 <form:form id="imgForm" modelAttribute="uploadImg" action="${oauthPath}/img/${agentKey}/submit" method="post"> 2 <input id="i

将Excel导入DataGridView 中的"select * from [Sheet1$]"中[ ]里面表单名的动态获取

Sheet1$是Excel默认的第一个表名,如果改动:select * from [Sheet1$]"将查询失败,因此应根据选择自动获取excel表名: 1 OpenFileDialog ofd = new OpenFileDialog(); //选择文件路径 2 ofd.Title = "Excel文件"; 3 ofd.FileName = ""; 4 ofd.Filter = "Excel文件(*.xls)| *.xls"; 5 s

select 设置发送超时发送注意事项

//设置发送超时你只发送, 并发送足够多的数据以填满发送缓冲区, 接收端一直不接收.发送端一量满发送缓冲区就会阻塞, 如果你设置了发送超时, 超时到了它就会返回发送超时了. 在send(),recv()过程中有时由于网络状况等原因,收发不能预期进行,而设置收发超时控制: 在Linux下需要注意的是时间的控制结构是struct timeval而并不是某一整型数,以下是来自于网上一篇文章中的摘录,它是这样写的:int nNetTimeout=1000;//1秒,//设置发送超时setsockopt(

IO复用select实现

1 #include<unistd.h> 2 #include<stdio.h> 3 #include<sys/select.h> 4 #include<sys/socket.h> 5 #include<netinet/in.h> 6 #include<string.h> 7 #include<stdlib.h> 8 #include<assert.h> 9 10 #define SERVER_IP "

Mysql数据库理论基础之五--SELECT单多表查询、子查询、别名

一.简介 由MySQL AB公司开发,是最流行的开放源码SQL数据库管理系统,主要特点: 1.是一种数据库管理系统 2.是一种关联数据库管理系统 3.是一种开放源码软件,且有大量可用的共享MySQL软件 4.MySQL数据库服务器具有快速.可靠和易于使用的特点 5.MySQL服务器工作在客户端/服务器模式下,或嵌入式系统中 InnoDB存储引擎将InnoDB表保存在一个表空间内,该表空间可由数个文件创建.这样,表的大小就能超过单独文件的最大容量.表空间可包括原始磁盘分区,从而使得很大的表成为可能

使用原生JavaScript实现对select增加option标签并附加value属性

好久没有写原生的东西了,今天写了一个小项目里面包含着option选项,所以我决定使用原生JavaScript动态生成, 本着互联网分享精神,我将本篇文章分享给大家. html代码(就是一个select) <select name="" id="reg-select"></select> json数据 var json = [ { "cc": "86", "code": "C

我所知道的数据库10-DQL语言SELECT(续)

昨天写到单表查询,今天写下嵌套吧,多表联查看时间了-- 先说伪劣吧 ROWID ROWID是一种数据类型,用来唯一标识一条记录的物理位置,由基于64位编码的18个字符组成. 组成: 000000 | FFF | BBBBBB | RRR 其中,1-6位是数据对象编号,7-9位是文件编号,10-15位是块编号,16-18位是行编号. ROWNUM ROWNUM是查询结果中某条记录的行号. 这两者有着巨大的不同.ROWID是真实的存在,相当于隐藏的列.ROWNUM是虚拟的,是RDBMS自动设定的,如

select 函数1

Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect.accept.recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回).可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不

模拟jquery封装简单的Select搜索

var Select=(function () { //自定义 select 方法的思路: //1> 定义一个 support 对象. 将需要使用的方法进行处理, 得到方法的能力 //2> 需要使用的可能有兼容性的方法, 定义一个可以完成该方法的函数来替代. 在函数内部进行兼容处理 //3> 定义 select 函数. 首先看是否支持 qsa, 如果支持直接使用. 如果不支持自己再来实现 //native code判断是否是内置方法 var rnative=/\[native code\