首页 > 系统相关 >Linux Network IO Model、Socket IO Model - select、poll、epoll

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

时间:2023-01-04 22:02:00浏览次数:40  
标签:Socket 阻塞 IO sockfd Model include buf addr

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系统的分层

Linux Network IO Model、Socket IO Model - select、poll、epoll_#include

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)

Linux Network IO Model、Socket IO Model - select、poll、epoll_数据_02

Linux Network IO Model、Socket IO Model - select、poll、epoll_非阻塞_03

在我们详细学习这5种IO模型之前,我们先来对它们的核心概念进行一下区分

0x4: 5个IO模型的区别

Linux Network IO Model、Socket IO Model - select、poll、epoll_非阻塞_04

值得注意的是,所谓"同步和异步"的说法,更注重的是通信上的问题(数据描述符缓存的读写)、而"阻塞和非阻塞",更注重的是整个执行流的角度(即请求方需要等待被请求方执行完毕之后才能继续)

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)

Linux Network IO Model、Socket IO Model - select、poll、epoll_#include_05

Linux Network IO Model、Socket IO Model - select、poll、epoll_非阻塞_06

1. 首先application调用recvfrom()转入kernel,注意kernel有2个过程
1) wait for data
2) copy data from kernel to user
2. 直到最后copy complete后
3. recvfrom()才返回
4. 此过程一直是阻塞的


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

Linux Network IO Model、Socket IO Model - select、poll、epoll_非阻塞_07

Linux Network IO Model、Socket IO Model - select、poll、epoll_#include_08

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

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

4. IO复用式IO模型(IO multiplexing model)

Linux Network IO Model、Socket IO Model - select、poll、epoll_数据_09

Linux Network IO Model、Socket IO Model - select、poll、epoll_非阻塞_10

IO复用模型是多了一个select函数,select函数有一个参数是文件描述符集合,意思就是对这些的文件描述符进行循环监听,select这是处于阻塞状态,当某个文件描述符就绪的时候(有活动套接字),就对这个文件描述符进行处理。与blocking I/O相比,select会有两次系统调用,但是select能处理多个套接字。所以依然属于阻塞同步IO。但是由于它可以对多个文件描述符进行阻塞监听,所以它的效率比阻塞IO模型高效。

Linux Network IO Model、Socket IO Model - select、poll、epoll_数据_11

 

5. 信号驱动式IO模型(signal-driven IO model)

Linux Network IO Model、Socket IO Model - select、poll、epoll_数据_12

Linux Network IO Model、Socket IO Model - select、poll、epoll_数据_13

信号驱动IO模型是应用进程告诉内核:当你的数据报准备好的时候,给我发送一个信号哈,并且调用我的信号处理函数来获取数据报。这个模型是由信号进行驱动

与I/O multiplexing (select and poll)相比,它的优势是,免去了select的阻塞与轮询,当有活跃套接字时,由注册的handler处理,但是copy date过程依然是阻塞的,所以属于非阻塞同步IO

 6. 异步IO式IO模型(asynchronous IO model)

Linux Network IO Model、Socket IO Model - select、poll、epoll_非阻塞_14

Linux Network IO Model、Socket IO Model - select、poll、epoll_数据_15

很少有*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. 将读缓存区中的数据复制到应用程序(用户区)返回

Linux Network IO Model、Socket IO Model - select、poll、epoll_#include_16

值得注意的是

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操作时,进程或线程进入等待状态,直到操作完成

Linux Network IO Model、Socket IO Model - select、poll、epoll_#include_17

#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返回数据之间存在一定的间隔,这会导致整体数据吞吐量的降低

Linux Network IO Model、Socket IO Model - select、poll、epoll_#include_18

#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可读或可写准备好时,应用能被通知到

Linux Network IO Model、Socket IO Model - select、poll、epoll_#include_19

#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读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环,让它读取数据报

Linux Network IO Model、Socket IO Model - select、poll、epoll_#include_20

服务端

#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操作何时完成(是一种等待模式的完全解放)

Linux Network IO Model、Socket IO Model - select、poll、epoll_非阻塞_21

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

 

Copyright (c) 2014 LittleHann All rights reserved

 



标签:Socket,阻塞,IO,sockfd,Model,include,buf,addr
From: https://blog.51cto.com/u_15775105/5989332

相关文章