1、先看man手册

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);
    
    // 关于第5个参数
        struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };

2. 函数说明:可以同时监控多个文件描述符是否发生了读写或者异常。(有点像windows下的waitformultipleobjects,可以同时等待多个事件)
参数说明:
1)nfds:要监控的文件描述符的最大值加1,这个值不能错。
2)readfds:指向fd_set的指针。这是一个集合,专门用于监视读取数据的。所有需要监控读取数据的描述符都需要放进这个集合中。比如你需要监控4描述符的读取数据,就把4放进这个集合之中。
3)writefds:同上,这里是专门监视写的集合
4)exceptfds:同上,这里是专门监视异常的集合
5)timeout:超时。指向的timeval 结构体。
如果参数设为NULL,则select是阻塞的。
如果不为空,则表示超时时间(当结构体里面的成员都设为0时,表示不阻塞,立即返回)。

重要说明:
第2-4个参数是输入输出参数,select返回时会将哪个文件描述符放到这个集合中。
比如我们监控了fd=5的描述符的读取数据操作,当发生了读取操作时,select则会返回,通过第二个参数可以获取5发生了读操作。用FD_ISSET();实现。 
返回值:
<0:表示出错
>0:等到了我们需要的事件
=0:表示超时了

3.fd_set说明
这是一个文件描述符的集合。
有几个宏可以对这个集合进行操作。
void FD_CLR(int fd, fd_set *set);  // 将集合中指定的fd移除
int FD_ISSET(int fd, fd_set *set); // 判断fd是否在set集合中
void FD_SET(int fd, fd_set *set); // 将fd插入集合中
void FD_ZERO(fd_set *set); // 将集合里面的数据清空

4.实例1:这个例子用来监控标准输入:

#include<sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<stdio.h>
int fun()
{
int ret = , nready = ;
fd_set rset;
FD_ZERO(&rset); // 清空操作
int fd_stdin = fileno(stdin);
int maxfd = fd_stdin;
char readbuf[] = {};
struct timeval tv;
tv.tv_sec = ;
tv.tv_usec = ;
while()
{
FD_SET(fd_stdin, &rset); // 需要重新设置,因为select每次返回会改变里面的值
//nready = select(maxfd+1, &rset, NULL, NULL, &tv);// 有超时
nready = select(maxfd+, &rset, NULL, NULL, NULL);
if(nready <= )
{
//printf();
break;
}
if(FD_ISSET(fd_stdin, &rset)) // rset也是输出参数,这里判断是否是fd_stdin发生了读操作
{
int i = read(fd_stdin, readbuf, sizeof(readbuf));
//printf("recv STDIN read:%d\n", i);
if(i > )
{
printf("STDIN buf:%s\n", readbuf);
}
memset(readbuf, , sizeof(readbuf));
}
} printf("fun() ---, nready = %d\n", nready);
}
int main()
{
printf("stdin:%d, stdout:%d, stderr:%d\n", fileno(stdin), fileno(stdout), fileno(stderr)); // 这个是其他知识
fun();
}

5.实例2(回射服务器):
我实现了一个socket服务器和客户端程序。server可以直接在终端中发送数据给client。client可以在终端中显示,也可以用标准输入直接发送回去。
所以在client的程序中,用select同时监控server的socket和标准输入,当有任意一个发生读取数据时都进行处理。
server.c 接收client的连接,并且收到client的数据原封不动的发送回去。可以接收多个client的连接。能同时处理多个client发送的数据。

先看server.c:

