Linux网络编程——I/O多路复用
来源:互联网 发布:华为mate9网络频段 编辑:程序博客网 时间:2024/06/05 09:29
学习笔记,小白可以相互学习,大佬看到能告诉咱理解不对的地方就好了。
I/O模型
在UNIX/Linux下主要有4种i/o模型:
1.阻塞i/o:最常用,最简单,效率最低。
大部分程序使用的就是阻塞i/o模式,缺省下,套接字建立后所处于的模式就是它。
2.非阻塞i/o:可以防止进程在i/o操作上,需要轮询。
用程序不停地来检查i/o操作是否就绪,这个是非常浪费cpu资源的操作。
可以使用fcntl()设置一个套接字的标志为O_NONBLOCK来实现非阻塞
int fcntl(int fd,int cmd,long arg)
int flag; flag = fcntl(sockfd,F_GETFL,0);//第二个参数是获得属性命令
flag |= O_NONBLOCK; //设置非阻塞的属性
fcntl = (sockfd,F_GETFL,flag); //再把非阻塞的属性赋给sockfd
3.i/o多路复用:允许同时对多个i/o进行控制
4.信号驱动i/o:一种异步通信模型
I/O多路复用
应用程序中同时处理多路输入输出流,
若采用阻塞模式,将得不到预期的目的;
若采用非阻塞模式,对多个输入进行轮询,但又太浪费cpu时间。
若设置多个进程分别处理一条数据路,将产生进程间同步与通信问题,使程序变得复杂。
最好的方法是使用i/o多路复用。
i/o多路复用的基本思想是:
1. 将所有要处理的文件描述符存储到一张表当中;
2. 检测表当中有没有已经就绪的文件描述符,如果有返回就绪的文件描述符;
3. 轮询所有就绪的文件描述符
while(1) {
if(listenfd)
说明是新的客户端发起了连接请求;
if(connfd)
说明是已经连接的客户端发送了数据请求;
if(普通文件)
的到普通文件的数据;
if(0)
标准输入
}
根据处理细节的不同,分为三种类型:
select()机制:
1. 根据描述符处理事件不同,创建不同的表,例如将所有要读的文件描述符添加到一张读的表中
2. 检测表当中是否有就额绪的文件描述符,如果有返回有的状态,同时返回就绪的文件描述符
3. 轮询 :a找到那些文件描述符就绪;b处理数据
函数原型:int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
功能:检测是否有文件描述符处于就绪状态,有返回。没有就一直阻塞。
参数:nfds: 最大文件描述符+1
readfds:读的文件描述符表
writefds: 写的文件描述符表
exceptfds: 错误处理文件描述符表
timeout: 超时时间;不设置用NULL,表示一直阻塞
返回:成功返回准备就绪的文件描述符的个数;失败返回-1;超时返回0;
宏函数:
void FD_CLR(int fd, fd_set *set); //将fd从集合set当中清除;
int FD_ISSET(int fd, fd_set *set); //判断fd是否在集合set当中;
void FD_SET(int fd, fd_set *set); //将fd添加到集合set当中;
void FD_ZERO(fd_set *set); //清空集合set
int main(){ int ret; int nfds; char buf[256]; struct timeval time; /*创建集合*/ fd_set readfds; FD_ZERO(&readfds); /*添加所要处理的文件描述付*/ FD_SET(0,&readfds); nfds = 1; memset(buf,0,sizeof(buf)); /**如果不设置,表示select阻塞读集合,直到有文件描述府就绪,select函数返回*/ /*设置之后,select函数阻塞3s,如果达到超时时间,改为非阻塞模式,不管有没有文件描述府,都返回*/ while(1) { time.tv_sec = 3; time.tv_usec = 0; fd_set rfds = readfds; /*检测继续的文件描述府*/ ret = select(nfds,&rfds,NULL,NULL,&time); if(-1 == ret) { perror("select"); return -1; } else if(0 ==ret) { printf("time out....\n"); continue; } int fd; for(fd = 0;fd < nfds; fd++) { if (FD_ISSET(fd,&rfds)) { ret = read(0,buf,sizeof(buf)); if(-1 == ret) { perror("read"); return -1; } printf("buf: %s\n",buf); } } memset(buf,0,sizeof(buf)); } return 0;}
poll()机制:
1. 创建一个集合;添加(文件描述符及其所要处理的事件)
2. 检测集合当中是否有事件发生(是否有文件描述符准备就绪);如果有返回有的状态,同时返回就绪的文件描述符及事件;
3. 轮询 :a. 找所发生的事件;b. 处理数据
函数原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
函数功能:检测是否有文件描述符和事件处于就绪状态,有返回。没有一直阻塞。
参数: fds:事件和文件描述符的集合;
struct pollfd {
int fd; /* 文件描述符*/
short events; /* 请求的事件 */
short revents; /*返回事件 */
};
nfds: 最大文件描述符+1;
timeout: 超时时间;ms级
#include <stdio.h>#include <string.h>#include <stdlib.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h>#include <arpa/inet.h>#include <poll.h>int client(int connfd){int ret;char buf[256];memset(buf, 0, sizeof(buf));ret = read(connfd, buf, sizeof(buf));if (ret == -1) {perror("server->read");return -1;} else if (ret == 0) {close(connfd);return -1;}printf("buf : %s\n", buf);ret = write(connfd, buf, sizeof(buf));if (ret == -1) {perror("server->write");return -1;}return 0;}int main(int argc, char *argv[]){int listenfd;int ret;socklen_t addrlen;int connfd;pid_t pid;char buf[256];struct sockaddr_in srvaddr;struct sockaddr_in cltaddr;/* 1. 创建服务器(创建一socket套接字);socket */listenfd = socket(AF_INET, SOCK_STREAM, 0);if (listenfd == -1) {perror("server->socket");return -1;}printf("create listenfd = %d success\n", listenfd);/* 2. 设置服务器的IP地址和端口号(将socket和服务器的IP地址和端口号进行绑定);bind */memset(&srvaddr, 0, sizeof(struct sockaddr_in));srvaddr.sin_family = AF_INET;srvaddr.sin_port = htons(9999);srvaddr.sin_addr.s_addr = inet_addr("192.168.2.100");ret = bind(listenfd, (const struct sockaddr *)&srvaddr, sizeof(srvaddr));printf("port : %d\n", ntohs(srvaddr.sin_port));if (ret == -1) {perror("server->bind");return -1;}printf("bind success !\n");/* 3. 启动监听(启动服务器); listen */ret = listen(listenfd, 1024);if (ret == -1) {perror("server->listen");return -1;}printf("listen success !\n");/* 创建集合,并且将集合当中的每一个数组元素的fd成员赋值为-1;*/int nfds;int fd;int i;int j;struct pollfd fds[1024];for (i = 0; i < 1024; i++) {fds[i].fd = -1;}/* 添加所需要处理的事件和文件描述符 */fds[0].fd = listenfd;fds[0].events = POLLIN;nfds = listenfd+1;while(1) {/* 检测集合当中是否有继续的文件描述符 */ret = poll(fds, nfds, 5000);if (ret == -1) {perror("poll");return -1;} else if (ret == 0) {printf("timeout\n");continue;}/* 轮循 */for (i = 0; i < nfds; i++) {/* 判断是什么事件 */if (POLLIN == fds[i].revents) {/* 判断,寻找就绪的文件描述符 */if (fds[i].fd != -1) {fd = fds[i].fd;/* 如果是监听套接字listenfd,则建立连接 */if (fd == listenfd) {memset(&cltaddr, 0, sizeof(cltaddr));addrlen = sizeof(socklen_t);connfd = accept(listenfd, (struct sockaddr *)&cltaddr, &addrlen);if (connfd == -1) {perror("accept");return -1;}printf("connfd = %d\n", connfd);for (j = 0; j < 1024; j++) {if (fds[j].fd != -1) {continue;}fds[j].fd = connfd;fds[j].events = POLLIN;#if 0if (nfds <= connfd) {nfds = connfd + 1;}#endifnfds = nfds <= connfd ? connfd+1 : nfds;break;}} else {ret = client(fd);if (ret == -1) {fds[i].fd = -1;}}}}}}close(listenfd);return 0;}
epoll机制:
1. 创建一个集合,添加(文件描述符及其所要处理的事件);
2. 检测集合当中是否有事件发生(是否有文件描述符准备就绪);如果有返回有的状态,同时返回就绪的文件描述符及事件的集合;
3. 轮询 :a. 找所发生的事件;b. 处理数据;
int epoll_create(int size);
功能:创建一个epoll实例
参数: size:epoll实例所能处理的文件描述符的最大个数。而不是容纳的文件描述符的个数。
返回值:成功,返回一个非负的文件描述符。错误,返回- 1,并设置errno
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:设置epoll实例(新增、修改、删除);
参数:epfd:epoll实例
op:执行的动作:
EPOLL_CTL_ADD (新增)
EPOLL_CTL_MOD (修改)
EPOLL_CTL_DEL (删除)
fd:文件描述符,
event:事件;
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* epoll的事件 */
epoll_data_t data; /* 用户数据变量 */
};
The events member is a bit set composed using the following available event types:
事件的成员是一个点集组成使用下面提供的事件类型:
EPOLLIN
The associated file is available for read(2) operations.相关文件可供阅读(2)的操作。
The associated file is available for write(2) operations.相关文件可供阅读(2)的操作。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:等待检测事件;
参数:
epfd:epoll实例;
events:事件集合(存储的是,准备就绪的文件描述符和对应事件集合);
maxevents:表示最大值。
timeout:超时时间;ms级;
返回值:
成功返回准备就绪的文件描述符的个数;失败返回-1;超时返回0;
#include <stdio.h>#include <string.h>#include <stdlib.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h>#include <arpa/inet.h>#include <sys/epoll.h>int client(int connfd){int ret;char buf[256];memset(buf, 0, sizeof(buf));ret = read(connfd, buf, sizeof(buf));if (ret == -1) {perror("server->read");return -1;} else if (ret == 0) {return -1;}printf("buf : %s\n", buf);ret = write(connfd, buf, sizeof(buf));if (ret == -1) {perror("server->write");return -1;}return 0;}int main(int argc, char *argv[]){int listenfd;int ret;socklen_t addrlen;int connfd;pid_t pid;char buf[256];struct sockaddr_in srvaddr;struct sockaddr_in cltaddr;/* 1. 创建服务器(创建一socket套接字);socket */listenfd = socket(AF_INET, SOCK_STREAM, 0);if (listenfd == -1) {perror("server->socket");return -1;}printf("create listenfd = %d success\n", listenfd);int opt = 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));/* 2. 设置服务器的IP地址和端口号(将socket和服务器的IP地址和端口号进行绑定);bind */memset(&srvaddr, 0, sizeof(struct sockaddr_in));srvaddr.sin_family = AF_INET;srvaddr.sin_port = htons(9999);srvaddr.sin_addr.s_addr = inet_addr("192.168.2.100");ret = bind(listenfd, (const struct sockaddr *)&srvaddr, sizeof(srvaddr));printf("port : %d\n", ntohs(srvaddr.sin_port));if (ret == -1) {perror("server->bind");return -1;}printf("bind success !\n");/* 3. 启动监听(启动服务器); listen */ret = listen(listenfd, 1024);if (ret == -1) {perror("server->listen");return -1;}printf("listen success !\n");/* 创建集合;*/int epfd;int i;int fd;epfd = epoll_create(1024);if (epfd == -1) {perror("epoll_create");return -1;}printf("epfd = %d\n", epfd);/* 添加所需要处理的事件和文件描述符 */struct epoll_event event;event.events = EPOLLIN;event.data.fd = listenfd;ret = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &event);if (ret == -1) {perror("epoll_ctl->EPOLL_CTL_ADD");return -1;}struct epoll_event events[1024];while(1) {/* 检测集合当中是否有继续的文件描述符 */ret = epoll_wait(epfd, events, 1024, 5000);if (ret == -1) {perror("poll");return -1;} else if (ret == 0) {printf("timeout\n");continue;}/* 轮循 */for (i = 0; i < ret; i++) {/* 判断是什么事件 */if (EPOLLIN == events[i].events) {fd = events[i].data.fd;/* 如果是监听套接字listenfd,则建立连接 */if (fd == listenfd) {memset(&cltaddr, 0, sizeof(cltaddr));addrlen = sizeof(socklen_t);connfd = accept(listenfd, (struct sockaddr *)&cltaddr, &addrlen);if (connfd == -1) {perror("accept");return -1;}printf("connfd = %d\n", connfd);event.events = EPOLLIN;event.data.fd = connfd;ret = epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &event);if (ret == -1) {perror("epoll_ctl->EPOLL_CTL_ADD");return -1;}} else {ret = client(fd);if (ret == -1) {ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &event);if (ret == -1) {perror("epoll_ctl->EPOLL_CTL_DEL");return -1;}close(fd);}}}}}close(epfd);close(listenfd);return 0;}
- Linux网络编程——I/O多路复用
- Linux 网络编程中的I/O多路复用
- 《网络编程》I/O 多路复用
- Linux网络编程---I/O多路复用 之 select
- Linux网络编程---I/O多路复用之epoll
- 嵌入式linux网络编程之I/O多路复用select
- [Linux C编程]I/O多路复用
- 使用多路复用套接字I/O提升性能之——ForkingMixIn 《Python网络编程攻略》
- Linux系统文件I/O编程(三)---I/O多路复用
- Linux系统文件I/O编程(三)---I/O多路复用
- Linux I/O多路复用
- linux:多路复用I/O
- Linux I/O多路复用
- Linux I/O 多路复用
- Linux I/O多路复用
- Linux I/O多路复用
- Linux系统编程——I/O多路复用select、poll、epoll的区别使用
- Linux系统编程——I/O多路复用select、poll、epoll的区别使用
- html5可缩放时钟
- C++模拟ATM
- Linux使用笔记
- 1002. A+B for Polynomials (25)
- openjudge 6256 带通配符的字符串匹配(动态规划)
- Linux网络编程——I/O多路复用
- 17.8.27 校内赛 解题报告【卢卡斯定理】【dfs+hash】【线段树】
- 小白文科生眼中的Linux系统
- Spring 事务详解
- 51Nod 迷宫游戏
- 匈牙利模板--poj 3041最小匹配
- 【asp.net】小结
- Go 语言,开源服务端代码自动生成 框架
- Anaconda使用总结