用途

在处理多个socket套接字的时候,会很自然的遇到一个问题:某个套接字什么时候可读?什么时候可写?哪些套接字是需要关闭的?我们可以回忆一下,一般我们在最开始编写socket程序的时候,send,recv都是同步的,send完后就傻等着recv。这种模式的一个很大的问题是,recv会占用一整个线程,单个线程里没法处理第二个socket。怎么办呢?加线程,每个socket分配一个线程?显然不合适,1000个客户端难道要1000个线程么。select提供了一种方式同时监控多个套接字,执行过程大致为:首先将感兴趣的套接字加入到select的集合中,select函数执行,监控这些个套接字,当其中有套接字产生了事件(可读,可写,异常)或者select超时后,select返回告知调用者,哪些个套接字发送了事件,调用者就对发生了事件的套接字挨个处理,然后继续执行select函数,下个循环开始。

通过这样的一种方式,在同一个线程里面就实现了对多个套接字的读写操作。当然需要注意的是,select调用针对的是文件描述符,不管是socket,pipe,file都是可以被select监控的。

函数说明

#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);

当前linux下selet的每个fd_set最多可以监控1024个文件描述符

参数

  • nfds 要监听的所有的文件描述符中最大值 + 1
  • readfds 要监听其读事件的文件描述符集合
  • writefds 要监听其写事件的文件描述符集合
  • exceptfds 要监听其异常事件的文件描述符集合(比如socket的带外数据就会引起exceptfds事件)
  • timeout 超时时间结构,指定为NULL表示一直阻塞,否则就阻塞指定的时间。

返回值

  • -1 出错
  • = 超时
  • >0 某些套接字产生事件

使用

假设对于一个文件描述符fd,我们想监控其读事件,就将加入到读监控集合中。

fd_set read_set;
FD_ZERO(&read_set);
FD_SET(fd, &read_set); select(maxFd +1, &read_set, NULL, NULL, NULL)

同理,对于写事件,异常事件也是一样的处理方式。

当select调用返回后,如果select返回>0 则可以对指定的文件描述符进行操作。

if (FD_ISSET(fd, &read_set)) {
recv(fd)
}

在程序设计中,一般将select结合while使用。

参考

实例

使用select构建的一个tcp echo程序,程序可以接受多个客户端的链接,并将客户端发送过来的字符串发送回去。

测试

测试环境:CentOS7.1

server端截图:



cleint1截图:



cleint2截图:

代码

/* author: bymzy
* 2017/2/12
* */ #include <sys/select.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <strings.h>
#include <stdlib.h> #define PORT 3333
#define IP "127.0.0.1"
#define BACKLOG 5
#define BUFSIZE 1025 void SetFDSet(int *clientFd, int listenFd, fd_set *read, fd_set *except, int *maxFd)
{
int i = listenFd + 1;
FD_ZERO(read);
FD_ZERO(except); for (;i <= *maxFd; ++i) {
if (clientFd[i] != 0) {
FD_SET(clientFd[i], read);
FD_SET(clientFd[i], except);
}
}
} void CheckFDSet(int *clientFd, int listenFd, fd_set *read, fd_set *except, int *maxFd)
{
int i = listenFd + 1;
int tempMaxFd = *maxFd;
char buf[1024];
int recved = 0;
bool needClose = false; bzero(buf, 1024);
for (;i <= tempMaxFd; ++i) {
bzero(buf, recved);
if (FD_ISSET(clientFd[i], read)) {
recved = recv(clientFd[i], buf, BUFSIZE -1 , 0);
if (recved <= 0) {
perror("Recv error");
close(clientFd[i]);
if (i == tempMaxFd) {
*maxFd = -1;
}
clientFd[i] = 0;
continue;
}
printf("Recv: %s \n", buf);
send(clientFd[i], buf, recved, 0);
} bzero(buf, recved);
if (FD_ISSET(clientFd[i], except)) {
recved = recv(clientFd[i], buf, BUFSIZE - 1, MSG_OOB);
if (recved <= 0) {
perror("Recv error");
close(clientFd[i]);
if (i == tempMaxFd) {
*maxFd = -1;
}
clientFd[i] = 0;
continue;
}
printf("OOB: %s \n", buf);
send(clientFd[i], buf, recved, MSG_OOB);
}
}
} void ChooseMaxFd(int *clientFd, int listenFd, int total, int *maxFd)
{
int i = listenFd;
for (;i < total; ++i) {
if(clientFd[i] != 0) {
*maxFd = clientFd[i];
}
}
} void CloseFd(int *clientFd, int listenFd, int total)
{
int i = listenFd + 1;
for (;i < total; ++i) {
if(clientFd[i] != 0) {
close(clientFd[i]);
clientFd[i] = 0;
}
} } int main(int argc, char* argv[])
{
int err = 0;
int listenFd = -1; do {
listenFd = socket(AF_INET, SOCK_STREAM, 0);
if (listenFd < 0) {
err = errno;
perror("Create listen socket failed");
break;
} struct sockaddr_in bindaddr;
bzero(&bindaddr, 0);
bindaddr.sin_addr.s_addr = inet_addr(IP);
bindaddr.sin_port = htons(PORT);
bindaddr.sin_family = AF_INET;
socklen_t socklen = sizeof(bindaddr);
int reuse = 1; setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); err = bind(listenFd, (sockaddr*)&bindaddr, socklen);
if (err != 0) {
err = errno;
perror("Listen socket bind failed!");
break;
} err = listen(listenFd, BACKLOG);
if (err != 0) {
err = errno;
perror("Listen socket listen failed!");
break;
} /* use select read data */
fd_set read_set;
fd_set exception_set;
struct timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0; int *clientFd = (int*)malloc(sizeof(int) * (FD_SETSIZE + listenFd + 1));
clientFd[listenFd] = listenFd;
int maxFd = -1;
while (1) {
if (maxFd == -1) {
ChooseMaxFd(clientFd, listenFd, FD_SETSIZE + listenFd + 1, &maxFd);
} SetFDSet(clientFd, listenFd, &read_set, &exception_set, &maxFd); if (maxFd != (FD_SETSIZE + listenFd)) {
FD_SET(listenFd, &read_set);
} tv.tv_sec = 5;
tv.tv_usec = 0; err = select(maxFd + 1, &read_set, NULL, &exception_set, &tv);
if (err < 0) {
perror("Select failed");
err = errno;
break;
} else if (err == 0) {
printf("Select timeout!\n");
} else {
if (FD_ISSET(listenFd, &read_set)) {
struct sockaddr_in clientaddr;
socklen_t clientlen;
int tempFd= -1;
tempFd = accept(listenFd, (sockaddr*)&clientaddr, &clientlen);
if (tempFd < 0) {
err = errno;
perror("accept failed!");
break;
} clientFd[tempFd] = tempFd;
if (tempFd > maxFd) {
maxFd = tempFd;
}
printf("Accept client fd: %d\n", tempFd);
} CheckFDSet(clientFd, listenFd, &read_set, &exception_set, &maxFd);
}
} CloseFd(clientFd, listenFd, FD_SETSIZE + listenFd + 1);
free(clientFd); } while(0); if (listenFd != -1) {
close(listenFd);
listenFd = -1;
} return err;
}

