linux io复用命令,Linux下的I/O复用

作者:2014-03-05

在早期的网络编程中,服务端的处理方式为:当有客户端连接上来时,就为客户端创建一个单独的进程或线程,用于处理客户端请求。由于系统中能创建的进程或线程数有限并且只能监听一个socket,所以这种处理方式的性能并不是太高。那么有什么方式能提高服务端的性能呢?答案是—I/O复用。

Linux下实现I/O复用的系统调用有:select、poll、epoll等(还有pselect等),下面分别介绍~

一 I/O复用的意义

I/O复用使程序能同时监听多个文件描述符(file descriptor),程序会在I/O复用系统调用处等待,直到被监视的文件描述符有一个或多个发生状态改变。在Linux下,文件描述符其实就是一个整数,我们比较熟悉的有0(标准输入stdin)、1(标准输出stdout)、2(标准错误输出stderr),其他的还有文件句柄FILE、套接字socket等。由于文章主要讨论网络相关的内容,所以文中文件描述符指的是socket套接字。

二 I/O复用的使用场景

程序需要同时处理多个socket连接

程序需要同时处理用户输入(文件描述符的值为0)和网络连接

TCP服务器需要同时处理监听socket和连接socket

服务器需要同时处理TCP请求和UDP请求

服务器需要要同时监听多个端口或多个服务

三 I/O复用的注意事项

I/O虽然能同时监听多个文件描述符,但是它本身是阻塞的(在select、poll、epoll系统调用出阻塞,直到有监视的文件描述符发生状态变化,并不是阻塞在I/O系统调用)

当多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能依次处理其中的每一个文件描述符,这使得服务器程序看起来是串行处理的。这时如果要实现并发,只有使用多进程或多线程等编程手段。

四 socket文件描述符就绪条件

1 socket可读

2 socket可写

3 socket异常

五 select系统调用

1 作用

在一段指定的时间内,监听用户感兴趣的文件描述符的可读、可写、异常事件

2 select函数介绍

select函数原型如下:

int select(int nfds, fd_set *readfds, fd_set *writefds,

fd_set *exceptfds, struct timeval *timeout);

select函数说明:

应用程序调用select函数时,通过readfds、writefds、exceptfds传入感兴趣的文件描述符,内核将修改它们来通知应用程序哪些文件描述符已经就绪。

select函数参数说明:

nfds: 被监听的文件描述符的总数,因为是用位记录要监听的文件描述符,比如需要监听文件描述符2,则表示要记录第2位,则会设置fd_set中的第2位,故最大值为2 + 1 =3,所以nfds通常设置为监听的所有文件描述符中的最大值加1,因为文件描述符、记录位是从0开始计数的。比如有a,b,c三个要监听的文件描述符,并且a的值最大,则nfds应该设置为a + 1

readfds: 可读的文件描述符集合

writefds: 可写的文件描述符集合

exceptfds: 异常的文件描述符集合

timeout: 设置select函数的超时时间

select返回值:

select函数成功时,返回就绪(可读、可写、异常)文件描述符的总数

如果在超时时间timeout内没有任何文件描述符就绪,则select函数返回0

select函数失败时,返回-1,并设置errno

如果在select函数等待期间,程序接收到信号,则select函数立即返回-1,并设置errno为EINTR

fd_set说明:

fd_set结构体仅包含一个整形数组,该数组的每一个元素的每一位(bit)标记一个文件描述符,由于位操作过于麻烦,所以Linux中提供下面一组函数来操作fd_set:

void FD_ZERO(fd_set *set);//清除set的所有位

void FD_SET(int fd, fd_set *set);//设置set的第fd位

void FD_CLR(int fd, fd_set *set);//清除set的第fd位

void FD_ISSET(int fd, fd_set *set);//测试set的第fd为是否被设置
**timeval说明:**