#include<sys/types.h>
#include<sys/socket.h>
#include<sys/select.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<errno.h> #define CLIENTCOUNT 100
int main(int argc, char **argv)
{
int listenfd = socket(AF_INET, SOCK_STREAM, );
if(listenfd < )
{
perror("socket");
return -;
}
unsigned short sport = ;
if(argc == )
{
sport = atoi(argv[]);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
printf("port = %d\n", sport);
addr.sin_port = htons(sport);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(bind(listenfd, (struct sockaddr*)&addr, sizeof(addr)) < )
{
perror("bind");
return -;
}
if(listen(listenfd, ) < )
{
perror("listen");
return -;
}
struct sockaddr_in connaddr;
int len = sizeof(connaddr); int i = , ret = ;
int client[CLIENTCOUNT];
for(i = ; i<CLIENTCOUNT; i++) // 将连接上client的描述符放到一个数组里,这里对数组进行初始化。-1表示空闲
client[i] = -;
fd_set rset;
fd_set allset;
FD_ZERO(&rset);
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
int maxfd = listenfd;
int nready = ;
char buf[] = {};
/*
这里很巧妙的是用到了两个fd_set。因为rset会发生改变。我们管理处理好allset就好了。
每次循环开始都将allset赋值给rset。
当有client连接时,加入到allset中。有client关闭时就从allset中移除
*/
while()
{
rset = allset;
nready = select(maxfd+, &rset, NULL, NULL, NULL);
if(nready == -)
{
perror("select");
return -;
}
if(nready == )
continue;
if(FD_ISSET(listenfd, &rset)) // 这里用来监听 监听套接字,就是listenfd
{
int conn = accept(listenfd, (struct sockaddr*)&connaddr, &len);
if(conn < )
{
perror("accept");
return -;
}
char strip[] = {};
char *ip = inet_ntoa(connaddr.sin_addr);
strcpy(strip, ip);
printf("new client connect, conn:%d,ip:%s, port:%d\n", conn, strip,ntohs(connaddr.sin_port)); FD_SET(conn, &allset); // 将连接上的套接字加入到allset中去
if(maxfd < conn) // update maxfd
maxfd = conn; int i = ;
for(i = ; i<CLIENTCOUNT; i++)
{
if(client[i] == -)
{
client[i] = conn; // 将conn存进client数组中取去
break;
}
}
if(i == CLIENTCOUNT)
{
printf("to many client connect\n");
exit();
}
if(--nready <= )
continue;
} for(i = ; i < CLIENTCOUNT; i++) // 这里处理客户端发送过来的数据,逐个处理所有的套接字
{
if(client[i] == -)
continue;
if(FD_ISSET(client[i], &rset))
{
ret = read(client[i], buf, sizeof(buf));
if(ret == -)
{
perror("read");
return -;
}
else if(ret == ) // 表示client关闭了
{
printf("client close remove:%d\n", client[i]);
FD_CLR(client[i], &allset); // 在allset中移除它
close(client[i]); // 同时也要关闭
client[i] = -; // 要在这里移除,这步非常重要。
} // fputs(buf, stdout);
printf("client%d:%s\n", client[i], buf);
write(client[i], buf, sizeof(buf)); // 发送数据给client
memset(buf, , sizeof(buf));
if(--nready <= )
continue;
}
}
} close(listenfd);
return ;
}

关于while(1)循环里面的理解:
1)先将listenfd加入监听队列
2)如果有client连接,就将连接套接字加入allset
3)然后重新监听rset(allset会赋值给它)。这样就相当于监听了client和listenfd
4)如果select又返回,若是listenfd,则重复上面的步骤
5)如果是client发来了数据,read返回不为0则打印出来,再发送回去。若read返回0,则表示client关闭了。就把对应文件描述符从allset中移除。同时关闭它。也要更新那个数组client。如果不更新for循环会出错

接着来看客户端:
client.c:

#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/select.h> #include<stdlib.h>
#include<stdio.h>
#include<string.h> void select_test(int conn)
{
int ret = ;
fd_set rset;
FD_ZERO(&rset); int nready;
int maxfd = conn;
int fd_stdin = fileno(stdin);
if(fd_stdin > maxfd)
{
maxfd = fd_stdin;
} int len = ;
char readbuf[] = {};
char writebuf[] = {};
while()
{
FD_ZERO(&rset);
FD_SET(fd_stdin, &rset);
FD_SET(conn, &rset);
nready = select(maxfd+, &rset, NULL, NULL, NULL); // 同时监听标准输入和服务器。
if(nready == -)
{
perror("select");
exit();
}
else if(nready == )
{
continue;
} if(FD_ISSET(conn, &rset)) // 服务器来了数据
{
ret = read(conn, readbuf, sizeof(readbuf));
if(ret == )
{
printf("server close1\n");
break;
}
else if(- == ret)
{
perror("read1");
break;
} fputs(readbuf, stdout);
memset(readbuf, , sizeof(readbuf));
} if(FD_ISSET(fd_stdin, &rset)) // 标准输入来了数据就发送给server
{
read(fd_stdin, writebuf, sizeof(writebuf));
len = strlen(writebuf);
ret = write(conn, writebuf, len);
if(ret == )
{
printf("server close3\n");
break;
}
else if(- == ret)
{
perror("write");
break;
} memset(writebuf, , sizeof(writebuf));
}
}
} int sockfd = ;
int main(int argc, char **argv)
{
sockfd = socket(AF_INET, SOCK_STREAM, );
if(sockfd < )
{
perror("socket");
return -;
} unsigned short sport = ;
if(argc == )
{
sport = atoi(argv[]);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
printf("port = %d\n", sport);
addr.sin_port = htons(sport);
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if(connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < )
{
perror("connect");
return -;
} struct sockaddr_in addr2;
socklen_t len = sizeof(addr2);
if(getpeername(sockfd, (struct sockaddr*)&addr2, &len) < )
{
perror("getsockname");
return -;
} printf("Server: port:%d, ip:%s\n", ntohs(addr2.sin_port), inet_ntoa(addr2.sin_addr)); select_test(sockfd); close(sockfd);
return ;
}

运行:

先运行服务器程序,再启动客户端程序,通过标准输入给server发送数据。server会回射回来。

这个服务器可以同时处理多个客户端的数据。

6. 用select可以实现在单进程中同时处理多个文件描述符的事件。

7.读、写、异常事件发生的条件

可读:
1)套接口缓冲区有数据可读
2)连接的读一半关闭,即接收到FIN段,读操作返回0
3)如果是监听套接口,已完成连接队列不为空时
4)套接口上发生了一个错误待处理,错误可以同getsetopt指定SO_ERROR选项来获取

