以美食为主的网站栏目怎么做,建设工程人员押证在哪个网站查,西安企业网站设计哪家专业,优化公司流程制度11.IO模型
什么是IO: IO 是 Input/Output 的缩写#xff0c;指的是输入和输出。在计算机当中#xff0c;IO 操作通常指将数据从一个设备或文件中读取到计算机内存中#xff0c;或将内存中的数据写入设备或文件中。这些设备可以包括硬盘驱动器、网卡、键盘、屏幕等。 通常用…11.IO模型
什么是IO: IO 是 Input/Output 的缩写指的是输入和输出。在计算机当中IO 操作通常指将数据从一个设备或文件中读取到计算机内存中或将内存中的数据写入设备或文件中。这些设备可以包括硬盘驱动器、网卡、键盘、屏幕等。 通常用户进程中的一个完整I/O分为两个阶段 用户进程空间→内核空间 内核空间→设备空间 I/O分为内存I/O、网络I/O和磁盘I/O三种 IO操作的两个阶段
Linux中进程无法直接操作I/O设备其必须通过系统调用请求内核来协助完成I/O操作。 内核会为每个I/O设备维护一个缓冲区。 对于一个输入操作来说进程I/O系统调用后内核会先看缓冲区中有没有相应的缓存数据没有的话再到设备比如网卡设备中读取因为设备I/O一般速度较慢需要等待 内核缓冲区有数据则直接复制到用户进程空间。 所以对于一个网络输入操作通常包括两个不同阶段
等待网络数据到达网卡把数据从网卡读取到内核缓冲区准备好数据。从内核缓冲区复制数据到用户进程空间。
网络I/O的本质是对socket的读取socket在Linux系统中被抽象为流I/O可以理解为对流的操作。 网络I/O的模型可分为两种
异步I/O(asynchronous I/O)同步I/O(synchronous I/O)
同步I/O又包括
阻塞I/O(blocking I/O)非阻塞I/O(non-blocking I/O)多路复用I/O(multiplexing I/O)信号驱动I/O(signal-driven I/O)
强调一下信号驱动I/O属于同步I/O原因往后看。 信号驱动I/O和异步I/O只作概念性的讲解不作为学习重点。
五种I/O模型
阻塞I/O(blocking I/O)
对于一个套接字上的输入操作第一步通常涉及等待数据从网络中到达当所有等待分组到达时它被复制到内核中的某个缓冲区。第二步是把数据从内核缓冲区复制到应用程序缓冲区。 同步阻塞I/O模型是最常用、最简单的模型。在Linux中默认情况下所有套接字都是阻塞的。下面我们以阻塞套接字的recvfrom的调用图来说明阻塞如图所示
非阻塞I/O(non-blocking I/O)
非阻塞的recvform系统调用之后进程并没有被阻塞内核马上返回给进程如果数据还没准备好此时会返回一个errorEAGAIN或EWOULDBLOCK。 进程在返回之后可以先处理其他的业务逻辑稍后再发起recvform系统调用。 采用轮询的方式检查内核数据直到数据准备好。再拷贝数据到进程进行数据处理。 在Linux下可以通过设置套接字选项使其变为非阻塞。非阻塞的套接字的recvfrom操作如图所示 可以看到前三次调用recvfrom请求时并没有数据返回内核返回errno(EWOULDBLOCK)并不会阻塞进程。 当第四次调用recvfrom时数据已经准备好了于是将它从内核空间拷贝到程序空间处理数据。 在非阻塞状态下I/O执行的等待阶段并不是完全阻塞的但是第二个阶段依然处于一个阻塞状态调用者将数据从内核拷贝到用户空间这个阶段阻塞)。
多路复用I/O(multiplexing I/O)
I/O多路复用的好处在于单个进程就可以同时处理多个网络连接的I/O。它的基本原理是不再由应用程序自己监视连接而由内核替应用程序监视文件描述符。 以select函数为例当用户进程调用了select那么整个进程会被阻塞而同时kernel会“监视”所有select负责的socket当任何一个socket中的数据准备好select就会返回。 这个时候用户进程再调用read操作将数据从内核拷贝到用户进程如下图所示。
信号驱动I/O(signal-driven I/O)
该模型允许socket进行信号驱动I/O并注册一个信号处理函数进程继续运行并不阻塞。当数据准备好时进程会收到一个SIGIO信号可以在信号处理函数中调用I/O操作函数处理数据如图所示 注意虽然信号驱动IO在注册完信号处理函数以后就可以做其他事情了。但是第二阶段拷贝数据的过程当中进程依然是被阻塞的而后要介绍的异步IO是完全不会阻塞进程的所以信号驱动虽然具有异步的特点但依然属于异步IO
异步I/O(asynchronous I/O)
相对于同步I/O异步I/O不是按顺序执行。用户进程进行aio_read系统调用之后就可以去处理其他逻辑了无论内核数据是否准备好都会直接返回给用户进程不会对进程造成阻塞。这是因为aio_read只向内核递交申请并不关心有没有数据。 等到数据准备好了内核直接复制数据到进程空间然后内核向进程发送通知此时数据已经在用户空间了可以对数据进行处理。
五种I/O模型比较 前四种I/O模型都是同步I/O操作它们的区别在于第一阶段而第二阶段是一样的在数据从内核复制到应用缓冲区期间用户空间)进程阻塞于recvfrom调用。 相反异步I/O模型在等待数据和接收数据的这两个阶段都是非阻塞的可以处理其他的逻辑用户进程将整个I/O操作交由内核完成内核完成后会发送通知。在此期间用户进程不需要检查I/O操作的状态也不需要主动拷贝数据。 在了解了Linux的I/O模型之后我们就可以进行服务器设计了。
12.IO多路复用
I/O多路复用的好处在于单个进程就可以同时处理多个网络连接的I/O。它的基本原理是不再由应用程序自己监视连接而由内核替应用程序监视文件描述符。
在Linux系统中select、poll和epoll是三种常用的I/O多路复用技术它们用于处理多个I/O流以实现高效的并发服务器设计。
特性select函数poll函数epoll函数族支持平台几乎所有类Unix系统Unix及类Unix系统主要为Linux系统数据结构位图限制文件描述符数量链表无文件描述符数量限制红黑树高效管理事件文件描述符限制通常最多1024个无限制无限制拷贝开销每次调用时拷贝整个集合每次调用时拷贝整个集合通过回调机制避免拷贝返回就绪描述符需要遍历所有描述符识别就绪状态直接返回就绪状态的描述符高效返回只包含就绪事件的描述符并发性能低至中等因位图扫描中等因链表扫描高高效的事件通知和数据结构触发模式不支持不支持支持水平触发和边缘触发API复杂度简单直观类似select但更灵活功能丰富但使用相对复杂适用场景小规模并发服务器或客户端中规模并发服务器大规模并发服务器尤其是网络服务
select函数 原型 #include sys/select.h
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);功能监视文件描述符集合等待其中任意一个文件描述符准备好进行I/O操作。 参数 nfds文件描述符集合中最大的文件描述符值加1。readfds需要监视可读状态的文件描述符集合。writefds需要监视可写状态的文件描述符集合。exceptfds需要监视异常状态的文件描述符集合。timeout设置select函数的超时时间。 NULL永久阻塞。0非阻塞。 返回值 成功返回准备好的文件描述符个数。超时返回0。出错返回-1并设置errno。 fd_set: 表示文件描述符集合的数据结构在fd_set中每个文件描述符都对应一个位如果该位为1则表示对应的文件描述符处于准备好的状态如果该位为0则表示对应的文件描述符未准备好。fd_set提供了一些宏操作来方便地对文件描述符集合进行操作: FD_ZERO(fd_set *set)清空文件描述符集合。FD_SET(int fd, fd_set *set)将指定的文件描述符添加到集合中。FD_CLR(int fd, fd_set *set)从集合中移除指定的文件描述符。FD_ISSET(int fd, fd_set *set)检查指定的文件描述符是否在集合中。FD_COPY(fd_set *src, fd_set *dst)复制源文件描述符集合到目标文件描述符集合。 struct timeval struct timeval {long tv_sec; /* 秒 */long tv_usec; /* 微秒 */
};select实现多路复用
sever.c
#include net.h
#include sys/select.h
#define MAX_SOCK_FD 1024 // 定义最大文件描述符数量为1024int main(int argc, char *argv[])
{int i, ret, fd, newfd; fd_set set, tmpset; Addr_in clientaddr;socklen_t clientlen sizeof(Addr_in); // 定义客户端地址长度clientlen/*检查参数小于3个 直接退出进程*/Argment(argc, argv);/*创建已设置监听模式的套接字*/fd CreateSocket(argv);FD_ZERO(set);FD_ZERO(tmpset);FD_SET(fd, set);while(1){ tmpset set;if( (ret select(MAX_SOCK_FD, tmpset, NULL, NULL, NULL)) 0) // 调用select函数监听文件描述符集合tmpset中的文件描述符ErrExit(select); if(FD_ISSET(fd, tmpset) ){/*接收客户端连接并生成新的文件描述符*/if( (newfd accept(fd, (Addr *)clientaddr, clientlen) ) 0) perror(accept);printf([%s:%d]已建立连接\n, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); // 输出客户端地址和端口号FD_SET(newfd, set); // 将新的文件描述符加入文件描述符集合set}else{ for(i fd 1; i MAX_SOCK_FD; i){ if(FD_ISSET(i, tmpset)){ if( DataHandle(i) 0){ if( getpeername(i, (Addr *)clientaddr, clientlen) )perror(getpeername); printf([%s:%d]断开连接\n, inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port)); // 输出客户端地址和端口号FD_CLR(i, set); // 从文件描述符集合set中移除文件描述符i}}}}}return 0; // 程序正常结束返回0
}socket.c
#include net.hvoid Argment(int argc, char *argv[]){if(argc 3){fprintf(stderr, %saddrport\n, argv[0]);exit(0);}
}
int CreateSocket(char *argv[]){/*创建套接字*/int fd socket(AF_INET, SOCK_STREAM, 0);if(fd 0)ErrExit(socket);/*允许地址快速重用*/int flag 1;if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, flag, sizeof(flag) ) )perror(setsockopt);/*设置通信结构体*/Addr_in addr;bzero(addr, sizeof(addr) );addr.sin_family AF_INET;addr.sin_port htons( atoi(argv[2]) );/*绑定通信结构体*/if( bind(fd, (Addr *)addr, sizeof(Addr_in) ) )ErrExit(bind);/*设置套接字为监听模式*/if( listen(fd, BACKLOG) )ErrExit(listen);return fd;
}
int DataHandle(int fd){char buf[BUFSIZ] {};Addr_in peeraddr;socklen_t peerlen sizeof(Addr_in);if( getpeername(fd, (Addr *)peeraddr, peerlen) )perror(getpeername);int ret recv(fd, buf, BUFSIZ, 0);if(ret 0)perror(recv);if(ret 0){printf([%s:%d]data: %s\n, inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);}return ret;
}net.h
#ifndef _NET_H_
#define _NET_H_#include stdio.h
#include stdlib.h
#include sys/socket.h
#include netinet/in.h
#include netinet/tcp.h
#include arpa/inet.h
#include unistd.h
#include strings.h
#include errno.htypedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
#define BACKLOG 5
#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)void Argment(int argc, char *argv[]);
int CreateSocket(char *argv[]);
int DataHandle(int fd);#endif成功建立连接
poll 函数
与select函数的功能类似 原型 #include poll.h
int poll(struct pollfd *fds, nfds_t nfds, int timeout);功能监视一组文件描述符的I/O状态等待它们中的一个或多个变为可读、可写或异常状态。 参数 fds一个指向pollfd结构体数组的指针该数组包含了需要监视的文件描述符及其对应的事件。nfdsfds数组中的元素个数。timeout等待的最长时间以毫秒为单位如果设置为0则表示立即返回如果设置为负数则表示无限期等待。 返回值 如果成功返回发生事件的文件描述符个数如果超时返回0如果出错返回-1。 struct pollfd struct pollfd {int fd; // 文件描述符short events; // 注册的事件short revents; // 返回的事件
};fd这是文件描述符即需要被监视的句柄。 events这是一个位掩码定义了我们关心的文件描述符的事件类型。常用的事件类型有 POLLIN表示文件描述符可读。POLLOUT表示文件描述符可写。POLLPRI表示文件描述符有紧急数据带外数据可读。POLLERR表示文件描述符发生错误。POLLHUP表示文件描述符挂起。 revents这是一个输出参数当poll返回时它指出了文件描述符上实际发生了哪些事件。 nfds_t: typedef unsigned long int nfds_t;poll实现多路复用
sever.c
#include net.h
#include poll.h#define MAX_SOCK_FD 1024
int main(int argc, char *argv[])
{int i, j, fd, newfd;nfds_t nfds 1;struct pollfd fds[MAX_SOCK_FD] {};Addr_in addr;socklen_t addrlen sizeof(Addr_in);/*检查参数小于3个 直接退出进程*/Argment(argc, argv);/*创建已设置监听模式的套接字*/fd CreateSocket(argv);fds[0].fd fd;fds[0].events POLLIN;while(1){if( poll(fds, nfds, -1) 0)ErrExit(poll);for(i 0; i nfds; i){/*接收客户端连接并生成新的文件描述符*/if(fds[i].fd fd fds[i].revents POLLIN){if( (newfd accept(fd, (Addr *)addr, addrlen) ) 0)perror(accept);fds[nfds].fd newfd;fds[nfds].events POLLIN;printf([%s:%d][nfds%lu] connection successful.\n, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), nfds);}/*处理客户端数据*/if(i 0 fds[i].revents POLLIN){if(DataHandle(fds[i].fd) 0){if( getpeername(fds[i].fd, (Addr *)addr, addrlen) 0)perror(getpeername);printf([%s:%d][fd%d] exited.\n, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), fds[i].fd);close(fds[i].fd);for(ji; jnfds-1; j)fds[j] fds[j1];nfds--;i--;}}}}close(fd);return 0;
}
socket.c
#include net.hvoid Argment(int argc, char *argv[]){if(argc 3){fprintf(stderr, %saddrport\n, argv[0]);exit(0);}
}
int CreateSocket(char *argv[]){/*创建套接字*/int fd socket(AF_INET, SOCK_STREAM, 0);if(fd 0)ErrExit(socket);/*允许地址快速重用*/int flag 1;if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, flag, sizeof(flag) ) )perror(setsockopt);/*设置通信结构体*/Addr_in addr;bzero(addr, sizeof(addr) );addr.sin_family AF_INET;addr.sin_port htons( atoi(argv[2]) );/*绑定通信结构体*/if( bind(fd, (Addr *)addr, sizeof(Addr_in) ) )ErrExit(bind);/*设置套接字为监听模式*/if( listen(fd, BACKLOG) )ErrExit(listen);return fd;
}
int DataHandle(int fd){char buf[BUFSIZ] {};Addr_in peeraddr;socklen_t peerlen sizeof(Addr_in);if( getpeername(fd, (Addr *)peeraddr, peerlen) )perror(getpeername);int ret recv(fd, buf, BUFSIZ, 0);if(ret 0)perror(recv);if(ret 0){printf([%s:%d]data: %s\n, inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);}return ret;
}
net.h
#ifndef _NET_H_
#define _NET_H_#include stdio.h
#include stdlib.h
#include sys/socket.h
#include netinet/in.h
#include netinet/tcp.h
#include arpa/inet.h
#include unistd.h
#include strings.h
#include errno.htypedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
#define BACKLOG 5
#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)void Argment(int argc, char *argv[]);
int CreateSocket(char *argv[]);
int DataHandle(int fd);#endif 成功实现多路复用
epoll函数族
epoll函数族用于高效的I/O事件管理特别适用于高并发服务器应用
头文件: #include sys/epoll.h epoll_create: int epoll_create(int size);功能创建一个epoll实例并返回一个文件描述符作为该epoll实例的标识。 参数size之前用于定义事件队列的大小但在Linux 2.6以后的版本中已被忽略。通常设置为0。 返回值 成功返回一个非负的文件描述符。失败返回-1并设置errno。 epoll_ctl: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);功能向epoll实例中添加、修改或删除文件描述符及其相关事件。 参数 epfdepoll实例的文件描述符。op操作类型可以是EPOLL_CTL_ADD添加新的文件描述符、EPOLL_CTL_MOD修改已注册的文件描述符的事件或EPOLL_CTL_DEL删除一个文件描述符。fd要操作的文件描述符。event指向epoll_event结构的指针用于指定事件类型和文件描述符的数据。 返回值 成功返回0。失败返回-1并设置errno。 epoll_wait: int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);功能阻塞等待已注册的文件描述符上的事件发生。 参数 epfdepoll实例的文件描述符。events指向epoll_event结构体数组的指针用于存储发生的事件。maxevents可以接收的事件数量的最大值。timeout超时时间以毫秒为单位决定函数的阻塞行为。设置为0立即返回设置为-1则无限期阻塞。 返回值 成功返回就绪事件的个数。超时返回0。失败返回-1并设置errno。
epoll实现多路复用
sever.c
#include net.h
#include sys/epoll.h#define MAX_SOCK_FD 1024int main(int argc, char *argv[])
{int i, nfds, fd, epfd, newfd;Addr_in addr;socklen_t addrlen sizeof(Addr_in);struct epoll_event tmp, events[MAX_SOCK_FD] {};/*检查参数小于3个 直接退出进程*/Argment(argc, argv);/*创建已设置监听模式的套接字*/fd CreateSocket(argv);if( (epfd epoll_create(1)) 0)ErrExit(epoll_create);tmp.events EPOLLIN;tmp.data.fd fd;if( epoll_ctl(epfd, EPOLL_CTL_ADD, fd, tmp) )ErrExit(epoll_ctl);while(1) {if( (nfds epoll_wait(epfd, events, MAX_SOCK_FD, -1) ) 0)ErrExit(epoll_wait);printf(nfds %d\n, nfds);for(i 0; i nfds; i) {if(events[i].data.fd fd){/*接收客户端连接并生成新的文件描述符*/if( (newfd accept(fd, (Addr *)addr, addrlen) ) 0)perror(accept);printf([%s:%d] connection.\n, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );tmp.events EPOLLIN;tmp.data.fd newfd;if( epoll_ctl(epfd, EPOLL_CTL_ADD, newfd, tmp) )ErrExit(epoll_ctl);}else{/*处理客户端数据*/if(DataHandle(events[i].data.fd) 0){if( epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL) )ErrExit(epoll_ctl);if( getpeername(events[i].data.fd, (Addr *)addr, addrlen) )perror(getpeername);printf([%s:%d] exited.\n, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );close(events[i].data.fd);}}}}close(epfd);close(fd);return 0;
}
socket.c
#include net.hvoid Argment(int argc, char *argv[]){if(argc 3){fprintf(stderr, %saddrport\n, argv[0]);exit(0);}
}
int CreateSocket(char *argv[]){/*创建套接字*/int fd socket(AF_INET, SOCK_STREAM, 0);if(fd 0)ErrExit(socket);/*允许地址快速重用*/int flag 1;if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, flag, sizeof(flag) ) )perror(setsockopt);/*设置通信结构体*/Addr_in addr;bzero(addr, sizeof(addr) );addr.sin_family AF_INET;addr.sin_port htons( atoi(argv[2]) );/*绑定通信结构体*/if( bind(fd, (Addr *)addr, sizeof(Addr_in) ) )ErrExit(bind);/*设置套接字为监听模式*/if( listen(fd, BACKLOG) )ErrExit(listen);return fd;
}
int DataHandle(int fd){char buf[BUFSIZ] {};Addr_in peeraddr;socklen_t peerlen sizeof(Addr_in);if( getpeername(fd, (Addr *)peeraddr, peerlen) )perror(getpeername);int ret recv(fd, buf, BUFSIZ, 0);if(ret 0)perror(recv);if(ret 0){printf([%s:%d]data: %s\n, inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);}return ret;
}
net.h
#ifndef _NET_H_
#define _NET_H_#include stdio.h
#include stdlib.h
#include sys/socket.h
#include netinet/in.h
#include netinet/tcp.h
#include arpa/inet.h
#include unistd.h
#include strings.h
#include errno.htypedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
#define BACKLOG 5
#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)void Argment(int argc, char *argv[]);
int CreateSocket(char *argv[]);
int DataHandle(int fd);#endif