当前位置: 技术问答>linux和unix
套接字I/O模型 请教
来源: 互联网 发布时间:2015-10-28
本文导语: 在windows的socket编程有 select模型, WSAAsyncSelect ,WSAEventSelect , 重叠模型,完成端口模型 等几种IO模型 特别是重叠模型,完成端口模型基本指针对 服务器在任何给定的时间,都会有大量的处理,简单点就是说在性...
在windows的socket编程有
select模型, WSAAsyncSelect ,WSAEventSelect , 重叠模型,完成端口模型
等几种IO模型
特别是重叠模型,完成端口模型基本指针对
服务器在任何给定的时间,都会有大量的处理,简单点就是说在性能上得到提高。
但是看了一下linux下面的网络编程,好像没有这方面的介绍
本人初学,可能孤陋寡闻了
select模型, WSAAsyncSelect ,WSAEventSelect , 重叠模型,完成端口模型
等几种IO模型
特别是重叠模型,完成端口模型基本指针对
服务器在任何给定的时间,都会有大量的处理,简单点就是说在性能上得到提高。
但是看了一下linux下面的网络编程,好像没有这方面的介绍
本人初学,可能孤陋寡闻了
|
非阻塞通信方法:
将文件管道通过fcntl()设为非阻塞通信方式,每隔一端时间对他们实行一次轮询,以判断是否可以进行读写操作。这种方式的缺点是费用太高,大部分资源浪费在轮询上。
子进程方法:
应用多个子进程,每一个对一个单工阻塞方式通信。所有子进程通过IPC和父进程进行通信。父进程掌管所有信息。这种方式的缺点是实现复杂,而且由于IPC在各个操作系统平台上并不完全一致,会导致可移植性降低。
信号驱动(SIGIO)的异步I/O方法:
首先,异步I/O是基于信号机制的,并不可靠。其次单一的信号不足以提供更多的信息来源。还是需要辅助以其他的手段,实现上有很高的难度。
select ()方法:
在BSD中提供了一种可以对多路I/O进行阻塞式查询的方法--select()。它提供同时对多个I/O描述符进行阻塞式查询的方法,利用它,我们可以很方便的实现多路复用。根据统一UNIX规范的协议,POSIX也采用了这种方法,因此,我们可以在大多数操作系统中使用select方法。
使用专门的I/O多路复用器:
在"UNIX SYSTEM V Programmer's Guide: STREAMS"一书中详细的说明了构造和使用多路复用器的方法。这里就不再详述了。
我们下面分别讨论多路I/O的两种实现方法:
1. 非阻塞通信方法
对一个文件描述符指定的文件或设备, 有两种工作方式: 阻塞与非阻塞。所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待状态, 直到有东西可读或者可写为止。而对于非阻塞状态, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待。缺省情况下, 文件描述符处于阻塞状态。在实现聊天室时, server 需要轮流查询与各client 建立的 socket, 一旦可读就将该 socket 中的字符读出来并向所有其他client 发送。并且, server 还要随时查看是否有新的 client 试图建立连接,这样, 如果 server 在任何一个地方阻塞了, 其他 client 发送的内容就会受到影响,得不到服务器的及时响应。新 client 试图建立连接也会受到影响。所以我们在这里不能使用缺省的阻塞的文件工作方式,而需要将文件的工作方式变成非阻塞方式。
在UNIX下,函数fcntl()可以用来改变文件I/O操作的工作方式,函数描述如下:
fcntl( sockfd, F_SETFL, O_NONBLOCK);
// sockfd 是要改变状态的文件描述符。
// F_SETFL 表明要改变文件描述符的状态
// O_NONBLOCK 表示将文件描述符变为非阻塞的。
为了节省篇幅我们使用自然语言描述聊天室 server :
while ( 1) {
if 有新连接 then 建立并记录该新连接;
for ( 所有的有效连接)
begin
if 该连接中有字符可读 then
begin
读入字符串;
for ( 所有其他的有效连接)
begin
将该字符串发送给该连接;
end;
end;
end;
end。
将文件管道通过fcntl()设为非阻塞通信方式,每隔一端时间对他们实行一次轮询,以判断是否可以进行读写操作。这种方式的缺点是费用太高,大部分资源浪费在轮询上。
子进程方法:
应用多个子进程,每一个对一个单工阻塞方式通信。所有子进程通过IPC和父进程进行通信。父进程掌管所有信息。这种方式的缺点是实现复杂,而且由于IPC在各个操作系统平台上并不完全一致,会导致可移植性降低。
信号驱动(SIGIO)的异步I/O方法:
首先,异步I/O是基于信号机制的,并不可靠。其次单一的信号不足以提供更多的信息来源。还是需要辅助以其他的手段,实现上有很高的难度。
select ()方法:
在BSD中提供了一种可以对多路I/O进行阻塞式查询的方法--select()。它提供同时对多个I/O描述符进行阻塞式查询的方法,利用它,我们可以很方便的实现多路复用。根据统一UNIX规范的协议,POSIX也采用了这种方法,因此,我们可以在大多数操作系统中使用select方法。
使用专门的I/O多路复用器:
在"UNIX SYSTEM V Programmer's Guide: STREAMS"一书中详细的说明了构造和使用多路复用器的方法。这里就不再详述了。
我们下面分别讨论多路I/O的两种实现方法:
1. 非阻塞通信方法
对一个文件描述符指定的文件或设备, 有两种工作方式: 阻塞与非阻塞。所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待状态, 直到有东西可读或者可写为止。而对于非阻塞状态, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待。缺省情况下, 文件描述符处于阻塞状态。在实现聊天室时, server 需要轮流查询与各client 建立的 socket, 一旦可读就将该 socket 中的字符读出来并向所有其他client 发送。并且, server 还要随时查看是否有新的 client 试图建立连接,这样, 如果 server 在任何一个地方阻塞了, 其他 client 发送的内容就会受到影响,得不到服务器的及时响应。新 client 试图建立连接也会受到影响。所以我们在这里不能使用缺省的阻塞的文件工作方式,而需要将文件的工作方式变成非阻塞方式。
在UNIX下,函数fcntl()可以用来改变文件I/O操作的工作方式,函数描述如下:
fcntl( sockfd, F_SETFL, O_NONBLOCK);
// sockfd 是要改变状态的文件描述符。
// F_SETFL 表明要改变文件描述符的状态
// O_NONBLOCK 表示将文件描述符变为非阻塞的。
为了节省篇幅我们使用自然语言描述聊天室 server :
while ( 1) {
if 有新连接 then 建立并记录该新连接;
for ( 所有的有效连接)
begin
if 该连接中有字符可读 then
begin
读入字符串;
for ( 所有其他的有效连接)
begin
将该字符串发送给该连接;
end;
end;
end;
end。
|
由于判断是否有新连接, 是否可读都是非阻塞的, 因此每次判断,不管有还是没有, 都会马上返回。 这样,任何一个 client 向 server 发送字符或者试图建立新连接, 都不会对其他 client 的活动造成影响。
对 client 而言, 建立连接之后, 只需要处理两个文件描述符, 一个是建立了连接的 socket 描述符, 另一个是标准输入和 server 一样, 如果使用阻塞方式的话, 很容易因为其中一个暂时没有输入而影响另外一个的读入。因此将它们都变成非阻塞的, 然后client 进行如下动作:
while ( 不想退出)
begin
if ( 与 server 的连接有字符可读)
begin
从该连接读入, 并输出到标准输出上去。
End;
if ( 标准输入可读)
Begin
从标准输入读入, 并输出到与 server 的连接中去。
End;
End。
上面的读写分别调用这样两个函数:
read( userfd[i], line, MAX_LINE);
// userfd[i] 是指第 i 个 client 连接的文件描述符。
// line 是指读出的字符存放的位置。
// MAX_LINE 是一次最多读出的字符数。
// 返回值是实际读出的字符数。
write( userfd[j], line, strlen( line));
// userfd[j] 是第 j 个 client 的文件描述符。
// line 是要发送的字符串。
// strlen( line) 是要发送的字符串长度。
分析上面的程序可以知道, 不管是 server 还是 client, 它们都不停的轮流查询各个文件描述符, 一旦可读就读入并进行处理。 这样的程序, 不停的在执行, 只要有CPU 资源, 就不会放过。因此对系统资源的消耗非常大。server 或者 client 单独执行时, CPU 资源的 98% 左右都被其占用。极大的消耗了系统资源。
因此,虽然我们不希望在某一个用户没有反应时阻塞其他的用户,但我们却应该在没有任何用户有反应的情况之下停止程序的运行,让出抢占的系统资源,进入阻塞状态。有没有这种方法呢?现在的UNIX系统中都提供了select方法,具体实现方式如下:
使用 select 判断一组文件描述符中是否有一个可读(写), 如果没有就阻塞, 直到有一个的时候就被唤醒。select 方法中, 所有文件描述符都是阻塞的。我们先看比较简单的 client 的实现:
由于 client 只需要处理两个文件描述符, 因此, 需要判断是否有可读写的文件描述符只需要加入两项:
FD_ZERO( sockset);
// 将 sockset 清空
FD_SET( sockfd, sockset);
// 把 sockfd 加入到 sockset 集合中
FD_SET( 0, sockset);
// 把 0 (标准输入) 加入到 sockset 集合中
然后 client 的处理如下:
while ( 不想退出) {
select( sockfd+1, &sockset, NULL, NULL, NULL);
// 此时该函数将阻塞直到标准输入或者 sockfd 中有一个可读为止
// 第一个参数是 0 和 sockfd 中的最大值加一
// 第二个参数是 读集, 也就是 sockset
// 第三, 四个参数是写集和异常集, 在本程序中都为空
// 第五个参数是超时时间, 即在指定时间内仍没有可读, 则出错
// 并返回。 当这个参数为NULL 时, 超时时间被设置为无限长。
// 当 select 因为可读返回时, sockset 中包含的只是可读的
// 那些文件描述符。
if ( FD_ISSET( sockfd, &sockset)) {
// FD_ISSET 这个宏判断 sockfd 是否属于可读的文件描述符
从 sockfd 中读入, 输出到标准输出上去。
}
if ( FD_ISSET( 0, &sockset)) {
// FD_ISSET 这个宏判断 sockfd 是否属于可读的文件描述符
从标准输入读入, 输出到 sockfd 中去。
}
重新设置 sockset。 (即将 sockset 清空, 并将 sockfd 和 0 加入)
}
下面看 server 的情况:
设置 sockset 如下:
FD_ZERO( sockset);
FD_SET( sockfd, sockset);
for ( 所有有效连接)
FD_SET( userfd[i], sockset);
}
maxfd = 最大的文件描述符号 + 1;
server 处理如下:
while ( 1) {
select( maxfd, &sockset, NULL, NULL, NULL);
if ( FD_ISSET( sockfd, &sockset)) {
// 有新连接
建立新连接, 并将该连接描述符加入到 sockset 中去了。
}
for ( 所有有效连接) {
if ( FD_ISSET ( userfd[i], &sockset)) {
// 该连接中有字符可读
从该连接中读入字符, 并发送到其他有效连接中去。
}
}
重新设置 sockset;
}
性能比较
由于采用 select 机制, 因此当没有字符可读时, 程序处于阻塞状态,最小程度的占用CPU 资源, 在同一台机器上执行一个 server 和若干个client 时, 系统负载只有 0。1 左右, 而采用原来的非阻塞通信方法, 只运行一个 server, 系统负载就可以达到 1。5 左右。 因此我们推荐使用 select。
------------------------------------
努力学习xslt中...
对 client 而言, 建立连接之后, 只需要处理两个文件描述符, 一个是建立了连接的 socket 描述符, 另一个是标准输入和 server 一样, 如果使用阻塞方式的话, 很容易因为其中一个暂时没有输入而影响另外一个的读入。因此将它们都变成非阻塞的, 然后client 进行如下动作:
while ( 不想退出)
begin
if ( 与 server 的连接有字符可读)
begin
从该连接读入, 并输出到标准输出上去。
End;
if ( 标准输入可读)
Begin
从标准输入读入, 并输出到与 server 的连接中去。
End;
End。
上面的读写分别调用这样两个函数:
read( userfd[i], line, MAX_LINE);
// userfd[i] 是指第 i 个 client 连接的文件描述符。
// line 是指读出的字符存放的位置。
// MAX_LINE 是一次最多读出的字符数。
// 返回值是实际读出的字符数。
write( userfd[j], line, strlen( line));
// userfd[j] 是第 j 个 client 的文件描述符。
// line 是要发送的字符串。
// strlen( line) 是要发送的字符串长度。
分析上面的程序可以知道, 不管是 server 还是 client, 它们都不停的轮流查询各个文件描述符, 一旦可读就读入并进行处理。 这样的程序, 不停的在执行, 只要有CPU 资源, 就不会放过。因此对系统资源的消耗非常大。server 或者 client 单独执行时, CPU 资源的 98% 左右都被其占用。极大的消耗了系统资源。
因此,虽然我们不希望在某一个用户没有反应时阻塞其他的用户,但我们却应该在没有任何用户有反应的情况之下停止程序的运行,让出抢占的系统资源,进入阻塞状态。有没有这种方法呢?现在的UNIX系统中都提供了select方法,具体实现方式如下:
使用 select 判断一组文件描述符中是否有一个可读(写), 如果没有就阻塞, 直到有一个的时候就被唤醒。select 方法中, 所有文件描述符都是阻塞的。我们先看比较简单的 client 的实现:
由于 client 只需要处理两个文件描述符, 因此, 需要判断是否有可读写的文件描述符只需要加入两项:
FD_ZERO( sockset);
// 将 sockset 清空
FD_SET( sockfd, sockset);
// 把 sockfd 加入到 sockset 集合中
FD_SET( 0, sockset);
// 把 0 (标准输入) 加入到 sockset 集合中
然后 client 的处理如下:
while ( 不想退出) {
select( sockfd+1, &sockset, NULL, NULL, NULL);
// 此时该函数将阻塞直到标准输入或者 sockfd 中有一个可读为止
// 第一个参数是 0 和 sockfd 中的最大值加一
// 第二个参数是 读集, 也就是 sockset
// 第三, 四个参数是写集和异常集, 在本程序中都为空
// 第五个参数是超时时间, 即在指定时间内仍没有可读, 则出错
// 并返回。 当这个参数为NULL 时, 超时时间被设置为无限长。
// 当 select 因为可读返回时, sockset 中包含的只是可读的
// 那些文件描述符。
if ( FD_ISSET( sockfd, &sockset)) {
// FD_ISSET 这个宏判断 sockfd 是否属于可读的文件描述符
从 sockfd 中读入, 输出到标准输出上去。
}
if ( FD_ISSET( 0, &sockset)) {
// FD_ISSET 这个宏判断 sockfd 是否属于可读的文件描述符
从标准输入读入, 输出到 sockfd 中去。
}
重新设置 sockset。 (即将 sockset 清空, 并将 sockfd 和 0 加入)
}
下面看 server 的情况:
设置 sockset 如下:
FD_ZERO( sockset);
FD_SET( sockfd, sockset);
for ( 所有有效连接)
FD_SET( userfd[i], sockset);
}
maxfd = 最大的文件描述符号 + 1;
server 处理如下:
while ( 1) {
select( maxfd, &sockset, NULL, NULL, NULL);
if ( FD_ISSET( sockfd, &sockset)) {
// 有新连接
建立新连接, 并将该连接描述符加入到 sockset 中去了。
}
for ( 所有有效连接) {
if ( FD_ISSET ( userfd[i], &sockset)) {
// 该连接中有字符可读
从该连接中读入字符, 并发送到其他有效连接中去。
}
}
重新设置 sockset;
}
性能比较
由于采用 select 机制, 因此当没有字符可读时, 程序处于阻塞状态,最小程度的占用CPU 资源, 在同一台机器上执行一个 server 和若干个client 时, 系统负载只有 0。1 左右, 而采用原来的非阻塞通信方法, 只运行一个 server, 系统负载就可以达到 1。5 左右。 因此我们推荐使用 select。
------------------------------------
努力学习xslt中...
|
你看看那本经典书籍 unix网络编程 上面就谈到了i/o模型。
好像有5种I/O模型吧。
大概有这几种方法:
select/poll/epoll/AIO/SIGIO
如果是BSD类型的UNIX,还有Kqueue
好像有5种I/O模型吧。
大概有这几种方法:
select/poll/epoll/AIO/SIGIO
如果是BSD类型的UNIX,还有Kqueue
|
我这有两个自己写的学习用的程序,你需要的话可以发给你,留下你的e-mail
------------------------------------
努力学习xslt中...
------------------------------------
努力学习xslt中...
|
linux有poll,epoll
|
poll和select在某些系统上面是等价的(仅仅是接口不一样,实现代码根本就是一个东西)
立即返回的是非阻塞式socket,这个和select配合起来才能真正起作用,对于阻塞式socket,select可以说是没有意义的。
send、和recv属于慢速调用,没有数据的时候会死等……
立即返回的是非阻塞式socket,这个和select配合起来才能真正起作用,对于阻塞式socket,select可以说是没有意义的。
send、和recv属于慢速调用,没有数据的时候会死等……
|
对于阻塞式socket,select可以说是没有意义的。
--------------------------------------------
这好像不对吧。
对于select管理多个socket时,不管这些socket是否阻塞还是不阻塞的,
select都是为了知道有读或写的事件发生,然后再决定要对那个socket读或写,
这至少比轮讯每个socket有效率多了。
linux下除了select还有epoll,听说比select的效率高许多。
--------------------------------------------
这好像不对吧。
对于select管理多个socket时,不管这些socket是否阻塞还是不阻塞的,
select都是为了知道有读或写的事件发生,然后再决定要对那个socket读或写,
这至少比轮讯每个socket有效率多了。
linux下除了select还有epoll,听说比select的效率高许多。