可写:
1)套接口发送缓冲区有空间容纳数据
2)连接的写一半关闭,即接收到RET段,再次调用write操作
3)套接口上发生了一个错误待处理,错误可以同getsetopt指定SO_ERROR选项来获取

异常:
1)套接口存在带外数据

Linux下select的用法--实现一个简单的回射服务器程序的更多相关文章

  1. linux下堆溢出unlink的一个简单例子及利用

    最近认真学习了下linux下堆的管理及堆溢出利用,做下笔记:作者作为初学者,如果有什么写的不对的地方而您又碰巧看到,欢迎指正. 本文用到的例子下载链接https://github.com/ctfs/w ...

  2. UNIX网络编程——使用select函数的TCP和UDP回射服务器程序

    服务器程序: #include <sys/wait.h> #include <string.h> #include <string.h> #include < ...

  3. Linux下select, poll和epoll IO模型的详解

    http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll 介绍 Epoll 可是当前在 Linux 下开发大规模并发网络程序的热 ...

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

    Linux下select, poll和epoll IO模型的详解 原文:http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll ...

  5. linux select函数:Linux下select函数的使用详解【转】

    本文转载自;http://www.bkjia.com/article/28216.html Linux下select函数的使用 Linux下select函数的使用 一.Select 函数详细介绍 Se ...

  6. 在Linux下如何使用GCC编译程序、简单生成 静态库及动态库

      最近在编写的一个Apache  kafka 的C/C++客户端,,在看他写的 example中,他的编译是用librdkafka++.a和librdkafka.a    静态库编译的,,,而我们这 ...

  7. 网络篇:linux下select、poll、epoll之间的区别总结

    select.poll.epoll之间的区别总结 select,poll,epoll都是IO多路复用的机制.I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪 ...

  8. Linux内核中的信号机制--一个简单的例子【转】

    本文转载自:http://blog.csdn.net/ce123_zhouwei/article/details/8562958 Linux内核中的信号机制--一个简单的例子 Author:ce123 ...

  9. Linux下find命令用法详解

    Linux下find命令用法详解   学神VIP烟火 学神IT教育:XueGod-IT   最负责任的线上直播教育平台   本文作者为VIP学员 烟火   第一部分:根据文件名查找   1.在当前目录 ...

随机推荐

  1. linux 更新源miss问题

    1.之前新装的linuxMint 执行 apt-get install vim 安装失败 发现原因是源更新失败导致,后来执行apt-get update 发现老是获取失败,查了google总结出以下解 ...

  2. Varnish 实战项目

    实现基于Keepalived+Haproxy+Varnish+LNMP企业级架构 原理:缓存,又称加速器,用于加速运行速度较快的设备与较慢设备之间的通信.基于程序的运行具有局部性特征其能实现加速的功能 ...

  3. 一:MySQL数据库的性能的影响分析及其优化

    MySQL数据库的性能的影响分析及其优化 MySQL数据库的性能的影响 一. 服务器的硬件的限制 二. 服务器所使用的操作系统 三. 服务器的所配置的参数设置不同 四. 数据库存储引擎的选择 五. 数 ...

  4. Maven依赖解析

    本文将记录Maven工程中依赖解析机制,内容包括: Maven依赖基本结构 从仓库解析依赖的机制 依赖传递性解析实例 1. Maven依赖基本结构 上篇文章记录了Maven依赖的聚合与继承,POM中依 ...

  5. [转载] 基于Redis实现分布式消息队列

    转载自http://www.linuxidc.com/Linux/2015-05/117661.htm 1.为什么需要消息队列?当系统中出现“生产“和“消费“的速度或稳定性等因素不一致的时候,就需要消 ...

  6. Python之上下文管理

    http://www.cnblogs.com/coser/archive/2013/01/28/2880328.html 上下文管理协议为代码块提供包含初始化和清理操作的上下文环境.即便代码块发生异常 ...

  7. Python 学习之路3

    接下来把剩下的实验一起写上去 实验2 写一个学生类,属性有学号,姓名,成绩(三门),方法有输出,求平均成绩. 设计思路: 1.         先写一个学生类,并向里面写一个求平均值和输出信息的方法. ...

  8. HTML5能否会成为Web技术的核心?

    谁会成为HTML5后继者? 那么会有一个HTML6吗? Jaffe表示,网上支付可能会推动进行这样的全面修订,以期能为网上支付来提供一个统一方式.如果大家将之称为HTML 6,那么HTML 6是极有可 ...

  9. SpringBoot错误求解决

    .   ____          _            __ _ _ /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \( ( )\___ | '_ | '_| | ...

  10. 数细胞-swust oj

    数细胞(0964) 一矩形阵列由数字0到9组成,数字1到9代表细胞,细胞的定义为沿细胞数字上下左右还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数.编程需要用到的队列及其相关函数已经实现,你只需要完 ...