五種I/O模型
Unix共有五種I/O模型:
- 阻塞式I/O
- 非阻塞I/O
- I/O複用(select和(e)poll)
- 信號驅動I/O(SIGIO)
- 異步I/O(Posix.1的aio_系列函數)
阻塞I/O模型
應用程序調用一個IO函數,導致應用程序阻塞,等待數據準備好。如果數據沒有準備好,一直等待。數據準備好了,從內核拷貝到用戶空間,表示IO結束,IO函數返回成功指示。
非阻塞I/O模型
我們把一個套接口設置爲非阻塞就是告訴內核,當所請求的I/O操作無法完成時,不要將進程睡眠,而是返回一個錯誤。這樣我們的I/O操作函數將不斷的測試 數據是否已經準備好,如果沒有準備好,繼續測試,直到數據準備好爲止。在這個不斷測試的過程中,會大量的佔用CPU的時間。
I/O複用模型
該種模型又被稱作是多路轉接,I/O複用模型會用到select或者poll函數,這兩個函數也會使進程阻塞,但是和阻塞I/O所不同的的,這兩個函數可以同時阻塞多個I/O操作。而且可以同時對多個讀操作,多個寫操作的I/O函數進行檢測,直到有數據可讀或可寫時,才真正調用I/O操作函數。
信號驅動I/O模型
首先我們允許套接口進行信號驅動I/O,並安裝一個信號處理函數,進程繼續運行並不阻塞。當數據準備好時,進程會收到一個SIGIO信號,可以在信號處理函數中調用I/O操作函數處理數據。
異步I/O模型
調用aio_read函數,告訴內核描述字,緩衝區指針,緩衝區大小,文件偏移以及通知的方式,然後立即返回。當內核將數據拷貝到緩衝區後,再通知應用程序。也就是它只需要發起這個讀寫事件,不要等待與數據搬遷,只需要在結束之後得到成果。
I/O多路轉接之select
系統提供select函數來實現多路複用輸入/輸出模型。select系統調用是用來讓我們的程序監視多個文件句柄的狀態變化的。 程序會停在select這裏等待,直到被監視的文件句柄有一個或多個發生了狀態改變。 關於文件句柄,其實就是一個整數,我們最熟悉的句柄是0、1、2三個,0是標準輸入,1是標準輸出,2是標準錯誤輸出。0、1、2是整數示的,對應的FILE *結構的表示就是stdin、stdout、stderr。
select的優缺點
優點:
1、不需要建立多個線程、進程就可以實現一對多的通信。
2、可以同時等待多個文件描述符,效率比起多進程多線程來說要高很多
select高效的原因
首先要知道一個概念,一次I/O分兩個部分(①等待數據就緒 ②進行I/O),減少等的比重,增加I/O的比重就可以達到高效服務器的目的。select工作原理就是這個,同時監控多個文件描述符(或者說文件句柄),一旦其中某一個進入就緒狀態,就進行I/O操作。監控多個文件句柄可以達到提高就緒狀態出現的概率,就可以使CPU在大多數時間下都處於忙碌狀態,大大提高CPU的性能。達到高效服務器的目的。 可以理解爲select輪詢監控多個文件句柄或套接字。
缺點:
1、每次進行select都要把文件描述符集fd由用戶態拷貝到內核態,這樣的開銷會很大。
2、實現select服務器,內部要不斷對文件描述符集fd進行循環遍歷,當fd很多時,開銷也很大。
3、select能監控文件描述符的數量有限,一般爲1024。(sizeof(fd_set) * 8 = 1024(fd_set內部是以位圖表示文件描述符))
操作函數
1.重定向的dup函數
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
函數功能:
dup:將oldfd文件描述符進行一份拷貝,返回值爲最新拷貝生成的文件描述符
dup2:使用newfd對oldfd文件描述符做一份拷貝,必要是可以先關閉newfd文件描述符
調用dup2之後,oldfd文件描述符不變,newfd和oldfd相等。
2.文件描述符集的四個函數:
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>
void FD_CLR(int fd,fd_set *set); ---- 將文件描述符fd從文件描述符集指針set指向的文件描述符集中清除掉
int FD_ISSET(int fd,fd_set *set); ---- 用來判斷文件描述符fd是否在文件描述符集指針set指向的文件描述符集中。
- 存在 返回1;
- 不存在 返回0;
void FD_SET(int fd,fd_set*set); ---- 將文件描述符fd設置進文件描述符集指針set指向的文件描述符集中。
void FD_ZERO(int fd,fd_set*set); ----將文件描述符集指針set指向的文件描述符集清0。
3.select函數
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *execptfds,struct timeval *timeout)
函數功能:通過參數readfds,writefds,execptfds將要監控的文件描述符寫入,就可以監控這些文件描述符。
參數:
(1) nfds:表示最大文件描述符+1,用來表示文件描述符的範圍。
(2) readfds:表示指向讀文件描述符集的指針
(3)writefds:表示指向寫文件描述符集的指針.
(4)execptfds:表示指向錯誤輸出文件描述符集的指針。
參數readfds,writefds,execptfds既是輸入參數,又是輸出參數。
輸入:將要監控的文件描述符傳給select
輸出:將處於就緒狀態的文件描述符返回。 (所以要在每次處理完一就緒事件後要將readfds,writefds,execptfd三個參數重置)
(4)timeout:表示超時時間限制。(有三種情況)
timeout也是一種輸入輸出兩用參數,輸入表示設定超時時間,輸出表示超時時間還剩多少。
timeout是一個timeval結構體類型的指針。 結構如下:
struct timeval {
long tv_sec; /* seconds */ //表示秒
long tv_usec; /* microseconds */ //表示微秒
};
我們可以通過設置上面結構體內兩個元素的值來設定select的超時時間。
設置timeout的方法:
struct timeval time = {5,0};//第一個值表示秒,第二個值表示微秒。
select(...,&time);//超時時間爲5秒
timeout的時間設定有三種情況:
① 上面示例的正常設定,設定一個正常的時間。等待一段時間,返回一次
②將時間設爲0,—-表示一直輪詢等待。或者說根本不等待,檢查完描述符後不管有沒有就緒立即返回
③傳參時傳爲NULL,—-表示一直阻塞等待
返回值
select的返回值有三種,-1 —– 執行錯誤,0 —– timeout時間到達,其他 —– 正確執行,並且有就緒事件到達
代碼實現
server.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/time.h>
#include<unistd.h>
#include<string.h>
#define SIZE sizeof(fd_set)*8
int readfds[SIZE]; //保存所有文件描述符的數組
void Usage(const char* name)
{
printf("Usage:%s [IP] [port]\n",name);
}
int StartUp(const char* ip,int port)
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socket");
return 1;
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip);
if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
perror("bind");
return 2;
}
if(listen(sock,5) < 0)
{
perror("listen");
return 3;
}
return sock;
}
int main(int argc,char* argv[])
{
//printf("hello\n");
if(argc != 3)
{
Usage(argv[0]);
return 4;
}
int sock = StartUp(argv[1],atoi(argv[2]));
int i = 0;
for(; i<SIZE; ++i)
{
readfds[i] = -1;//全部初始化爲-1
}
readfds[0] = sock;
int max_fd = readfds[0]; //保存最大的文件描述符做select函數的參數
while(1)
{
fd_set rfds,wfds;
char buf[1024];
FD_ZERO(&rfds); //將文件描述符集指針指向的文件描述符集清零
int j = 0;
for(; j<SIZE; ++j)
{
if(readfds[j] != -1)
FD_SET(readfds[j],&rfds);//將文件描述符設置進文件描述符集指針指向的文件描述符集中
if(max_fd < readfds[j])
max_fd = readfds[j];
}
struct timeval timeout = {5,0};
switch (select(max_fd+1,&rfds,&wfds,NULL,&timeout))
{
case -1:
{
perror("select");
break;
}
case 0:
{
printf("timeout...\n");
break;
}
default:
{
int k = 0;
for(; k<SIZE; ++k)
{
if(readfds[k] == sock && FD_ISSET(readfds[k],&rfds))
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newsock = accept(sock,(struct sockaddr*)&peer,&len);
if(newsock < 0)
{
perror("accept");
continue;
}
int l = 0;
for(; l<SIZE; ++l)
{
if(readfds[l] == -1)
{
readfds[l] = newsock;
break;
}
}
if( l == SIZE)
{
printf("readfds is full\n");
return 5;
}
}
else if(readfds[k] > 0 && FD_ISSET(readfds[k],&rfds))
{
ssize_t s = read(readfds[k],buf,sizeof(buf)-1);
if(s < 0)
{
perror("read");
return 6;
}
else if(s == 0)
{
printf("client quit\n");
readfds[k] = -1;
close(readfds[k]);
continue;
}
else
{
buf[s] = 0;
printf("client # %s\n",buf);
fflush(stdout);
write(readfds[k],buf,strlen(buf));
}
}
}
}
break;
}
}
close(sock);
return 0;
}
client.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<string.h>
#include<unistd.h>
void Usage(const char* name)
{
printf("Usage:%s [IP] [port]\n");
}
int main(int argc,char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socket");
return 2;
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[2]));
local.sin_addr.s_addr = inet_addr(argv[1]);
if(connect(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
perror("connecct");
return 3;
}
printf("connect success\n");
char buf[1024];
while(1)
{
printf("client # ");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s <= 0)
{
perror("read");
return 4;
}
else
{
buf[s-1] = 0;
int fd = dup(1);
dup2(sock,1);
printf("%s",buf);
fflush(stdout);
dup2(fd,1);
}
s = read(sock,buf,sizeof(buf)-1);
if(s == 0)
{
printf("server quit\n");
break;
}
else if(s < 0)
{
perror("read");
return 5;
}
else
{
buf[s] = 0;
printf("server #%s\n",buf);
}
}
close(sock);
return 0;
}