Linux Network IO Model、Socket IO Model - select、poll、epoll

目录

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.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 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://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

 

posted @ 2014-08-08 10:17 郑瀚Andrew.Hann 阅读(...) 评论(...) 编辑 收藏