(1)阻塞式IO模型:
最流行的I/O模型是阻塞式I/O模型,默认情况下,所有的套接字都是阻塞的。
如上图所示,进程调用recvfrom,其系统调用直到数据报到达且被复制到应用进程的缓冲区中或发生错误才返回。最常见的错误是系统调用被信号中断,我们说进程在从调用recvfrom开始到它返回的整段时间内是被阻塞的。recvfrom成功返回后,应用进程开始处理数据报。
(2)非阻塞式I/O模型:
进程把一个套接字设置成非阻塞是在通知内核:当所请求的I/O操作非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误。
前三次调用recvfrom时没有数据可返回,因此内核转而立即返回一个EWOULDBLOCK错误。第四次调用recvfrom时已有一个数据报准备好,它被复制到应用进程缓冲区,于是recvfrom成功返回。我们接着处理数据。
当一个应用进程像这样对一个非阻塞描述符循环调用recvfrom时,我们称之为轮询。应用进程持续轮询内核,以查看某个操作是否就绪。这么做往往耗费大量CPU时间,不过这种模型偶尔也会遇到,通常是在专门提供某一种功能的系统中才有。
(3)I/O复用模型:
有了I/O复用。我们就可以调用select或poll,阻塞在这两个系统调用中的某一个之上,而不是阻塞在真正的I/O系统调用上。
我们阻塞于select调用,等待数据报套接字变为可读。当select返回套接字可读这一条件时,我们调用recvfrom把所读数据报复制到应用进程缓冲区。
比较I/O复用图和I/O阻塞图,I/O复用并不显得有什么优势,实际上由于使用select需要两个而不是单个系统调用,I/O复用还稍有优势。使用select的优势在于我们可以等待多个描述符就绪。
(4)信号驱动式I/O模型:
我们也可以用信号,让内核在描述符就绪时发送SIGNO信号通知我们。我们称这种模型为信号驱动式I/O。
我们首先开启套接字的信号驱动式I/O功能,并通过sigaction系统调用安装一个信号处理函数。该系统调用将立即返回,我们的进程继续工作。也就是说它没有阻塞。当数据报准备好读取时,内核就为进程产生一个SIGNIO的信号。我们随后可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已备好待处理,也可以立即通知主循环,让它读取数据报。
无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间进程不被阻塞。主循环可以继续执行,只要等待来自信号处理函数的通知:既可以是数据报已准备好,也可以是数据报已准备好读取。
(5)异步I/O模型:
其工作机制是:告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。这种模型与前一个的信号驱动模型的主要区别在于:信号驱动式I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。
各I/O模型的比较:
select函数:
该函数允许进程指示内核等待多个事件中的任何一个,并只有在一个或多个事件发生或经历一段指定的时间后才唤醒它。
#include<sys/select.h>
#include<sys/time.h>
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *expectset,const struct timeval *timeout);
返回:若有就绪描述符则为其数目,若超时则为0,若出错则为-1.
参数timeout:它告知内核等待所指定描述符的任何一个就绪可花多长时间。其timeval结构用于指定这段时间的秒数核微妙数。
struct timeval
{
long tv_sec;
long tv_usec;
}
这个参数有以下三种可能:
(1)永远等待下去:仅在有一个描述符准备好I/O才返回。为此,我们把参数设置为空指针。
(2)等待一段固定时间:在有一个描述符准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微妙数。
(3)根本不等待:检查描述符后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。
中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述符。select使用描述符集,通常是一个整数数组,其中每个整数中的每一位对应一个描述符。举例来说,假设使用32为整数,那么该数组的第一个元素对应于描述符0~31,第二个元素对应于描述符32~63,依次类推。这些隐藏在名为fd_set的数据类型和以下四个宏中:
void FD_ZERO(fd_set *fdset);
void FD_SET(int fd,fd_set *fdset);
void FD_CLR(int fd,fd_set *fdset);
void FD_ISSET(int fd,fd_set *fdset);
我们分配一个fd_set数据类型的描述符集,并用这些宏设置或测试该集合中的每一位,也可以用C语言中的赋值语句把它赋值成另外一个描述符集。
select函数的中间三个参数readset、writeset和expectset中,如果我们对某一个的条件不感兴趣,就可以把它设为空指针。
maxfdp1参数指定待测试的描述符个数,它的值是待测试的最大描述符加1,描述符0,1,2....一直到maxfdp1 - 1均被测试。
下面给出select的一个简单程序,该程序实现客户端给服务器发送消息,服务器将消息回射回来,解决当有多个客户端向服务器发送数据时,服务器能辨识出是与哪个客户端通信。。。
服务器:
#include"../util.h"
#include<malloc.h>
typedef struct server_context_st
{
int cli_cnt; //当前客户端的连接个数
int clifds[SIZE]; //客户端连接成功套接字
fd_set allfds; //描述符集
int maxfd; //待测试的描述符个数
}server_context_st;
static server_context_st *s_srv_ctx = NULL;
//结构体的初始化
int server_init()
{
s_srv_ctx = (server_context_st *)malloc(sizeof(server_context_st));
if(s_srv_ctx == NULL)
return -1;
memset(s_srv_ctx,0,sizeof(server_context_st));
for(int i = 0; i < SIZE; ++i)
{
s_srv_ctx->clifds[i] = -1;
}
return 0;
}
void server_uninit()
{
if(s_srv_ctx)
{
free(s_srv_ctx);
s_srv_ctx = NULL;
}
}
int create_server_proc(const char*ip,short port)
{
int fd;
//创建套接字
fd = socket(AF_INET,SOCK_STREAM,0);
if(fd == -1)
{
perror("socket");
return -1;
}
struct sockaddr_in addrSer;
addrSer.sin_family = AF_INET;
addrSer.sin_port = htons(port);
addrSer.sin_addr.s_addr = inet_addr(ip);
socklen_t addrlen = sizeof(struct sockaddr);
//绑定
int res = bind(fd,(struct sockaddr*)&addrSer,addrlen);
if(res == -1)
{
perror("bind");
return -1;
}
//监听
listen(fd,LISTENQ);
return fd;
}
int accept_client_proc(int srvfd)
{
struct sockaddr_in addrCli;
socklen_t addrlen = sizeof(struct sockaddr);
int clifd;
ACCEPT:
clifd = accept(srvfd,(struct sockaddr *)&addrCli,&addrlen);
if(clifd == -1)
{
goto ACCEPT;
}
printf("accept a new client: %s:%d\n",inet_ntoa(addrCli.sin_addr),addrCli.sin_port);
int i;
//在数组中遍历描述符为-1的项
for(i = 0; i < SIZE; ++i)
{
if(s_srv_ctx->clifds[i] == -1)
{
s_srv_ctx->clifds[i] = clifd;
//修改客户端连接个数
s_srv_ctx->cli_cnt++;
break;
}
}
if(i == SIZE)
{
printf("server over load.\n");
return -1;
}
}
void handle_client_msg(int fd,char* buf)
{
//将接收到的数据又发送到客户端
printf("recv buf is:>%s\n",buf);
send(fd,buf,strlen(buf)+1,0);
}
void recv_client_msg(fd_set *readfds)
{
int clifd;
char buffer[256];
int n;
for(int i = 0; i < s_srv_ctx->cli_cnt;++i)
{
clifd = s_srv_ctx->clifds[i];
if(clifd < 0)
continue;
if(FD_ISSET(clifd,readfds))
{
n = recv(clifd,buffer,256,0);
//n小于0时说明客户端断开连接
if(n <= 0)
{
FD_CLR(clifd,&s_srv_ctx->allfds);
close(clifd);
s_srv_ctx->clifds[i] = -1;
s_srv_ctx->cli_cnt--;
continue;
}
//处理来自客户端的数据
handle_client_msg(clifd,buffer);
}
}
}
int handle_client_proc(int srvfd)
{
int clifd = -1;
int retval = 0;
fd_set *readfds = &s_srv_ctx->allfds;
struct timeval tv;
while(1)
{
//清空描述符集合
FD_ZERO(readfds);
//置位
FD_SET(srvfd,readfds);
s_srv_ctx->maxfd = srvfd;
tv.tv_sec = 30;
tv.tv_usec = 0;
int i;
for(i = 0; i < s_srv_ctx->cli_cnt; ++i)
{
clifd = s_srv_ctx->clifds[i];
//将连接成功的客户端在集合内置位
FD_SET(clifd,readfds);
s_srv_ctx->maxfd = (clifd > s_srv_ctx->maxfd ? clifd : s_srv_ctx->maxfd);
}
retval = select(s_srv_ctx->maxfd+1,readfds,NULL,NULL,&tv);
if(retval == -1)
{
perror("select");
return -1;
}
if(retval == 0)
{
perror("select time out.\n");
continue;
}
//accept
if(FD_ISSET(srvfd,readfds))
{
//接受新的客户端的连接
accept_client_proc(srvfd);
}
else
{
//接收已连接客户端的数据
recv_client_msg(readfds);
}
}
}
int main(int argc,char*argv[])
{
int sockSer;
if(server_init() < 0)
perror("server_init");
sockSer = create_server_proc(IPADDR,PORT);
if(sockSer < 0)
{
perror("create_server_porc");
goto err;
}
handle_client_proc(sockSer);
return 0;
err:
server_uninit();
return -1;
}
客户端:
#include"../util.h"
void handle_connection(int sockfd)
{
fd_set readfds;
int maxfd = sockfd;
struct timeval tv;
while(1)
{
//将描述符集合清空
FD_ZERO(&readfds);
FD_SET(sockfd,&readfds);
maxfd = sockfd;
tv.tv_sec = 5;
tv.tv_usec = 0;
int res = select(maxfd + 1,&readfds,NULL,NULL,&tv);
if(res == -1)
{
perror("select");
return;
}
if(res == 0)
{
printf("client time out.\n");
continue;
}
int n;
char recvbuf[256];
if(FD_ISSET(sockfd,&readfds))
{
n = recv(sockfd,recvbuf,256,0);
//n小于0说明服务器关闭
if(n <= 0)
{
printf("server is closed.\n");
close(sockfd);
FD_CLR(sockfd,&readfds);
return ;
}
printf("client recv self msg:>%s\n",recvbuf);
//sleep(3);
char sendbuf[256];
scanf("%s",sendbuf);
send(sockfd,sendbuf,strlen(sendbuf)+1,0);
}
}
}
int main(int argc,char*argv[])
{
int sockCli;
//创建套接字
sockCli = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in addrSer;
addrSer.sin_family = AF_INET;
addrSer.sin_port = htons(PORT);
addrSer.sin_addr.s_addr = inet_addr(IPADDR);
socklen_t addrlen = sizeof(struct sockaddr);
//连接服务器
int res = connect(sockCli,(struct sockaddr *)&addrSer,addrlen);
if(res < 0)
perror("connect");
printf("client connect server ok.\n");
//发送数据
char sendbuf[256];
scanf("%s",sendbuf);
send(sockCli,sendbuf,strlen(sendbuf)+1,0);
handle_connection(sockCli);
return 0;
}
公用头文件:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<poll.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<string.h>
#define IPADDR "127.0.0.1"
#define PORT 8787
#define MAXLINE 1024
#define LISTENQ 5
#define SIZE 10
#define OPEN_SIZE 5
程序的执行结果:
程序的执行结果如图所示。。。