5种IO模型:
1.阻塞IO:
在内核将数据准备好之前,系统调用会一直等待,所有的套接字默认都是阻塞方式。
2.非阻塞IO:
如果内核还未将数据准备好,系统调用仍然会直接返回,并返回EWOULDBLOCK错误码。
3.信号驱动IO:
内核将数据准备好的时候,使用SIGIO信号通知应用程序进行IO操作。
4.IO多路转接:
与阻塞IO类似,不同之处在于IO多路转接能同时等待多个文件描述符的就绪状态。
5.异步IO:
由内核在数据拷贝完成时,通知应用程序,与信号驱动IO不同之处在于,信号驱动IO是告诉应用程序何时可以开始拷贝数据。
同步与异步VS同步与互斥:
同步与异步:
同步:
发出调用时,在没有得到结果前,该调用不会返回,一旦调用返回,便得到了结果。即由调用者主动等待这个调用的结果。
异步:
发出调用后,调用者不会立即得到结果,而是被调用者在结果准备好的时候,通过状态、通知等通知调用者或通过回调函数处理这个调用。
同步与互斥:
为了完成某个任务,创建的多个进程或线程,需要协调它们的工作次序而等待、传输信息等所产生的制约关系。
select服务器:
select()函数:
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
说明:系统提供了select()函数实现IO多路转接模型,select()函数可以同时监视多个文件描述符的状态变化,直到被监视的文件描述符中由一个或多个发生了状态改变。
参数:
- nfds:需要监视的最大文件描述符值+1。
- readfds:输入输出型参数,输入时,表示要监视的可读文件描述符集合,输出时,表示监视的可读文件描述符集合中读就绪的文件描述符集合,不关心读事件可置为NULL。
- writefds:输入输出型参数,输入时,表示要监视的可写文件描述符集合,输出时,表示监视的可写文件描述符集合中写就绪的文件描述符集合,不关心写事件可置为NULL。
- exceptfds:输入输出型参数,输入时,表示要监视的异常文件描述符集合,输出时,表示监视的异常文件描述符集合中异常发生的文件描述符集合,不关心异常事件可置为NULL。
- timeout:用于设置select()等待时间。NULL,表示select将一直阻塞等待,直到监听的某个文件符状态改变;0,表示立即返回;特定时间,表示在特定的时间内等待监听文件描述符的状态改变,若特定时间内没有文件符状态改变,select()超时,返回0,若特定时间内有文件描述符状态改变,则select()立即返回改变文件描述符个数,timeout为剩余时间。
返回值:
- 大于0:文件描述符状态改变的个数。
- 等于0:在timeout时间内文件描述符没有状态改变的。
- 等于-1:调用发生错误,错误原因存于errno,此时readfds、writefds、exceptfds、timeout值变为不可预测。
备注:
fd_set:fd_set类型是一个整型数组,类似一个位图,通过其中的对应的位来表示要监视的文件描述符。系统为我们提供了一组操作fd_set的接口,如下所示:
//清除文件描述符集set中相应fd的位
void FD_CLR(int fd, fd_set *set);
//判断文件描述符集set中相应fd的位是否为真
int FD_ISSET(int fd, fd_set *set);
//设置文件描述符集set中相应fd的位
void FD_SET(int fd, fd_set *set);
//清除文件描述符集set的全部位
void FD_ZERO(fd_set *set);
timeval:
struct timeval
{
__time_t tv_sec; /* Seconds. */
__suseconds_t tv_usec; /* Microseconds. */
};
timeval结构体表示一段时间,tv_sec表示多少秒,tv_usec表示一秒内的多少微秒。
socket就绪条件:
读就绪:
- 内核中接收缓冲区的字节数,大于等于低水位标记SO_RCVLOWAT。此时可以非阻塞读该文件描述符,
- socket TCP通信中对端关闭连接。此时对该socket对,则返回0.
- 监听socket上有新的连接请求。
- socket上有未处理的错误。
写就绪:
- 内核中发送缓冲区的空余字节数,大于等于低水位标记SO_SNDLOWAT。此时可以无阻塞的写,并且返回值大于0。
- socket的写操作被关闭。此时对该socket进行写,会触发SIGPIPE信号。
- socket使用非阻塞connect连接成功或失败之后。
- socket上有未读取的错误。
异常就绪:
- select上收到带外数据。
特点:
- 可监视的文件描述符个数取决于
sizeof(fd_set)*8
的大小,此参数可调整,但涉及编译内核。 - 将要监视的文件描述符加入监视文件描述符集的同时,需要一个额外的数组来保存加入监视文件描述符集的文件描述符。原因有两个:一个是因为,select返回后,会将监视文件描述符中未发生事件的文件描述符清除,再次使用select时,必须重新利用数组重建监视文件描述符集,在重建时获得最大文件描述符,用于select第一个参数。另外一个原因是,在select返回后,需要利用数组和返回的发生事件文件描述符集进行FD_ISSET判断文件描述符的事件是否发生。
优点:
- select可以同时等待多个文件描述符,将多个文件描述符的等待数据的时间复用,节省了程序总体等待数据的时间。
缺点:
- select可监视的文件描述符个数太少。
- 每次调用select前都要手动重新设置监视文件描述符集。
- 每次调用select时都要将监视文件描述符集从用户态拷贝到内核态,在监视文件描述符集很大的时候开销会很大。
- 每次调用select时内核都要遍历监视文件描述符集,在监视文件描述符集很大的时候开销会很大。
select服务器:
利用select编写多连接ECHO服务器。
server.c:
#include<stdio.h>
#include<unistd.h>
#include<sys/select.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
void Init(int *fd_list,int list_size){
int i = 0;
for(;i < list_size;i++){
fd_list[i] = -1;
}
}
void Reload(int listen_fd,int *connect_fds,int fds_size,fd_set *read_fds,int *maxfd){
FD_ZERO(read_fds);
FD_SET(listen_fd,read_fds);
int max = listen_fd;
int i = 0;
for(;i < fds_size;i++){
if(connect_fds[i] != -1){
FD_SET(connect_fds[i],read_fds);
if(connect_fds[i] > max){
max = connect_fds[i];
}
}
}
*maxfd = max;
}
void Add(int fd,int *connect_fds,int fds_size){
int i = 0;
for(;i < fds_size;i++){
if(connect_fds[i] == -1){
connect_fds[i] = fd;
break;
}
}
}
int main(int argc,char *argv[]){
if(argc != 3){
printf("Usage: ./server ip port\n");
return -1;
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
perror("socket");
return -2;
}
int opt = 1;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
int ret = bind(sock,(struct sockaddr*)&addr,sizeof(struct sockaddr_in));
if(ret < 0){
perror("bind");
return -3;
}
ret = listen(sock,5);
if(ret < 0){
perror("listen");
return -4;
}
fd_set read_fds;
int connect_fds[1024];
Init(connect_fds,sizeof(connect_fds)/sizeof(int));
while(1){
int maxfd;
Reload(sock,connect_fds,sizeof(connect_fds)/sizeof(int),&read_fds,&maxfd);
printf("before select:%d\n",FD_ISSET(sock,&read_fds));
int ret = select(maxfd+1,&read_fds,NULL,NULL,NULL);
printf("after select:%d\n",FD_ISSET(sock,&read_fds));
if(ret < 0){
perror("select");
continue;
}else if(ret == 0){
printf("error! select wait fail\n");
continue;
}else{
if(FD_ISSET(sock,&read_fds)){
struct sockaddr_in connect_addr;
socklen_t len = sizeof(connect_addr);
int connect_sock = accept(sock,(struct sockaddr*)&connect_addr,&len);
if(connect_sock < 0){
perror("accept");
continue;
}
printf("client %s:%d\n",inet_ntoa(connect_addr.sin_addr),ntohs(connect_addr.sin_port));
Add(connect_sock,connect_fds,sizeof(connect_fds)/sizeof(int));
}
int i = 0;
for(;i < sizeof(connect_fds)/sizeof(int);i++){
if(connect_fds[i] == -1){
continue;
}else if(!FD_ISSET(connect_fds[i],&read_fds)){
continue;
}else{
char buf[1024] = {0};
ssize_t readsize = read(connect_fds[i],buf,sizeof(buf)-1);
if(readsize < 0){
perror("read");
continue;
}
if(readsize == 0){
printf("client say goodbay\n");
close(connect_fds[i]);
connect_fds[i] = -1;
continue;
}
buf[readsize] = 0;
printf("client $ %s",buf);
write(connect_fds[i],buf,strlen(buf));
}
}
}
}
return 0;
}
client.c:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
int main(int argc,char *argv[]){
if(argc != 3){
perror("Usage :./server ip port\n");
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
perror("socket");
return -1;
}
int ret = connect(sock,(struct sockaddr*)&addr,sizeof(addr));
if(ret < 0){
perror("connect");
return -2;
}
while(1){
char buf[1024] = {0};
printf("> ");
fflush(stdout);
read(0,buf,sizeof(buf)-1);
ssize_t writesize = write(sock,buf,strlen(buf));
if(writesize < 0){
perror("write");
continue;
}
ssize_t readsize = read(sock,buf,sizeof(buf)-1);
if(readsize < 0){
perror("read");
continue;
}
if(readsize == 0){
printf("server close\n");
break;
}
buf[readsize] = 0;
printf("server say:%s",buf);
}
close(sock);
return 0;
}
结果演示: