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)的操作。

        EPOLLOUT
              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;}