Linux Network IO Model Learning

目录

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下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.cnblogs.com/yjf512/archive/2012/05/29/2523692.html
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:

http://blog.csdn.net/shallwake/article/details/5265287

3. 非阻塞式IO模型(noblocking IO model)

我们说过,阻塞和非阻塞的区别在于程序流的"连续性",从"阻塞IO"和"非阻塞IO"的图中可以看出来,在系统调用这个环节,非阻塞IO比阻塞IO"连贯",但是就数据通信这个环节,它们都还是同步的,即数据通信是"不连贯"的

可以看出,在非阻塞IO模式下,recvfrom()的系统调用会以轮询的方式进行,每次用户询问内核是否有数据报准备好(文件描述符缓冲区是否就绪),直到数据报准备好的时候(内核缓冲区有数据),就进行拷贝数据报的操作。当数据报没有准备好的时候,也不阻塞程序,内核直接返回未准备就绪的信号,等待用户程序的下一次轮询。
(从这个例子思考阻塞和同步的区别)

Relevant Link:

http://blog.csdn.net/shallwake/article/details/5265287


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.cnblogs.com/yjf512/archive/2012/05/31/2527966.html
http://www.blogjava.net/lihao336/archive/2009/12/27/307430.html

7. Linux下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://www.cnblogs.com/yjf512/archive/2012/06/05/2536005.html
http://www.cnblogs.com/yjf512/archive/2012/06/07/2539755.html
http://www.cnblogs.com/yjf512/archive/2012/06/11/2545615.html
http://blog.chinaunix.net/uid-26000296-id-4100620.html

Copyright (c) 2014 LittleHann All rights reserved

Linux Network IO Model Learning

时间: 2024-11-05 12:20:01

Linux Network IO Model Learning的相关文章

【learning log】Linux network programming

DNS host entry 包含 DNS database 中有关某一 domin name 或 ip address 的 DNS 信息. 1 struct hostent{ 2 char *h_name; 3 char *h_aliases; 4 int h_addrtype; 5 int h_length; 6 char **h_addr_list; 7 }; hostinfo 程序, 用来从 ip 或 domin name 解析 DNS info. 1 /*This program is

linux网络IO模型——阻塞、非阻塞和同步、异步

最近几天在学习nginx的时候了解了一下linux网络IO模型,在此谈谈我自己的理解,如有错误请多多指教.本文参考书籍Richard Stevens的“UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2节“I/O Models ”. Linux网络IO请求数据分为两段: 1.数据准备 2.将数据从内核拷贝到进程空间 其实,阻塞.非阻塞和同步.异步的不同就在于这两个阶段的不同. 同步和异步关

python之IO model

一.事件驱动模型 在介绍协程时,遇到IO操作就切换,但什么时候切换回来,怎么确定IO操作结束? 很多程序员可能会考虑使用"线程池"或"连接池"."线程池"旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务."连接池"维持连接的缓存池,尽量重用已有的连接.减少创建和关闭连接的频率.这两种技术都可以很好的降低系统开销,都被广泛应用很多大型系统,如websphere.tomcat和各种数据库等

Linux串口IO模式的一些心得

众所周知,在Linux系统下所有设备都是以文件的形式存在,串口也一样. 通常I/O操作都是有阻塞与非阻塞的两种方式. 其中"超时"这个概念其实是阻塞中的一种处理手段,本质还是属于阻塞的I/O模式. 在Linux中串口的IO操作 本文将它分为三种状态: 阻塞状态 超时状态 非阻塞状态 这三种状态的转换组合有这么几种: 阻塞 --> 超时 阻塞 --> 非阻塞 超时 --> 阻塞 超时 --> 非阻塞 非阻塞 --> 阻塞 我们一个一个来分析 首先在一个串口的

linux 同步IO: sync、fsync与fdatasync

[linux 同步IO: sync.fsync与fdatasync] 传统的UNIX实现在内核中设有缓冲区高速缓存或页面高速缓存,大多数磁盘I/O都通过缓冲进行.当将数据写入文件时,内核通常先将该数据复制到其中一个缓冲区中,如果该缓冲区尚未写满,则并不将其排入输出队列,而是等待其写满或者当内核需要重用该缓冲区以便存放其他磁盘块数据时,再将该缓冲排入输出队列,然后待其到达队首时,才进行实际的I/O操作.这种输出方式被称为延迟写(delayed write) 延迟写减少了磁盘读写次数,但是却降低了文

Linux noop io 调度算法分析

定义了一个elevator_noop的调度器类型: static struct elevator_type elevator_noop = { .ops = { .elevator_merge_req_fn = noop_merged_requests,//查询一个request,用于将bio并入 .elevator_dispatch_fn = noop_dispatch,//将noop调度器链表中最前面的请求取出,分派给块设备的请求队列 .elevator_add_req_fn = noop_

Linux deadline io 调度算法

deadline算法的核心就是在传统的电梯算法中加入了请求超时的机制,该机制主要体现在两点: 1.请求超时时,对超时请求的选择. 2.没有请求超时时,当扫描完电梯最后一个request后,准备返回时,对第一个request的选择.基于以上两点,平衡了系统i/o吞吐量和响应时间. 此外,该算法还考虑到了读操作对写操作造成的饥饿. 定义了elevator_deadline调度器类型: static struct elevator_type iosched_deadline = { .ops = {

linux 同步IO: sync msync、fsync、fdatasync与 fflush

最近阅读leveldb源码,作为一个保证可靠性的kv数据库其数据与磁盘的交互可谓是极其关键,其中涉及到了不少内存和磁盘同步的操作和策略.为了加深理解,从网上整理了linux池畔同步IO相关的函数,这里做一个罗列和对比.大部分为copy,仅为记录,请各位看官勿喷. 传统的UNIX实现在内核中设有缓冲区高速缓存或页面高速缓存,大多数磁盘I/O都通过缓冲进行.当将数据写入文件时,内核通常先将该数据复制到其中一个缓冲区中,如果该缓冲区尚未写满,则并不将其排入输出队列,而是等待其写满或者当内核需要重用该缓

[Linux 005]——IO重定向

通常在 Shell 中执行命令的时候,我们会在输入命令的下方看到执行结果,操作系统默认将命令的执行结果输出到显示器上.当然,我们也可以手动的指定输出路径,或者输入路径,这就是 I/O 重定向. 1.标准输出重定向 使用 cat 命令,命令的执行结果将会打印在屏幕中. 我们使用 > 来进行输出重定向,此时屏幕上不再打印命令执行结果了,而是将执行结果保存到了 ./target.xxx 文件中. 来看一下 ./target.xxx 文件中的内容: 2.标准输入重定向 tr 命令可以从键盘上读取标准输入