Linux select I/O 复用的更多相关文章

  1. Linux网络编程-IO复用技术

    IO复用是Linux中的IO模型之一,IO复用就是进程预先告诉内核需要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程进程处理,从而不会在单个IO上阻塞了.Linux中,提 ...

  2. linux select 与 阻塞( blocking ) 及非阻塞 (non blocking)实现io多路复用的示例

    除了自己实现之外,还有个c语言写的基于事件的开源网络库:libevent http://www.cnblogs.com/Anker/p/3265058.html 最简单的select示例: #incl ...

  3. Linux中的IO复用接口简介(文件监视?)

    I/O复用是Linux中的I/O模型之一.所谓I/O复用,指的是进程预先告诉内核,使得内核一旦发现进程指定的一个或多个I/O条件就绪,就通知进程进行处理,从而不会在单个I/O上导致阻塞. 在Linux ...

  4. linux select 与 阻塞( blocking ) 及非阻塞 (non blocking)实现io多路复用的示例【转】

    转自:https://www.cnblogs.com/welhzh/p/4950341.html 除了自己实现之外,还有个c语言写的基于事件的开源网络库:libevent http://www.cnb ...

  5. linux select函数详解

    linux select函数详解 在Linux中,我们可以使用select函数实现I/O端口的复用,传递给 select函数的参数会告诉内核: •我们所关心的文件描述符 •对每个描述符,我们所关心的状 ...

  6. Linux select 机制深入分析

    Linux select 机制深入分析            作为IO复用的实现方式.select是提高了抽象和batch处理的级别,不是传统方式那样堵塞在真正IO读写的系统调用上.而是堵塞在sele ...

  7. linux—select具体解释

    linux—select具体解释 select系统调用时用来让我们的程序监视多个文件句柄的状态变化的.程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变. 关于文件句柄,事 ...

  8. Linux Select之坑

    最近在写一个demo程序,调用select()来监听socket状态,流程如下: r_set 初始化 timeout 初始化3秒超时 loop{ select(ntfs, &r_set, nu ...

  9. 实现Linux select IO复用C/S服务器代码

    已在ubuntu 下验证可用 服务器端 #include<stdio.h>#include<unistd.h>#include<stdlib.h>#include& ...

随机推荐

  1. mysql优化———第二篇:数据库优化调整参数

    摘要 参数调优内容: 1. 内存利用方面 2. 日志控制方面 3.文件IO分配,空间占用方面 4. 其它相关参数 一  摘要 通过参数提高MYSQL的性能.核心思想如下:         1 提高my ...

  2. linux shell 之if-------用if做判断

    综合网络,略有修改, 一 简介 1 字符串判断 str1 = str2 当两个串有相同内容.长度时为真  str1 != str2 当串str1和str2不等时为真  -n str1 当串的长度大于0 ...

  3. Ext实现简单计算器

    以下是本人原创,如若转载和使用请注明转载地址.本博客信息切勿用于商业,可以个人使用,若喜欢我的博客,请关注我,谢谢!少帅的博客 使用Ext实现简单计算器,网页版实现 1.页面部分calculator. ...

  4. S3C2440的RTC解析

    位二-十进制交换码(BCD)值数据给CPU.这些数据包括年.月.日.星期.时.分和秒的时间信息.RTC单元工作在外部32.768kHz晶振并且可以执行闹钟功能 实时时钟模块保存的数据是DCD码形式. ...

  5. 【转】int && 非常量右值

    C++ 11中引入的右值引用正好可用于标识一个非常量右值.C++ 11中用&表示左值引用,用&&表示右值引用,如: int &&a = 10 右值引用根据其修饰 ...

  6. cocos2d-x 跨平台usleep方法

    #if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) #define usleep(t) Sleep(t) #else #include <unistd.h ...

  7. IOS开发中如何判断程序第一次启动(根据判断结果决定是否显示新手操作引导)

    IOS开发中如何判断程序第一次启动 在软件下载安装完成后,第一次启动往往需要显示一个新手操作引导,来告诉用户怎么操作这个app,这就需要在程序一开始运行就判断程序是否第一次启动,如果是,则显示新手操作 ...

  8. IOS开发-UI学习-UIPageControl(页码控制器)的使用

    UIPageControl即页码控制器,是在翻动图片阅览时下面显示的几个小点,属性设置如下: UIPageControl *pagecontrol = [[UIPageControl alloc]in ...

  9. Java解析JSON文件的方法 (二)

    assets文件夹资源的访问        assets文件夹里面的文件都是保持原始的文件格式,需要用AssetManager以字节流的形式读取文件.       1. 先在Activity里面调用g ...

  10. MySQL5.6-Tomcat7环境变量的配置

    一.MySQL环境变量配置(zip安装):系统-高级系统设置--环境变量--path添加D:\Mysql\bin 找到mysql解压目录下的my-default.ini文件修改 basedir = D ...