struct timeval {

long tv_sec; // 秒

long tv_usec; // 微秒

}; select函数的最后一个参数timeout是timeval类型的,用来设置select函数的超时时间,timeout是一个timeval类型的指针,所以内核能修改修改它,从而告诉应用程序select函数等待了多长时间,不过我们不能完全信任select函数调用后返回的timeout值,比如调用失败时,timeout的值是不确定的。 通过timeval的定义,我们可以发现,select函数给我们提供了一个微秒级别的定时器。如果给timeout变量的tv_sec和tv_usec都设置成0,则select函数会立即返回。如果将timeout设置为NULL,则select函数将一直阻塞,直到某个文件描述符就绪。

3 select函数使用示例

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

int main( int argc, char **argv )

{

//判断输入参数的合法性 if ( argc <= 1 )

{

printf("usage: %s port\n", argv[0]);

return -1;

}

int port = atoi(argv[1]);

printf("listen at port:%d.\n", port);

//服务端地址 struct sockaddr_in server_addr;

memset( &server_addr, 0, sizeof(server_addr) );

server_addr.sin_family = AF_INET;

server_addr.sin_port = htons(port);

server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

//创建套接字 int server_fd = socket( AF_INET, SOCK_STREAM, 0 );

if ( -1 == server_fd )

{

printf("create socket failed.\n");

return -1;

}

//绑定套接字 int ret = bind( server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr) );

if ( -1 == ret )

{

printf("bind failed.\n");

return -1;

}

//监听 ret = listen( server_fd, 5 );

if ( -1 == ret )

{

printf("listen failed.\n");

return -1;

}

//客户端地址 struct sockaddr_in client_addr;

memset( &client_addr, 0, sizeof(client_addr) );

socklen_t client_addr_len = sizeof(client_addr);

//接收客户端连接 为了方便此处只接收一个来自客户端的请求 int talk_fd = accept( server_fd, (struct sockaddr*)&client_addr, &client_addr_len );

if ( -1 == talk_fd )

{

printf("accept failed.\n");

close( server_fd );

return -1;

}

//接收信息缓冲区 char buff[1204];

memset( buff, 0, sizeof(buff) );

fd_set read_fd; //关注的可读文件描述符集合 fd_set exception_fd; //关注的异常文件描述符集合 FD_ZERO( &read_fd ); //清空可读文件描述符集合的所有位 FD_ZERO( &exception_fd ); //清空异常文件描述符集合的所有位

while(1)

{

//清空信息缓冲区 memset( buff, 0, sizeof(buff) );

//每次调用select前需要重新设置文件描述符集合 因为事件发生之后文件描述符集合将被内核修改 FD_SET( talk_fd, &read_fd ); //将客户端、服务端的socket描述符加入可读描述符集合 FD_SET( talk_fd, &exception_fd );//将客户端、服务端的socket描述符加入异常描述符集合

//阻塞在select调用 直到可读、异常描述符集合发生状态变化 ret = select( talk_fd + 1, &read_fd, NULL, &exception_fd, NULL );

if ( -1 == ret )

{

printf("select failed.\n");

return -1;

}

//对于可读事件 采用普通的recv函数读取数据 if ( FD_ISSET( talk_fd, &read_fd ) )

{

ret = recv( talk_fd, buff, sizeof(buff) - 1, 0 );

if ( ret < 0 )

{

printf("recv read_fd failed.\n");

break;

}

//输出接收到的普通数据 printf( "get %d bytes of normal data: %s\n", ret, buff );

}

//对于异常事件 采用带MSG_OOB标志的recv函数接收数据 if ( FD_ISSET( talk_fd, &exception_fd ) )

{

ret = recv( talk_fd, buff, sizeof(buff) - 1, MSG_OOB );

if ( ret < 0 )

{

printf("recv exception_fd failed.\n");

break;

}

//输出接收到的异常数据 printf( "get %d bytes of oob data: %s\n", ret, buff );

}

}

close( talk_fd );

close( server_fd );

return 0;

}

编译完成后,可以尝试使用telnet连接该服务端,进行接下来的测试~

作者:hahaya

出处:http://hahaya.github.com/linux-io-multiplexing

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值