目录
0. 引言
1. IO机制简介
2. 阻塞式IO模型(blocking IO model)
3. 非阻塞式IO模型(noblocking IO model)
4. IO复用式IO模型(IO multiplexing model)
5. 信号驱动式IO模型(signal-driven IO model)
6. 异步IO式IO模型(asynchronous IO model)
7. Linux Socket IO技术简介
8. IO模型编程举例
0. 引言
Linux将所有外部设备都看做一个文件来进行操作。因此,linux对所有外部设备(包括实体设备、以及虚拟设备)的操作都可以看做是文件的操作。文件的操作当然需要有个标示描述它,这就是文件描述符(file descriptor)
而对文件的操作,本质上就是IO的操作,本问将重点讨论IO操作中的网络IO(Network IO)操作,在开始学习之前,我们需要明白一点,不管是对于linux还是window来说,都存在模式和技术方案的关系
1. IO模式
所谓是一种理论模型,是一种思想
2. IO技术(机制)
指linux下具体的IO通信技术
1. IO机制简介
0x1: IO系统的分层
1. File System(文件系统)
解决了空间管理的问题,即
1) 数据如何存放
2) 数据如何读取
2. Buffer Cache
解决数据缓冲的问题
1) 对读,进行cache
缓存经常要用到的数据
2) 对写,进行buffer
缓冲一定数据以后,一次性进行写入
3. Vol Mgmt
VM(Vol Mgmt)其实跟IO没有必然联系。他是处于文件系统和磁盘(存储)中间的一层。VM屏蔽了底层磁盘对上层文件系统的影响。当没有VM的时候,文件系统直接使用存储上的地址空间,因此文件系统直接受限于物理硬盘,这时如果
发生磁盘空间不足的情况,对应用而言将是一场噩梦,不得不新增硬盘,然后重新进行数据复制。而VM则可以实现动态扩展,而对文件系统没有影响。另外,VM也可以把多个磁盘合并成一个磁盘,对文件系统呈现统一的地址空间
/*
数据实际存储
数据最终会放在这里,因此,效率、数据安全、容灾是这里需要考虑的问题。而提高存储的性能,则可以直接提高物理IO的性能
*/
4. Device Driver
5. IO Channel
6. Disk Device
值得主要的是:
//逻辑IO和物理IO不是一一对应的
1. Logical IO
逻辑IO是操作系统发起的IO,这个数据可能会放在磁盘上,也可能会放在内存(文件系统的Cache)里
2. Physical IO
物理IO是设备驱动发起的IO,这个数据最终会落在磁盘上
0x2: IO请求的两个阶段
1. 等待资源阶段(排队)
IO请求一般需要请求特殊的资源(如磁盘、RAM、文件),当资源被上一个使用者使用没有被释放时,IO请求就会被阻塞,直到能够使用这个资源(阻塞条件解除)
在等待数据阶段,IO分为阻塞IO和非阻塞IO
1.1 阻塞IO
资源不可用时,IO请求一直阻塞,直到反馈结果(有数据或超时)
1.2 非阻塞IO(返回失败)
资源不可用时,IO请求离开返回,返回数据标识资源不可用
1.3 非阻塞IO(异步回调)
资源不可用时,IO请求离开返回,返回数据标识资源不可用,但使用注册回调机制,当资源可用时由资源方主动向资源请求方发出通知信号(表明资源可用)
2. 使用资源阶段(服务)
真正进行数据接收和发生
在使用资源阶段,IO分为同步IO和异步IO
2.1 同步IO
应用阻塞在发送或接收数据的状态,直到数据成功传输或返回失败
2. 异步IO
应用发送或接收数据后立刻返回,数据写入OS缓存,由OS完成数据发送或接收,并返回成功或失败的信息给应用
0x3: Unix/Linux下的的5个IO模型
1. 阻塞式IO模型(blocking IO model)
2. 非阻塞式IO模型(noblocking IO model)
3. IO复用式IO模型(IO multiplexing model)
4. 信号驱动式IO模型(signal-driven IO model)
5. 异步IO式IO模型(asynchronous IO model)
在我们详细学习这5种IO模型之前,我们先来对它们的核心概念进行一下区分
0x4: 5个IO模型的区别
值得注意的是,所谓"同步和异步"的说法,更注重的是通信上的问题(数据描述符缓存的读写)、而"阻塞和非阻塞",更注重的是整个执行流的角度(即请求方需要等待被请求方执行完毕之后才能继续)
1. 同步异步标准是
数据描述符缓存是由谁来进行读取的
1) 由用户程序读取: 则判断为同步
2) 由内核推送: 判断为异步
2. 阻塞非阻塞标准是
调用的用户进程是否是阻塞的状态
1) 资源请求方的用户进程处于阻塞状态(发出调用后阻塞等待): 阻塞式IO
2) 资源请求方的用户进程处于非阻塞状态(发出调用后立即返回): 非阻塞式IO
也就是说,本质上来说,"同步异步"和"阻塞非阻塞"是可以两种独立讨论的概念,并不是说异步就一定是非阻塞,它们的评判角度是不同的
0x5: 各种IO模型的特点
1. 阻塞IO
使用简单,但随之而来的问题就是会形成阻塞,需要独立线程配合,而这些线程在大多数时候都是没有进行运算的。Java的BIO使用这种方式,问题带来的问题很明显,一个Socket需要一个独立的线程,因此,会造成线程膨胀
2. 非阻塞IO
采用轮询方式,不会形成线程的阻塞。Java的NIO使用这种方式,对比BIO的优势很明显,可以使用一个线程进行所有Socket的监听(select),大大减少了线程数。
3. 同步IO
同步IO保证一个IO操作结束之后才会返回,因此同步IO效率会低一些,但是对应用来说,编程方式会简单。Java的BIO和NIO都是使用这种方式进行数据处理
4. 异步IO
由于异步IO请求只是写入了缓存,从缓存到硬盘是否成功不可知,因此异步IO相当于把一个IO拆成了两部分,一是发起请求,二是获取处理结果。因此,对应用来说增加了复杂性。但是异步IO的性能是所有很好的,而且异步的思想贯穿了
IT系统方方面面
Relevant Link:
http://pengjiaheng.iteye.com/blog/847588
http://pengjiaheng.iteye.com/blog/847615
unix网络编程 (1).pdf 6.2节
http://www.360doc.com/content/12/0426/15/507289_206688978.shtml
http://blog.chinaunix.net/uid-26000296-id-4100620.html
2. 阻塞式IO模型(blocking IO model)
1. 首先application调用recvfrom()转入kernel,注意kernel有2个过程
1) wait for data
2) copy data from kernel to user
2. 直到最后copy complete后
3. recvfrom()才返回
4. 此过程一直是阻塞的
Relevant Link:
3. 非阻塞式IO模型(noblocking IO model)
我们说过,阻塞和非阻塞的区别在于程序流的"连续性",从"阻塞IO"和"非阻塞IO"的图中可以看出来,在系统调用这个环节,非阻塞IO比阻塞IO"连贯",但是就数据通信这个环节,它们都还是同步的,即数据通信是"不连贯"的
可以看出,在非阻塞IO模式下,recvfrom()的系统调用会以轮询的方式进行,每次用户询问内核是否有数据报准备好(文件描述符缓冲区是否就绪),直到数据报准备好的时候(内核缓冲区有数据),就进行拷贝数据报的操作。当数据报没有准备好的时候,也不阻塞程序,内核直接返回未准备就绪的信号,等待用户程序的下一次轮询。
(从这个例子思考阻塞和同步的区别)
Relevant Link:
4. IO复用式IO模型(IO multiplexing model)
IO复用模型是多了一个select函数,select函数有一个参数是文件描述符集合,意思就是对这些的文件描述符进行循环监听,select这是处于阻塞状态,当某个文件描述符就绪的时候(有活动套接字),就对这个文件描述符进行处理。与blocking I/O相比,select会有两次系统调用,但是select能处理多个套接字。所以依然属于阻塞同步IO。但是由于它可以对多个文件描述符进行阻塞监听,所以它的效率比阻塞IO模型高效。
5. 信号驱动式IO模型(signal-driven IO model)
信号驱动IO模型是应用进程告诉内核:当你的数据报准备好的时候,给我发送一个信号哈,并且调用我的信号处理函数来获取数据报。这个模型是由信号进行驱动
与I/O multiplexing (select and poll)相比,它的优势是,免去了select的阻塞与轮询,当有活跃套接字时,由注册的handler处理,但是copy date过程依然是阻塞的,所以属于非阻塞同步IO
6. 异步IO式IO模型(asynchronous IO model)
很少有*nix系统支持,windows的IOCP则是此模型(这里不讨论windows)
当应用程序调用aio_read的时候,内核一方面去取数据报内容返回,另外一方面将程序控制权还给应用进程,应用进程继续处理其他事务。这样应用进程就是一种非阻塞的状态。
当内核的数据报就绪的时候,是由内核将数据报拷贝到应用进程中,返回给aio_read中定义好的函数处理程序。所以这是一种阻塞异步IO模型
Relevant Link:
http://www.blogjava.net/lihao336/archive/2009/12/27/307430.html
7. Linux Socket IO技术简介
0x1: socket read()流程
我们说网络socket的read()是一个IO操作命令,具体流程是这样的:
1. 应用程序调用read命令,通知内核需要做读取数据操作
2. 内核创建一个文件描述符
3. 内核从物理层收到读数据的命令,从网络中获取数据包
4. 数据包传递到TCP/IP层,解析数据包的头
5. 内核将数据包缓存在文件描述符的读缓存区(接受缓存区)中,注意这里的读缓存区是在内核中的
6. 当文件描述符读缓存区数据字节数大于应用程序定义的低水位(阈值)的时候(read的一个参数),此时文件描述符处于读就绪的状态
7. 将读缓存区中的数据复制到应用程序(用户区)返回
值得注意的是
1. 每个文件描述符都有自己的读缓冲区和写缓冲区,读缓冲区对应的是read操作,写缓冲区对应的就是write操作了
2. 读缓冲区和写缓冲区都是在内核区中
0x2: Linux Poll技术
poll IO技术属于IO复用模型(同步阻塞),
0x3: Linux epoll技术
I/O multiplexing
signal driven I/O(callback特性)
0x4: Kqueue技术
I/O multiplexing
0x4: Linux select技术
I/O multiplexing
0x5: javaScript、nodejs中的读取网络(文件)数据
javaScript或者nodejs中的读取网络(文件)数据,然后提供回调函数进行处理,是异步阻塞IO
0x6: IOCP
asynchronous I/O
windows下的IO技术,本文暂不讨论
7. IO模型编程举例
0x1: 同步阻塞模型
在这个模式中,用户空间的应用程序执行一个系统调用,并阻塞,直到系统调用完成为止(数据传输完成或发生错误)
Socket设置为阻塞模式,当socket不能立即完成I/O操作时,进程或线程进入等待状态,直到操作完成
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#define SERVPORT 80
#define MAXDATASIZE 100
int main(int argc, char *argv[])
{
int sockfd, recvbytes;
char rcv_buf[MAXDATASIZE]; /*./client 127.0.0.1 hello */
char snd_buf[MAXDATASIZE];
struct hostent *host;
/* struct hostent
* {
* char *h_name; // general hostname
* char **h_aliases; // hostname's alias
* int h_addrtype; // AF_INET
* int h_length;
* char **h_addr_list;
* };
*/
struct sockaddr_in server_addr;
if (argc < 3)
{
printf("Usage:%s [ip address] [any string]\n", argv[0]);
return 1;
}
*snd_buf = '\0';
strcat(snd_buf, argv[2]);
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket:");
exit(1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVPORT);
inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
memset(&(server_addr.sin_zero), 0, 8);
/* create the connection by socket
* means that connect "sockfd" to "server_addr"
* 同步阻塞模式
*/
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
{
perror("connect");
exit(1);
}
/* 同步阻塞模式 */
if (send(sockfd, snd_buf, sizeof(snd_buf), 0) == -1)
{
perror("send:");
exit(1);
}
printf("send:%s\n", snd_buf);
/* 同步阻塞模式 */
if ((recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, 0)) == -1)
{
perror("recv:");
exit(1);
}
rcv_buf[recvbytes] = '\0';
printf("recv:%s\n", rcv_buf);
close(sockfd);
return 0;
}
显然,代码中的connect,send,,recv都是同步阻塞工作模式,在结果没有返回时,程序什么也不做,这种模型非常经典,也被广泛使用
1. 优势
在于非常简单,等待的过程中占用的系统资源微乎其微,程序调用返回时,必定可以拿到数据
2. 缺点
程序在数据到来并准备好以前,不能进行其他操作,需要有一个线程专门用于等待,这种代价对于需要处理大量连接的服务器而言,是很难接受的
0x2: 同步非阻塞模型
同步阻塞I/O的一种效率稍低的变种是同步非阻塞I/O,在这种模型中,系统调用是以非阻塞的形式打开的。这意味着I/O操作不会立即完成, 操作可能会返回一个错误代码,说明这个命令不能立即满足(EAGAIN或EWOULDBLOCK),非阻塞的实现是I/O命令可能并不会立即满足,需要应用程序调用许多次来等待操作完成。这可能效率不高,因为在很多情况下,当内核执行这个命令时,应用程序必须要进行忙碌等待,直到数据可用为止,或者试图执行其他工作。因为数据在内核中变为可用到用户调用read返回数据之间存在一定的间隔,这会导致整体数据吞吐量的降低
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define SERVPORT 80
#define MAXDATASIZE 100
int main(int argc, char *argv[])
{
int sockfd, recvbytes;
char rcv_buf[MAXDATASIZE]; /*./client 127.0.0.1 hello */
char snd_buf[MAXDATASIZE];
struct hostent *host;
/* struct hostent
* {
* char *h_name; // general hostname
* char **h_aliases; // hostname's alias
* int h_addrtype; // AF_INET
* int h_length;
* char **h_addr_list;
* };
*/
struct sockaddr_in server_addr;
int flags;
int addr_len;
if (argc < 3)
{
printf("Usage:%s [ip address] [any string]\n", argv[0]);
return 1;
}
*snd_buf = '\0';
strcat(snd_buf, argv[2]);
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket:");
exit(1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVPORT);
inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
memset(&(server_addr.sin_zero), 0, 8);
addr_len = sizeof(struct sockaddr_in);
/* Setting socket to nonblock */
flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, flags|O_NONBLOCK);
/* create the connection by socket
* means that connect "sockfd" to "server_addr"
* 同步阻塞模式
*/
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
{
perror("connect");
exit(1);
}
/* 同步非阻塞模式 */
while (send(sockfd, snd_buf, sizeof(snd_buf), MSG_DONTWAIT) == -1)
{
sleep(10);
printf("sleep\n");
}
printf("send:%s\n", snd_buf);
/* 同步非阻塞模式 */
while ((recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, MSG_DONTWAIT)) == -1)
{
sleep(10);
printf("sleep\n");
}
rcv_buf[recvbytes] = '\0';
printf("recv:%s\n", rcv_buf);
close(sockfd);
return 0;
}
这种模式在没有数据可以接收时,可以进行其他的一些操作,比如有多个socket时,可以去查看其他socket有没有可以接收的数据
实际应用中,这种I/O模型的直接使用并不常见,因为它需要不停的查询,而这些查询大部分会是无必要的调用,白白浪费了系统资源
非阻塞I/O应该算是一个铺垫,为I/O复用和信号驱动奠定了非阻塞使用的基础
while(1)
{
非阻塞read(设备1);
if(设备1有数据到达)
处理数据;
非阻塞read(设备2);
if(设备2有数据到达)
处理数据;
..............................
}
0x3: I/O复用(异步阻塞)模式
在这种模型中,配置的是非阻塞I/O,然后使用阻塞select系统调用来确定一个I/O描述符何时有操作。对于select调用来说非常有趣的是它可以用来为多个描述符提供通知,而不仅仅为一个描述符提供通知。于每个提示符来说,我们可以请求这个描述符可以写数据、有读数据可用以及是否发生错误的通知。I/O复用模型能让一个或多个socket可读或可写准备好时,应用能被通知到
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netdb.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SERVPORT 80
#define MAXDATASIZE 100
#define TFILE "data_from_socket.txt"
int main(int argc, char *argv[])
{
int sockfd, recvbytes;
char rcv_buf[MAXDATASIZE]; /*./client 127.0.0.1 hello */
char snd_buf[MAXDATASIZE];
struct hostent *host;
/* struct hostent
* {
* char *h_name; // general hostname
* char **h_aliases; // hostname's alias
* int h_addrtype; // AF_INET
* int h_length;
* char **h_addr_list;
* };
*/
struct sockaddr_in server_addr;
/* */
fd_set readset, writeset;
int check_timeval = 1;
struct timeval timeout={check_timeval,0}; //阻塞式select, 等待1秒,1秒轮询
int maxfd;
int fp;
int cir_count = 0;
int ret;
if (argc < 3)
{
printf("Usage:%s [ip address] [any string]\n", argv[0]);
return 1;
}
*snd_buf = '\0';
strcat(snd_buf, argv[2]);
if ((fp = open(TFILE,O_WRONLY)) < 0) //不是用fopen
{
perror("fopen:");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket:");
exit(1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVPORT);
inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
memset(&(server_addr.sin_zero), 0, 8);
/* create the connection by socket
* means that connect "sockfd" to "server_addr"
*/
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
{
perror("connect");
exit(1);
}
/**/
if (send(sockfd, snd_buf, sizeof(snd_buf), 0) == -1)
{
perror("send:");
exit(1);
}
printf("send:%s\n", snd_buf);
while (1)
{
FD_ZERO(&readset); //每次循环都要清空集合,否则不能检测描述符变化
FD_SET(sockfd, &readset); //添加描述符
FD_ZERO(&writeset);
FD_SET(fp, &writeset);
maxfd = sockfd > fp ? (sockfd+1) : (fp+1); //描述符最大值加1
ret = select(maxfd, &readset, NULL, NULL, NULL); // 阻塞模式
switch( ret)
{
case -1:
exit(-1);
break;
case 0:
break;
default:
if (FD_ISSET(sockfd, &readset)) //测试sock是否可读,即是否网络上有数据
{
recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, MSG_DONTWAIT);
rcv_buf[recvbytes] = '\0';
printf("recv:%s\n", rcv_buf);
if (FD_ISSET(fp, &writeset))
{
write(fp, rcv_buf, strlen(rcv_buf)); // 不是用fwrite
}
goto end;
}
}
cir_count++;
printf("CNT : %d \n",cir_count);
}
end:
close(fp);
close(sockfd);
return 0;
}
perl实现:
#! /usr/bin/perl
###############################################################################
# \File
# tcp_client.pl
# \Descript
# send message to server
###############################################################################
use IO::Socket;
use IO::Select;
#hash to install IP Port
%srv_info =(
#"srv_ip" => "61.184.93.197",
"srv_ip" => "192.168.1.73",
"srv_port"=> "8080",
);
my $srv_addr = $srv_info{"srv_ip"};
my $srv_port = $srv_info{"srv_port"};
my $sock = IO::Socket::INET->new(
PeerAddr => "$srv_addr",
PeerPort => "$srv_port",
Type => SOCK_STREAM,
Blocking => 1,
# Timeout => 5,
Proto => "tcp")
or die "Can not create socket connect. $@";
$sock->send("Hello server!\n", 0) or warn "send failed: $!, $@";
$sock->autoflush(1);
my $sel = IO::Select->new($sock);
while(my @ready = $sel->can_read)
{
foreach my $fh(@ready)
{
if($fh == $sock)
{
while()
{
print $_;
}
$sel->remove($fh);
close $fh;
}
}
}
$sock->close();
用select来管理多个I/O,当没有数据时select阻塞,如果在超时时间内数据到来则select返回,再调用recv进行数据的复制,recv返回后处理数据
0x4: 信号驱动I/O模型
我们也可以用信号,让内核在描述字就绪时发送SIGIO信号通知我们
1. 首先开启套接口的信号驱动 I/O功能,并通过sigaction系统调用安装一个信号处理函数
2. 该系统调用将立即返回,我们的进程继续工作,也就是说没被阻塞
3. 当数据报准备好读取时,内核就为该进程产生一个SIGIO信号
4. 我们随后既可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环,让它读取数据报
服务端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
int listenfd1;
void do_sigio(int sig)
{
int clifd, clilen;
struct sockaddr_in cli_addr;
char buffer[256];
clifd = accept(listenfd1, (struct sockaddr *) &cli_addr, &clilen);
bzero(buffer, 256);
read(clifd, buffer, 255);
printf("Listenfd1 Message%s\r\n", buffer);
}
int main(int argc, char *argv[])
{
//绑定监听7779端口的fd
struct sockaddr_in serv_addr1;
listenfd1 = socket(AF_INET, SOCK_DGRAM, 0);
bzero((char *) &serv_addr1, sizeof(serv_addr1));
serv_addr1.sin_family = AF_INET;
serv_addr1.sin_port = htons(7779);
serv_addr1.sin_addr.s_addr = INADDR_ANY;
struct sigaction sigio_action;
memset(&sigio_action, 0, sizeof(sigio_action));
sigio_action.sa_flags = 0;
sigio_action.sa_handler = do_sigio;
sigaction(SIGIO, &sigio_action, NULL);
fcntl(listenfd1, F_SETOWN, getpid());
int flags;
flags = fcntl(listenfd1, F_GETFL, 0);
flags |= O_ASYNC | O_NONBLOCK;
fcntl(listenfd1, F_SETFL, flags);
bind(listenfd1, (struct sockaddr *) &serv_addr1, sizeof(serv_addr1));
while(1);
close(listenfd1);
return 0;
}
客户端
//客户端
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
int socketfd, n;
socketfd = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in serv_addr;
bzero((char *)&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(7779);
connect(socketfd,(struct sockaddr *) &serv_addr, sizeof(serv_addr));
write(socketfd, "client message", 14);
return 0;
}
0x5: 异步非阻塞模式
linux下的asynchronous IO其实用得很少。与前面的信号驱动模型的主要区别在于
1. 信号驱动I/O是由内核通知我们何时可以启动一个I/O操作
2. 异步I/O模型是由内核通知我们I/O操作何时完成(是一种等待模式的完全解放)
client
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netdb.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SERVPORT 80
#define MAXDATASIZE 100
#define TFILE "data_from_socket.txt"
int main(int argc, char *argv[])
{
int sockfd, recvbytes;
char rcv_buf[MAXDATASIZE]; /*./client 127.0.0.1 hello */
char snd_buf[MAXDATASIZE];
struct hostent *host;
/* struct hostent
* {
* char *h_name; // general hostname
* char **h_aliases; // hostname's alias
* int h_addrtype; // AF_INET
* int h_length;
* char **h_addr_list;
* };
*/
struct sockaddr_in server_addr;
/* */
fd_set readset, writeset;
int check_timeval = 1;
struct timeval timeout={check_timeval,0}; //阻塞式select, 等待1秒,1秒轮询
int maxfd;
int fp;
int cir_count = 0;
int ret;
if (argc < 3)
{
printf("Usage:%s [ip address] [any string]\n", argv[0]);
return 1;
}
*snd_buf = '\0';
strcat(snd_buf, argv[2]);
if ((fp = open(TFILE,O_WRONLY)) < 0) //不是用fopen
{
perror("fopen:");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket:");
exit(1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVPORT);
inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
memset(&(server_addr.sin_zero), 0, 8);
/* create the connection by socket
* means that connect "sockfd" to "server_addr"
*/
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
{
perror("connect");
exit(1);
}
/**/
if (send(sockfd, snd_buf, sizeof(snd_buf), 0) == -1)
{
perror("send:");
exit(1);
}
printf("send:%s\n", snd_buf);
while (1)
{
FD_ZERO(&readset); //每次循环都要清空集合,否则不能检测描述符变化
FD_SET(sockfd, &readset); //添加描述符
FD_ZERO(&writeset);
FD_SET(fp, &writeset);
maxfd = sockfd > fp ? (sockfd+1) : (fp+1); //描述符最大值加1
ret = select(maxfd, &readset, NULL, NULL, &timeout); // 非阻塞模式
switch( ret)
{
case -1:
exit(-1);
break;
case 0:
break;
default:
if (FD_ISSET(sockfd, &readset)) //测试sock是否可读,即是否网络上有数据
{
recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, MSG_DONTWAIT);
rcv_buf[recvbytes] = '\0';
printf("recv:%s\n", rcv_buf);
if (FD_ISSET(fp, &writeset))
{
write(fp, rcv_buf, strlen(rcv_buf)); // 不是用fwrite
}
goto end;
}
}
timeout.tv_sec = check_timeval; // 必须重新设置,因为超时时间到后会将其置零
cir_count++;
printf("CNT : %d \n",cir_count);
}
end:
close(fp);
close(sockfd);
return 0;
}
server
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#define SERVPORT 80
#define BACKLOG 10 // max numbef of client connection
#define MAXDATASIZE 100
int main(char argc, char *argv[])
{
int sockfd, client_fd, addr_size, recvbytes;
char rcv_buf[MAXDATASIZE], snd_buf[MAXDATASIZE];
char* val;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int bReuseaddr = 1;
char IPdotdec[20];
/* create a new socket and regiter it to os .
* SOCK_STREAM means that supply tcp service,
* and must connect() before data transfort.
*/
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket:");
exit(1);
}
/* setting server's socket */
server_addr.sin_family = AF_INET; // IPv4 network protocol
server_addr.sin_port = htons(SERVPORT);
server_addr.sin_addr.s_addr = INADDR_ANY; // auto IP detect
memset(&(server_addr.sin_zero),0, 8);
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&bReuseaddr, sizeof(int));
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr))== -1)
{
perror("bind:");
exit(1);
}
/*
* watting for connection ,
* and server permit to recive the requestion from sockfd
*/
if (listen(sockfd, BACKLOG) == -1) // BACKLOG assign thd max number of connection
{
perror("listen:");
exit(1);
}
while(1)
{
addr_size = sizeof(struct sockaddr_in);
/*
* accept the sockfd's connection,
* return an new socket and assign far host to client_addr
*/
printf("watting for connect...\n");
if ((client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &addr_size)) == -1)
{
/* Nonblocking mode */
perror("accept:");
continue;
}
/* network-digital to ip address */
inet_ntop(AF_INET, (void*)&client_addr, IPdotdec, 16);
printf("connetion from:%d : %s\n",client_addr.sin_addr, IPdotdec);
//if (!fork())
{
/* child process handle with the client connection */
/* recive the client's data by client_fd */
if ((recvbytes = recv(client_fd, rcv_buf, MAXDATASIZE, 0)) == -1)
{
perror("recv:");
exit(1);
}
rcv_buf[recvbytes]='\0';
printf("recv:%s\n", rcv_buf);
*snd_buf='\0';
strcat(snd_buf, "welcome");
sleep(3);
/* send the message to far-hosts by client_fd */
if (send(client_fd, snd_buf, strlen(snd_buf), 0) == -1)
{
perror("send:");
exit(1);
}
printf("send:%s\n", snd_buf);
close(client_fd);
//exit(1);
}
//close(client_fd);
}
return 0;
}
steps:
1. 调用read
2. read请求会立即返回,说明请求已经成功发起了
3. 在后台完成读操作这段时间内,应用程序可以执行其他处理操作
4. 当 read 的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成这次I/O处理过程
用户进程发起read操作之后,立刻就可以开始去做其它的事
而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block
然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了
Relevant Link:
http://blog.chinaunix.net/uid-26000296-id-4100620.html
技术链接
目录
0. 引言
1. IO机制简介
2. 阻塞式IO模型(blocking IO model)
3. 非阻塞式IO模型(noblocking IO model)
4. IO复用式IO模型(IO multiplexing model)
5. 信号驱动式IO模型(signal-driven IO model)
6. 异步IO式IO模型(asynchronous IO model)
7. Linux Socket IO技术简介
8. IO模型编程举例
0. 引言
Linux将所有外部设备都看做一个文件来进行操作。因此,linux对所有外部设备(包括实体设备、以及虚拟设备)的操作都可以看做是文件的操作。文件的操作当然需要有个标示描述它,这就是文件描述符(file descriptor)
而对文件的操作,本质上就是IO的操作,本问将重点讨论IO操作中的网络IO(Network IO)操作,在开始学习之前,我们需要明白一点,不管是对于linux还是window来说,都存在模式和技术方案的关系
1. IO模式
所谓是一种理论模型,是一种思想
2. IO技术(机制)
指linux下具体的IO通信技术
1. IO机制简介
0x1: IO系统的分层
1. File System(文件系统)
解决了空间管理的问题,即
1) 数据如何存放
2) 数据如何读取
2. Buffer Cache
解决数据缓冲的问题
1) 对读,进行cache
缓存经常要用到的数据
2) 对写,进行buffer
缓冲一定数据以后,一次性进行写入
3. Vol Mgmt
VM(Vol Mgmt)其实跟IO没有必然联系。他是处于文件系统和磁盘(存储)中间的一层。VM屏蔽了底层磁盘对上层文件系统的影响。当没有VM的时候,文件系统直接使用存储上的地址空间,因此文件系统直接受限于物理硬盘,这时如果
发生磁盘空间不足的情况,对应用而言将是一场噩梦,不得不新增硬盘,然后重新进行数据复制。而VM则可以实现动态扩展,而对文件系统没有影响。另外,VM也可以把多个磁盘合并成一个磁盘,对文件系统呈现统一的地址空间
/*
数据实际存储
数据最终会放在这里,因此,效率、数据安全、容灾是这里需要考虑的问题。而提高存储的性能,则可以直接提高物理IO的性能
*/
4. Device Driver
5. IO Channel
6. Disk Device
值得主要的是:
//逻辑IO和物理IO不是一一对应的
1. Logical IO
逻辑IO是操作系统发起的IO,这个数据可能会放在磁盘上,也可能会放在内存(文件系统的Cache)里
2. Physical IO
物理IO是设备驱动发起的IO,这个数据最终会落在磁盘上
0x2: IO请求的两个阶段
1. 等待资源阶段(排队)
IO请求一般需要请求特殊的资源(如磁盘、RAM、文件),当资源被上一个使用者使用没有被释放时,IO请求就会被阻塞,直到能够使用这个资源(阻塞条件解除)
在等待数据阶段,IO分为阻塞IO和非阻塞IO
1.1 阻塞IO
资源不可用时,IO请求一直阻塞,直到反馈结果(有数据或超时)
1.2 非阻塞IO(返回失败)
资源不可用时,IO请求离开返回,返回数据标识资源不可用
1.3 非阻塞IO(异步回调)
资源不可用时,IO请求离开返回,返回数据标识资源不可用,但使用注册回调机制,当资源可用时由资源方主动向资源请求方发出通知信号(表明资源可用)
2. 使用资源阶段(服务)
真正进行数据接收和发生
在使用资源阶段,IO分为同步IO和异步IO
2.1 同步IO
应用阻塞在发送或接收数据的状态,直到数据成功传输或返回失败
2. 异步IO
应用发送或接收数据后立刻返回,数据写入OS缓存,由OS完成数据发送或接收,并返回成功或失败的信息给应用
0x3: Unix/Linux下的的5个IO模型
1. 阻塞式IO模型(blocking IO model)
2. 非阻塞式IO模型(noblocking IO model)
3. IO复用式IO模型(IO multiplexing model)
4. 信号驱动式IO模型(signal-driven IO model)
5. 异步IO式IO模型(asynchronous IO model)
在我们详细学习这5种IO模型之前,我们先来对它们的核心概念进行一下区分
0x4: 5个IO模型的区别
值得注意的是,所谓"同步和异步"的说法,更注重的是通信上的问题(数据描述符缓存的读写)、而"阻塞和非阻塞",更注重的是整个执行流的角度(即请求方需要等待被请求方执行完毕之后才能继续)
1. 同步异步标准是
数据描述符缓存是由谁来进行读取的
1) 由用户程序读取: 则判断为同步
2) 由内核推送: 判断为异步
2. 阻塞非阻塞标准是
调用的用户进程是否是阻塞的状态
1) 资源请求方的用户进程处于阻塞状态(发出调用后阻塞等待): 阻塞式IO
2) 资源请求方的用户进程处于非阻塞状态(发出调用后立即返回): 非阻塞式IO
也就是说,本质上来说,"同步异步"和"阻塞非阻塞"是可以两种独立讨论的概念,并不是说异步就一定是非阻塞,它们的评判角度是不同的
0x5: 各种IO模型的特点
1. 阻塞IO
使用简单,但随之而来的问题就是会形成阻塞,需要独立线程配合,而这些线程在大多数时候都是没有进行运算的。Java的BIO使用这种方式,问题带来的问题很明显,一个Socket需要一个独立的线程,因此,会造成线程膨胀
2. 非阻塞IO
采用轮询方式,不会形成线程的阻塞。Java的NIO使用这种方式,对比BIO的优势很明显,可以使用一个线程进行所有Socket的监听(select),大大减少了线程数。
3. 同步IO
同步IO保证一个IO操作结束之后才会返回,因此同步IO效率会低一些,但是对应用来说,编程方式会简单。Java的BIO和NIO都是使用这种方式进行数据处理
4. 异步IO
由于异步IO请求只是写入了缓存,从缓存到硬盘是否成功不可知,因此异步IO相当于把一个IO拆成了两部分,一是发起请求,二是获取处理结果。因此,对应用来说增加了复杂性。但是异步IO的性能是所有很好的,而且异步的思想贯穿了
IT系统方方面面
Relevant Link:
http://pengjiaheng.iteye.com/blog/847588
http://pengjiaheng.iteye.com/blog/847615
unix网络编程 (1).pdf 6.2节
http://www.360doc.com/content/12/0426/15/507289_206688978.shtml
http://blog.chinaunix.net/uid-26000296-id-4100620.html
2. 阻塞式IO模型(blocking IO model)
1. 首先application调用recvfrom()转入kernel,注意kernel有2个过程
1) wait for data
2) copy data from kernel to user
2. 直到最后copy complete后
3. recvfrom()才返回
4. 此过程一直是阻塞的
Relevant Link:
3. 非阻塞式IO模型(noblocking IO model)
我们说过,阻塞和非阻塞的区别在于程序流的"连续性",从"阻塞IO"和"非阻塞IO"的图中可以看出来,在系统调用这个环节,非阻塞IO比阻塞IO"连贯",但是就数据通信这个环节,它们都还是同步的,即数据通信是"不连贯"的
可以看出,在非阻塞IO模式下,recvfrom()的系统调用会以轮询的方式进行,每次用户询问内核是否有数据报准备好(文件描述符缓冲区是否就绪),直到数据报准备好的时候(内核缓冲区有数据),就进行拷贝数据报的操作。当数据报没有准备好的时候,也不阻塞程序,内核直接返回未准备就绪的信号,等待用户程序的下一次轮询。
(从这个例子思考阻塞和同步的区别)
Relevant Link:
4. IO复用式IO模型(IO multiplexing model)
IO复用模型是多了一个select函数,select函数有一个参数是文件描述符集合,意思就是对这些的文件描述符进行循环监听,select这是处于阻塞状态,当某个文件描述符就绪的时候(有活动套接字),就对这个文件描述符进行处理。与blocking I/O相比,select会有两次系统调用,但是select能处理多个套接字。所以依然属于阻塞同步IO。但是由于它可以对多个文件描述符进行阻塞监听,所以它的效率比阻塞IO模型高效。
5. 信号驱动式IO模型(signal-driven IO model)
信号驱动IO模型是应用进程告诉内核:当你的数据报准备好的时候,给我发送一个信号哈,并且调用我的信号处理函数来获取数据报。这个模型是由信号进行驱动
与I/O multiplexing (select and poll)相比,它的优势是,免去了select的阻塞与轮询,当有活跃套接字时,由注册的handler处理,但是copy date过程依然是阻塞的,所以属于非阻塞同步IO
6. 异步IO式IO模型(asynchronous IO model)
很少有*nix系统支持,windows的IOCP则是此模型(这里不讨论windows)
当应用程序调用aio_read的时候,内核一方面去取数据报内容返回,另外一方面将程序控制权还给应用进程,应用进程继续处理其他事务。这样应用进程就是一种非阻塞的状态。
当内核的数据报就绪的时候,是由内核将数据报拷贝到应用进程中,返回给aio_read中定义好的函数处理程序。所以这是一种阻塞异步IO模型
Relevant Link:
7. Linux Socket IO技术简介
0x1: socket read()流程
我们说网络socket的read()是一个IO操作命令,具体流程是这样的:
1. 应用程序调用read命令,通知内核需要做读取数据操作
2. 内核创建一个文件描述符
3. 内核从物理层收到读数据的命令,从网络中获取数据包
4. 数据包传递到TCP/IP层,解析数据包的头
5. 内核将数据包缓存在文件描述符的读缓存区(接受缓存区)中,注意这里的读缓存区是在内核中的
6. 当文件描述符读缓存区数据字节数大于应用程序定义的低水位(阈值)的时候(read的一个参数),此时文件描述符处于读就绪的状态
7. 将读缓存区中的数据复制到应用程序(用户区)返回
值得注意的是
1. 每个文件描述符都有自己的读缓冲区和写缓冲区,读缓冲区对应的是read操作,写缓冲区对应的就是write操作了
2. 读缓冲区和写缓冲区都是在内核区中
0x2: Linux Poll技术
poll IO技术属于IO复用模型(同步阻塞),
0x3: Linux epoll技术
I/O multiplexing
signal driven I/O(callback特性)
0x4: Kqueue技术
I/O multiplexing
0x4: Linux select技术
I/O multiplexing
0x5: javaScript、nodejs中的读取网络(文件)数据
javaScript或者nodejs中的读取网络(文件)数据,然后提供回调函数进行处理,是异步阻塞IO
0x6: IOCP
asynchronous I/O
windows下的IO技术,本文暂不讨论
7. IO模型编程举例
0x1: 同步阻塞模型
在这个模式中,用户空间的应用程序执行一个系统调用,并阻塞,直到系统调用完成为止(数据传输完成或发生错误)
Socket设置为阻塞模式,当socket不能立即完成I/O操作时,进程或线程进入等待状态,直到操作完成
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#define SERVPORT 80
#define MAXDATASIZE 100
int main(int argc, char *argv[])
{
int sockfd, recvbytes;
char rcv_buf[MAXDATASIZE]; /*./client 127.0.0.1 hello */
char snd_buf[MAXDATASIZE];
struct hostent *host;
/* struct hostent
* {
* char *h_name; // general hostname
* char **h_aliases; // hostname's alias
* int h_addrtype; // AF_INET
* int h_length;
* char **h_addr_list;
* };
*/
struct sockaddr_in server_addr;
if (argc < 3)
{
printf("Usage:%s [ip address] [any string]\n", argv[0]);
return 1;
}
*snd_buf = '\0';
strcat(snd_buf, argv[2]);
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket:");
exit(1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVPORT);
inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
memset(&(server_addr.sin_zero), 0, 8);
/* create the connection by socket
* means that connect "sockfd" to "server_addr"
* 同步阻塞模式
*/
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
{
perror("connect");
exit(1);
}
/* 同步阻塞模式 */
if (send(sockfd, snd_buf, sizeof(snd_buf), 0) == -1)
{
perror("send:");
exit(1);
}
printf("send:%s\n", snd_buf);
/* 同步阻塞模式 */
if ((recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, 0)) == -1)
{
perror("recv:");
exit(1);
}
rcv_buf[recvbytes] = '\0';
printf("recv:%s\n", rcv_buf);
close(sockfd);
return 0;
}
显然,代码中的connect,send,,recv都是同步阻塞工作模式,在结果没有返回时,程序什么也不做,这种模型非常经典,也被广泛使用
1. 优势
在于非常简单,等待的过程中占用的系统资源微乎其微,程序调用返回时,必定可以拿到数据
2. 缺点
程序在数据到来并准备好以前,不能进行其他操作,需要有一个线程专门用于等待,这种代价对于需要处理大量连接的服务器而言,是很难接受的
0x2: 同步非阻塞模型
同步阻塞I/O的一种效率稍低的变种是同步非阻塞I/O,在这种模型中,系统调用是以非阻塞的形式打开的。这意味着I/O操作不会立即完成, 操作可能会返回一个错误代码,说明这个命令不能立即满足(EAGAIN或EWOULDBLOCK),非阻塞的实现是I/O命令可能并不会立即满足,需要应用程序调用许多次来等待操作完成。这可能效率不高,因为在很多情况下,当内核执行这个命令时,应用程序必须要进行忙碌等待,直到数据可用为止,或者试图执行其他工作。因为数据在内核中变为可用到用户调用read返回数据之间存在一定的间隔,这会导致整体数据吞吐量的降低
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define SERVPORT 80
#define MAXDATASIZE 100
int main(int argc, char *argv[])
{
int sockfd, recvbytes;
char rcv_buf[MAXDATASIZE]; /*./client 127.0.0.1 hello */
char snd_buf[MAXDATASIZE];
struct hostent *host;
/* struct hostent
* {
* char *h_name; // general hostname
* char **h_aliases; // hostname's alias
* int h_addrtype; // AF_INET
* int h_length;
* char **h_addr_list;
* };
*/
struct sockaddr_in server_addr;
int flags;
int addr_len;
if (argc < 3)
{
printf("Usage:%s [ip address] [any string]\n", argv[0]);
return 1;
}
*snd_buf = '\0';
strcat(snd_buf, argv[2]);
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket:");
exit(1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVPORT);
inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
memset(&(server_addr.sin_zero), 0, 8);
addr_len = sizeof(struct sockaddr_in);
/* Setting socket to nonblock */
flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, flags|O_NONBLOCK);
/* create the connection by socket
* means that connect "sockfd" to "server_addr"
* 同步阻塞模式
*/
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
{
perror("connect");
exit(1);
}
/* 同步非阻塞模式 */
while (send(sockfd, snd_buf, sizeof(snd_buf), MSG_DONTWAIT) == -1)
{
sleep(10);
printf("sleep\n");
}
printf("send:%s\n", snd_buf);
/* 同步非阻塞模式 */
while ((recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, MSG_DONTWAIT)) == -1)
{
sleep(10);
printf("sleep\n");
}
rcv_buf[recvbytes] = '\0';
printf("recv:%s\n", rcv_buf);
close(sockfd);
return 0;
}
这种模式在没有数据可以接收时,可以进行其他的一些操作,比如有多个socket时,可以去查看其他socket有没有可以接收的数据
实际应用中,这种I/O模型的直接使用并不常见,因为它需要不停的查询,而这些查询大部分会是无必要的调用,白白浪费了系统资源
非阻塞I/O应该算是一个铺垫,为I/O复用和信号驱动奠定了非阻塞使用的基础
while(1)
{
非阻塞read(设备1);
if(设备1有数据到达)
处理数据;
非阻塞read(设备2);
if(设备2有数据到达)
处理数据;
..............................
}
0x3: I/O复用(异步阻塞)模式
在这种模型中,配置的是非阻塞I/O,然后使用阻塞select系统调用来确定一个I/O描述符何时有操作。对于select调用来说非常有趣的是它可以用来为多个描述符提供通知,而不仅仅为一个描述符提供通知。于每个提示符来说,我们可以请求这个描述符可以写数据、有读数据可用以及是否发生错误的通知。I/O复用模型能让一个或多个socket可读或可写准备好时,应用能被通知到
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netdb.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SERVPORT 80
#define MAXDATASIZE 100
#define TFILE "data_from_socket.txt"
int main(int argc, char *argv[])
{
int sockfd, recvbytes;
char rcv_buf[MAXDATASIZE]; /*./client 127.0.0.1 hello */
char snd_buf[MAXDATASIZE];
struct hostent *host;
/* struct hostent
* {
* char *h_name; // general hostname
* char **h_aliases; // hostname's alias
* int h_addrtype; // AF_INET
* int h_length;
* char **h_addr_list;
* };
*/
struct sockaddr_in server_addr;
/* */
fd_set readset, writeset;
int check_timeval = 1;
struct timeval timeout={check_timeval,0}; //阻塞式select, 等待1秒,1秒轮询
int maxfd;
int fp;
int cir_count = 0;
int ret;
if (argc < 3)
{
printf("Usage:%s [ip address] [any string]\n", argv[0]);
return 1;
}
*snd_buf = '\0';
strcat(snd_buf, argv[2]);
if ((fp = open(TFILE,O_WRONLY)) < 0) //不是用fopen
{
perror("fopen:");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket:");
exit(1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVPORT);
inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
memset(&(server_addr.sin_zero), 0, 8);
/* create the connection by socket
* means that connect "sockfd" to "server_addr"
*/
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
{
perror("connect");
exit(1);
}
/**/
if (send(sockfd, snd_buf, sizeof(snd_buf), 0) == -1)
{
perror("send:");
exit(1);
}
printf("send:%s\n", snd_buf);
while (1)
{
FD_ZERO(&readset); //每次循环都要清空集合,否则不能检测描述符变化
FD_SET(sockfd, &readset); //添加描述符
FD_ZERO(&writeset);
FD_SET(fp, &writeset);
maxfd = sockfd > fp ? (sockfd+1) : (fp+1); //描述符最大值加1
ret = select(maxfd, &readset, NULL, NULL, NULL); // 阻塞模式
switch( ret)
{
case -1:
exit(-1);
break;
case 0:
break;
default:
if (FD_ISSET(sockfd, &readset)) //测试sock是否可读,即是否网络上有数据
{
recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, MSG_DONTWAIT);
rcv_buf[recvbytes] = '\0';
printf("recv:%s\n", rcv_buf);
if (FD_ISSET(fp, &writeset))
{
write(fp, rcv_buf, strlen(rcv_buf)); // 不是用fwrite
}
goto end;
}
}
cir_count++;
printf("CNT : %d \n",cir_count);
}
end:
close(fp);
close(sockfd);
return 0;
}
perl实现:
#! /usr/bin/perl
###############################################################################
# \File
# tcp_client.pl
# \Descript
# send message to server
###############################################################################
use IO::Socket;
use IO::Select;
#hash to install IP Port
%srv_info =(
#"srv_ip" => "61.184.93.197",
"srv_ip" => "192.168.1.73",
"srv_port"=> "8080",
);
my $srv_addr = $srv_info{"srv_ip"};
my $srv_port = $srv_info{"srv_port"};
my $sock = IO::Socket::INET->new(
PeerAddr => "$srv_addr",
PeerPort => "$srv_port",
Type => SOCK_STREAM,
Blocking => 1,
# Timeout => 5,
Proto => "tcp")
or die "Can not create socket connect. $@";
$sock->send("Hello server!\n", 0) or warn "send failed: $!, $@";
$sock->autoflush(1);
my $sel = IO::Select->new($sock);
while(my @ready = $sel->can_read)
{
foreach my $fh(@ready)
{
if($fh == $sock)
{
while()
{
print $_;
}
$sel->remove($fh);
close $fh;
}
}
}
$sock->close();
用select来管理多个I/O,当没有数据时select阻塞,如果在超时时间内数据到来则select返回,再调用recv进行数据的复制,recv返回后处理数据
0x4: 信号驱动I/O模型
我们也可以用信号,让内核在描述字就绪时发送SIGIO信号通知我们
1. 首先开启套接口的信号驱动 I/O功能,并通过sigaction系统调用安装一个信号处理函数
2. 该系统调用将立即返回,我们的进程继续工作,也就是说没被阻塞
3. 当数据报准备好读取时,内核就为该进程产生一个SIGIO信号
4. 我们随后既可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环,让它读取数据报
服务端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
int listenfd1;
void do_sigio(int sig)
{
int clifd, clilen;
struct sockaddr_in cli_addr;
char buffer[256];
clifd = accept(listenfd1, (struct sockaddr *) &cli_addr, &clilen);
bzero(buffer, 256);
read(clifd, buffer, 255);
printf("Listenfd1 Message%s\r\n", buffer);
}
int main(int argc, char *argv[])
{
//绑定监听7779端口的fd
struct sockaddr_in serv_addr1;
listenfd1 = socket(AF_INET, SOCK_DGRAM, 0);
bzero((char *) &serv_addr1, sizeof(serv_addr1));
serv_addr1.sin_family = AF_INET;
serv_addr1.sin_port = htons(7779);
serv_addr1.sin_addr.s_addr = INADDR_ANY;
struct sigaction sigio_action;
memset(&sigio_action, 0, sizeof(sigio_action));
sigio_action.sa_flags = 0;
sigio_action.sa_handler = do_sigio;
sigaction(SIGIO, &sigio_action, NULL);
fcntl(listenfd1, F_SETOWN, getpid());
int flags;
flags = fcntl(listenfd1, F_GETFL, 0);
flags |= O_ASYNC | O_NONBLOCK;
fcntl(listenfd1, F_SETFL, flags);
bind(listenfd1, (struct sockaddr *) &serv_addr1, sizeof(serv_addr1));
while(1);
close(listenfd1);
return 0;
}
客户端
//客户端
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
int socketfd, n;
socketfd = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in serv_addr;
bzero((char *)&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(7779);
connect(socketfd,(struct sockaddr *) &serv_addr, sizeof(serv_addr));
write(socketfd, "client message", 14);
return 0;
}
0x5: 异步非阻塞模式
linux下的asynchronous IO其实用得很少。与前面的信号驱动模型的主要区别在于
1. 信号驱动I/O是由内核通知我们何时可以启动一个I/O操作
2. 异步I/O模型是由内核通知我们I/O操作何时完成(是一种等待模式的完全解放)
client
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netdb.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SERVPORT 80
#define MAXDATASIZE 100
#define TFILE "data_from_socket.txt"
int main(int argc, char *argv[])
{
int sockfd, recvbytes;
char rcv_buf[MAXDATASIZE]; /*./client 127.0.0.1 hello */
char snd_buf[MAXDATASIZE];
struct hostent *host;
/* struct hostent
* {
* char *h_name; // general hostname
* char **h_aliases; // hostname's alias
* int h_addrtype; // AF_INET
* int h_length;
* char **h_addr_list;
* };
*/
struct sockaddr_in server_addr;
/* */
fd_set readset, writeset;
int check_timeval = 1;
struct timeval timeout={check_timeval,0}; //阻塞式select, 等待1秒,1秒轮询
int maxfd;
int fp;
int cir_count = 0;
int ret;
if (argc < 3)
{
printf("Usage:%s [ip address] [any string]\n", argv[0]);
return 1;
}
*snd_buf = '\0';
strcat(snd_buf, argv[2]);
if ((fp = open(TFILE,O_WRONLY)) < 0) //不是用fopen
{
perror("fopen:");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket:");
exit(1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVPORT);
inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
memset(&(server_addr.sin_zero), 0, 8);
/* create the connection by socket
* means that connect "sockfd" to "server_addr"
*/
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
{
perror("connect");
exit(1);
}
/**/
if (send(sockfd, snd_buf, sizeof(snd_buf), 0) == -1)
{
perror("send:");
exit(1);
}
printf("send:%s\n", snd_buf);
while (1)
{
FD_ZERO(&readset); //每次循环都要清空集合,否则不能检测描述符变化
FD_SET(sockfd, &readset); //添加描述符
FD_ZERO(&writeset);
FD_SET(fp, &writeset);
maxfd = sockfd > fp ? (sockfd+1) : (fp+1); //描述符最大值加1
ret = select(maxfd, &readset, NULL, NULL, &timeout); // 非阻塞模式
switch( ret)
{
case -1:
exit(-1);
break;
case 0:
break;
default:
if (FD_ISSET(sockfd, &readset)) //测试sock是否可读,即是否网络上有数据
{
recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, MSG_DONTWAIT);
rcv_buf[recvbytes] = '\0';
printf("recv:%s\n", rcv_buf);
if (FD_ISSET(fp, &writeset))
{
write(fp, rcv_buf, strlen(rcv_buf)); // 不是用fwrite
}
goto end;
}
}
timeout.tv_sec = check_timeval; // 必须重新设置,因为超时时间到后会将其置零
cir_count++;
printf("CNT : %d \n",cir_count);
}
end:
close(fp);
close(sockfd);
return 0;
}
server
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#define SERVPORT 80
#define BACKLOG 10 // max numbef of client connection
#define MAXDATASIZE 100
int main(char argc, char *argv[])
{
int sockfd, client_fd, addr_size, recvbytes;
char rcv_buf[MAXDATASIZE], snd_buf[MAXDATASIZE];
char* val;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int bReuseaddr = 1;
char IPdotdec[20];
/* create a new socket and regiter it to os .
* SOCK_STREAM means that supply tcp service,
* and must connect() before data transfort.
*/
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket:");
exit(1);
}
/* setting server's socket */
server_addr.sin_family = AF_INET; // IPv4 network protocol
server_addr.sin_port = htons(SERVPORT);
server_addr.sin_addr.s_addr = INADDR_ANY; // auto IP detect
memset(&(server_addr.sin_zero),0, 8);
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&bReuseaddr, sizeof(int));
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr))== -1)
{
perror("bind:");
exit(1);
}
/*
* watting for connection ,
* and server permit to recive the requestion from sockfd
*/
if (listen(sockfd, BACKLOG) == -1) // BACKLOG assign thd max number of connection
{
perror("listen:");
exit(1);
}
while(1)
{
addr_size = sizeof(struct sockaddr_in);
/*
* accept the sockfd's connection,
* return an new socket and assign far host to client_addr
*/
printf("watting for connect...\n");
if ((client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &addr_size)) == -1)
{
/* Nonblocking mode */
perror("accept:");
continue;
}
/* network-digital to ip address */
inet_ntop(AF_INET, (void*)&client_addr, IPdotdec, 16);
printf("connetion from:%d : %s\n",client_addr.sin_addr, IPdotdec);
//if (!fork())
{
/* child process handle with the client connection */
/* recive the client's data by client_fd */
if ((recvbytes = recv(client_fd, rcv_buf, MAXDATASIZE, 0)) == -1)
{
perror("recv:");
exit(1);
}
rcv_buf[recvbytes]='\0';
printf("recv:%s\n", rcv_buf);
*snd_buf='\0';
strcat(snd_buf, "welcome");
sleep(3);
/* send the message to far-hosts by client_fd */
if (send(client_fd, snd_buf, strlen(snd_buf), 0) == -1)
{
perror("send:");
exit(1);
}
printf("send:%s\n", snd_buf);
close(client_fd);
//exit(1);
}
//close(client_fd);
}
return 0;
}
steps:
1. 调用read
2. read请求会立即返回,说明请求已经成功发起了
3. 在后台完成读操作这段时间内,应用程序可以执行其他处理操作
4. 当 read 的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成这次I/O处理过程
用户进程发起read操作之后,立刻就可以开始去做其它的事
而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block
然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了
Relevant Link: