I/O复用

原创 2017年01月03日 19:13:28

多进程与I/O复用

之前为了构建并发服务器,采用多进程的方式,一旦有客户端连接就会创建新进场,但由于创建新进场需要耗费大量的CPU资源与内存资源,且进程之间拥有完全独立的内存空间,还需要使用IPC;I/O复用则不需要创建多个进程,采用事件监听的方式,一旦事件发生了才去处理

I/O复用https://www.zhihu.com/question/32163005/answer/55772739

 

步骤、设置文件描述符、设置超时       2、调用select函数        3、查看调用结果

 

设置文件描述符:

利用select函数监视多个文件描述符之前,需要将要监视的文件描述符按照读(接收)、写(发送)、异常分类,相同的类型集中到一起

 

使用fd_set变量可以完成某种类型的集中,fd_set变量的每一位bit代表一个文件描述符的状态。fd_set变量的最左端的位表示文件描述符0,由于linux下的文件描述符是从0开始往上递增的,所以fd_set中从左向右第二位是文件描述符1,第三位………fd_set变量中哪一位是1,则表示对应的文件描述符是监视对象

 

对fd_set变量的操作由宏完成

(1)	FD_ZERO(fd_set *fdset) 	将fdset所指变量所有位初始化为0
(2)	FD_SET(int  fd ,fd_set * fdset)	将文件描述符fd注册到fdset所指变量中
(3)	FD_CLR(int  fd,fd_set* fdset)    将文件描述符fd从fdset所指变量中删除
(4)	FD_ISSET(int  fd,fd_set* fdset)   检查fdset所指变量中是否包含文件描述符fd的信息,有返回真

其中,宏FD_ISSET(int  fd,fd_set* fdset)是用来查看调用结果的,宏的功能如图:

 

调用select函数

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)	//成功返回发生事件的文件描述符数目,超时返回0,出错返回-1


(1)第一个参数maxfdp1指定被监视的描述符个数,它的值是被监视的最大描述符加1 (linux下文件描述符是从0开始递增的),描述符0、1、2...maxfdp1-1均将被监视。

(2)中间的三个参数readset、writeset和exceptset指定我们要让内核监视读(接收)、写(发送)和异常条件的描述符。如果对某一个的条件不感兴趣,就可以把它设为空指针NULL。

(3)最后一个参数告知内核等待所指定描述符中的任何一个发生事件可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。

        struct timeval
	{
                   long tv_sec;   //seconds
                   long tv_usec;  //microseconds
      	};

这个参数有三种可能:

(1)永远等待下去(阻塞):仅在有一个描述符发生事件时才返回。为此,把该参数设置为空指针NULL。

(2)等待一段固定时间:在有一个描述符发生事件时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。

(3)根本不等待(阻塞):检查描述符后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。

 

查看调用结果

select函数在调用后,如果返回值大于0,即有相应数目的文件描述符发生事件。相应的,向其传递fd_set变量中也会发生变化:除发生事件的文件描述符对应位,原来为1的所有位均变为0,也就是说,调用select函数后fd_set变量中值仍为1的位对应的文件描述符发生了事件

 基于I/O复用的服务器端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>

#define BUF_SIZE 100
void error_handling(const char *message);

int main(int argc, const char * argv[]) 
{
    int serv_sock = -1, clnt_sock = -1;
    struct sockaddr_in serv_adr, clnt_adr;
    struct timeval timeout;
    fd_set reads, cpy_reads;				

    socklen_t adr_sz;
    int fd_max = -1, str_len = -1, fd_num = -1, i = -1;
    char buf[BUF_SIZE] = { 0 };
    if( 2 != argc )
	{
        printf("Usage: %s <port> \n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if( -1 == bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) )
        error_handling("bind() error");
    if(-1 == listen(serv_sock, 5) )
        error_handling("listen() error");

    FD_ZERO(&reads);				//初始化所有位
    FD_SET(serv_sock, &reads);		//将文件描述符serv_sock注册进fd_set变量,即监控serv_sock的事件
    fd_max = serv_sock;

    while (1)
    {
        cpy_reads = reads;			//因为发生事件后select函数会改变其参数fd_set所指变量,故备份
        timeout.tv_sec = 5;			//指定超时时间,发生事件后,select函数会将其参数timeout所指结构体成员值设为超市前剩余时间
        timeout.tv_usec = 5000;		//故将fd_set类型变量与结构体timeout设置在循环内

        if( -1 == (fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout)) )	//监视读,监视个数为最大描述符 +1
            break;					
        if( fd_num == 0 )	//超时
            continue;
			
		for(i=0; i<fd_max + 1; i++)	//遍历fd_set变量所有被监视位
		{
			if( FD_ISSET(i, &cpy_reads) )//检查变量中是否有文件描述符i的信息
			{
				if( i == serv_sock )	//serv_sock发生的读事件(请求连接也是通过传输数据发生的,套接字缓冲中接收了数据)
				{
					adr_sz = sizeof(clnt_adr);
					clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);
					FD_SET(clnt_sock, &reads);	//注册文件描述符clnt_sock,即监控clnt_sock事件
					if(fd_max < clnt_sock)
						fd_max = clnt_sock;		//更新最大描述符
					printf("connected client: %d \n", clnt_sock);
					
				}
				else	//某个客户端套接字发生读事件
				{	//此处为i不为clnt_sock!描述符(套接字)i发生读事件,服务器需要服务多个客户端
					str_len = read(i, buf, BUF_SIZE);	
					if( 0 == str_len )//客服端发送EOF close或shutdown
					{
						FD_CLR(i, &reads);		//清除clnt_sock的注册信息
						close(i);		//关闭套接字
						printf("closed client: %d \n", i);
					}
					else
					{
						write(i, buf, str_len);	//回传
					}
				}
			}			
		}
    }

    close(serv_sock);	//关闭服务器套接字
    return 0;
}


void error_handling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, const char * argv[]) 
{
    int sock;
    char message[BUF_SIZE];
    int str_len, recv_len, recv_cnt;
    struct sockaddr_in serv_adr;

    if( 3 != argc )
    {
        printf("Usage: %s <IP> <port> \n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if( -1 == sock )
        error_handling("socket() error");

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    if( -1 == connect(sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) )
        error_handling("connect() error");
    else
        puts("Connected ...............");

    while (1) 
	{
        fputs("Input message(Q to quit): ", stdout);
        fgets(message, BUF_SIZE, stdin);
        if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
            break;

        str_len = write(sock, message, strlen(message));
        recv_len = 0;
        while (recv_len < str_len)
		{
            recv_cnt = read(sock, &message[recv_len], BUF_SIZE - 1);
            if(recv_cnt == -1)
                error_handling("read() error");
            recv_len += recv_cnt;
        }
        message[recv_len] = 0;
        printf("Message from server: %s", message);
    }

    close(sock);
    return 0;
}

void error_handling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

windows下的select函数

函数原型与返回值与linux下完全相同,只是参数1无意义,填0即可

timeval结构使用了typedef

         typedef struct timeval
	{

                   long tv_sec;   //seconds

                   long tv_usec;  //microseconds

      	}TIMEVAL;


 

fd_set变量为结构体

	typedef struct fd_set
	{
		u_int fd_count;
		SOCKET fd_array[FD_SETSIZE];
	}fd_set;


Linux下文件描述符从0递增,而windows下的句柄并非从0开始,而且句柄之间无规律可循。因此需要保存句柄数量与句柄

fd_set reads, cpy_reads;
……..
cpy_reads = reads;
……
for(i=0; i< reads.fd_count; i++)	//注意使用reads而不是cpy_reads
{
	if( FD_ISSET(reads.fd_array[i], &cpy_reads) );
	…..
}








 细谈select函数(C语言):http://blog.csdn.net/piaojun_pj/article/details/5991968/

IO多路复用之select总结:http://www.cnblogs.com/Anker/archive/2013/08/14/3258674.html

select函数详解及实例分析:http://blog.csdn.net/leo115/article/details/8097143

 Linux五种IO模型性能分析:http://blog.csdn.net/jay900323/article/details/18141217/

 

 

 

 

 

 

 

 

I/O复用之间的区别

select、poll、epoll之间的区别总结[整理]   select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述...
  • u012570105
  • u012570105
  • 2016年07月31日 17:45
  • 640

多路I/O复用分析

今天看到一个I/O性能的问题。就对这个问题思考了下。 分析阻塞、非阻塞I/O,这两种I/O一个共同点是,很多I/O中无法确认那些I/O是准备好,只能通过一个个轮询的方式,这种方式下,准备好与没有准备好...
  • xygl2009
  • xygl2009
  • 2015年08月27日 22:46
  • 694

Linux网络编程——I/O复用之select详解

一、I/O复用概述 I/O复用概念: 解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,使进程不阻塞于某个特定的 I/O 系统调 I/O复用使用的场合: 1.当客户处理多个描...
  • lianghe_work
  • lianghe_work
  • 2015年06月15日 17:46
  • 2479

Linux网络编程之I/O复用

Linux编程之IO多路复用(I/O Multiplexing) 程序设计过程中有时候会对多个输入进行操作,例如标准输入或者多个socket。那么如果在对某个进行处理的时候阻塞住的话,另外的输入进来...
  • Charce1989
  • Charce1989
  • 2017年04月24日 01:02
  • 472

Socket编程实践(8) --Select-I/O复用

五种I/O模型介绍(1)阻塞I/O[默认]   当上层应用App调用recv系统调用时,如果对等方没有发送数据(Linux内核缓冲区中没有数据),上层应用Application1将阻塞;当对等方发送了...
  • hanqing280441589
  • hanqing280441589
  • 2015年03月14日 15:32
  • 3340

Linux网络编程——tcp并发服务器(I/O复用之select

http://blog.csdn.net/lianghe_work/article/details/46519633 与多线程、多进程相比,I/O复用最大的优势是系统开销小,系统不需要建...
  • sinat_35297665
  • sinat_35297665
  • 2017年10月26日 21:02
  • 139

I/O复用select函数

《UNIX网络编程第六章笔记》 1、liunx下可用的5种I/O模型:阻塞式I/O、非阻塞式I/O、I/O复用、信号驱动I/O、异步I/O 1.1、阻塞式I/O 默认情形下,所有套接字都是阻塞的...
  • wustzjf
  • wustzjf
  • 2016年05月14日 11:57
  • 916

多路复用I/O模型之select

1 .所谓I/O多路复用是指内核一旦发现进程指定的一个或者多个I/O条件准备读取,它就通知该进程。I/O多路复用适用如下场合:  (1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用...
  • lixiaogang_theanswer
  • lixiaogang_theanswer
  • 2017年06月16日 17:08
  • 201

基于windows的I/O复用

I/O复用技术主要就是select函数的使用。                                            使用select函数可以同时监视多个文件描述符。当然,监视...
  • zl908760230
  • zl908760230
  • 2017年04月20日 15:09
  • 295

用TCP/TP进行网际互连(6) ———— 利用I/O复用完成单进程并发服务器的处理

用TCP/TP进行网际互连(6)———— 利用I/O复用完成单进程并发服务器的处理 1.掌握利用单线程进行数据驱动处理 2.掌握单线程并发服务器的原理与使用...
  • KevinBetterQ
  • KevinBetterQ
  • 2017年08月10日 19:20
  • 751
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:I/O复用
举报原因:
原因补充:

(最多只允许输入30个字)