目录
- 1:i/O复用技术的作用
- 2: 什么情况下需要此技术
- select 系统调用
- select系统调用原型如下:
- fd_set结构体定义如下:
- 可以使用如下宏访问fd_set 结构体中的位:
- timeout结构体如下:
- 文件描述符就绪条件
- 什么情况下 socket可读?
- 什么情况下socket可写
- 处理带外数据
- poll系统调用
- poll原型
- poll结构体
- epoll系统调用
1:i/O复用技术的作用
能让程序同时监听多个文件描述符,提高程序性能
2: 什么情况下需要此技术
- 客户端需要同时处理多个socket。
- 客户端需要同时处理用户输入和网络连接
- TCP服务器需要同时监听socket 和连接socket。这是最多的应用场景
- 服务器需要同时处理tcp请求和udp请求
- 服务器需要同时监听多个端口或者监听多个文件描述符
需要注意的是 虽然可以同时监听多个文件描述符 ,但是只是按顺序处理其中的一个,因为它本身是阻塞的,如果有多个文件描述符,只能串行处理,按顺序处理。如果要实现并发,就要使用多线程或者多进程的手段
select 系统调用
select系统调用原型如下:
1)nfds参数指定被监听文件描述符总数,因为文件描述符是从0开始计数的,所以此值应该被设置为所有的文件描述符+1.
2)readfds,writefds,exceptfds参数分别指向 可读,可写,异常等时间锁对应的文件描述符集合。通过这三个参数来传入自己感兴趣的文件描述符 ,select调用返回时,内核将修改它们来通知应用程序那些文件描述符已经就绪。
fd_set结构体定义如下:
可以看到fd_set里面仅包含一个整型数组,数组的每一个元素就是一个文件描述符。此结构体能容纳的数量由FD_SETSIZE指定。此举限制了select能同时处理文件描述符的总量.
可以使用如下宏访问fd_set 结构体中的位:
3)timeout 是传递给内核去修改的 。传递给它的数据是设置select的超时事件 ,用来告知应用程序 select等待了多久。但是调用失败后的select是不可信任的。
timeout结构体如下:
两个数据成员如果传递参数为 0 则select 立刻返回 。如果给timeout 传递NULL,则select将已知阻塞,直到有文件描述符就绪。
成功调用返回就绪的文件描述符总数。在超时事件内没有文件描述符就绪,则返回0.select失败的时候返回-1 并设置error 。
如果在select等待期间收到了外部信号 。select立刻返回-1 ,并设置error 为EINTR。
文件描述符就绪条件
什么情况下 socket可读?
- socket内核接受缓冲区里面的字节数大于或等于其低水位标志SO_RCVLOWAT。此时可以无阻塞的读取改socket。并返回读取的字节数。
- socket通信对方关闭连接。此时对该socket的读操作返回0
- 监听的socket上有新的连接请求
- socket上有未处理的错误 。可以通过调用getsockopt来读取和清除该错误。
什么情况下socket可写
- socket内核发送缓冲区可用字节大于或等于其低水位标志SO_SNDLOWAT。此时我们可以无阻塞的写该socket。并且写操作的返回字节数大于0.
- socket写操作被关闭的时候 执行写操作会触发SIGPIPE的信号
- socket使用非阻塞connect 连接成功或者失败(超时)之后
- socket上有未处理的错误 。我们可以使用getsockpot来读取和清除该错误。
select能处理的异常情况只有:socket上接收到带外数据。
下面详细讨论:
处理带外数据
select处理普通数据和带外数据都将使用select返回 。但是socket处于不同的就绪状态。前者处理可读,后者处于异常。下面代码描述select 如果同时处理二者
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
int main( int argc, char* argv[] )
{
if( argc <= 2 )
{
printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
return 1;
}
const char* ip = argv[1];
int port = atoi( argv[2] );
printf( "ip is %s and port is %d\n", ip, port );
int ret = 0;
struct sockaddr_in address;
bzero( &address, sizeof( address ) );
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port );
int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
assert( listenfd >= 0 );
ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
assert( ret != -1 );
ret = listen( listenfd, 5 );
assert( ret != -1 );
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof( client_address );
int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
if ( connfd < 0 )
{
printf( "errno is: %d\n", errno );
close( listenfd );
}
char remote_addr[INET_ADDRSTRLEN];
printf( "connected with ip: %s and port: %d\n", inet_ntop( AF_INET, &client_address.sin_addr, remote_addr, INET_ADDRSTRLEN ), ntohs( client_address.sin_port ) );
char buf[1024];
fd_set read_fds;
fd_set exception_fds;
FD_ZERO( &read_fds );
FD_ZERO( &exception_fds );
int nReuseAddr = 1;
setsockopt( connfd, SOL_SOCKET, SO_OOBINLINE, &nReuseAddr, sizeof( nReuseAddr ) );
while( 1 )
{
memset( buf, '\0', sizeof( buf ) );
/*每次调用后都需要重新设置一下connfd ,因为事件发生后 文件描述符集合会被内核修改*/
FD_SET( connfd, &read_fds );
FD_SET( connfd, &exception_fds );
ret = select( connfd + 1, &read_fds, NULL, &exception_fds, NULL );
printf( "select one\n" );
if ( ret < 0 )
{
printf( "selection failure\n" );
break;
}
/* 对于可读的事件 ,采用普通的recv函数来读取数据 */
if ( FD_ISSET( connfd, &read_fds ) )
{
ret = recv( connfd, buf, sizeof( buf )-1, 0 );
if( ret <= 0 )
{
break;
}
printf( "get %d bytes of normal data: %s\n", ret, buf );
}
/*对于异常事件 采用带MSG_OOB recv函数读取带外数据*/
else if( FD_ISSET( connfd, &exception_fds ) )
{
ret = recv( connfd, buf, sizeof( buf )-1, MSG_OOB );
if( ret <= 0 )
{
break;
}
printf( "get %d bytes of oob data: %s\n", ret, buf );
}
}
close( connfd );
close( listenfd );
return 0;
}
poll系统调用
和sekect调用内部类似 都是在指定事件内轮训一定数量的文件描述符 ,已测试其是否有就绪者
poll原型
fds参数是一个pollfd结构体类型的数据,指定了文件描述符上面的事件:
poll结构体
fd指定文件描述符 ,events表示程序感兴趣那些事件。revents由内核修改,通知应用程序fd实际发生了那些事件。poll支持的事件类型如下表:
liunx并不完全支持普通数据 和 优先级数据。
2)nfds参数指定了监听事件集合fds的大小
typedef unsigned long int nfds_t
3)timeout 指定超时值 单位毫秒 当timeout为-1的时候 poll一直阻塞知道有事件发生 ,当timeout为0的时候 poll调用将立刻返回
返回值含义同select