IO多路复用:select机制,poll机制,epoll机制(C语言)

        大家好,我是练习编程时长两年半的个人练习生昆工第一ikun,今天我们来分享一下如何让TCP通信可以像UDP那样做到多个客户端可以同时与服务器进行通信,我们可以使用非阻塞模式和多进程多线程,但是这两种方法有其弊端,下面我们将介绍一种弊端较少的方法——IO多路复用。


 

目录

 一、select机制

1.selec的相关函数

2.select机制实例

3.select机制的缺点

二、poll机制

1.poll机制的相关函数

2.poll机制的实例 

3.poll机制的缺点

 三、epoll机制

1.epoll机制的相关函数

2.epoll机制的实例

3.epoll机制的缺点


  • 应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;
     
  • 若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间
  • 若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂;

比较好的方法是使用I/O多路复用。其基本思想是︰

  • 先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回
  • 函数返回时告诉进程那个描述符已就绪,可以进行I/0操作。

 一、select机制

1.selec的相关函数

大部分Unix/Linux都支持select函数,该函数用于探测多个文件句柄的状态变 化。
#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 机制
参数说明:
        nfds: 最大文件描述符+1
        readfds: 读事件的表
        writefds: 写事件的表
        exceptfds: 异常事件的表
        timeout:超时检测
        NULL: 一直阻塞,直到文件描述符准备好为止返回值:
成功: 准备好的文件描述符的个数
失败: -1
void FD_CLR(int fd, fd_set *set); // 把表中删除一个文件描述符
int FD_ISSET(int fd, fd_set *set); //检测文件描述符是否准备好了,准备好了返回1,否则返回0
void FD_SET(int fd, fd_set *set); // 加入到表中
void FD_ZERO(fd_set *set); // 清空表
        基本原理:首先创建一张文件描述符表(fd_set),通过使用特有的函数(select),让内核帮
助上层用户循环检测是否有可操作的文件描述符,如果有则告诉应用程序去操作。

2.select机制实例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>


int tcpser_init(int port);

int main(int argc, const char *argv[])
{  
        int sockfd = tcpser_init(11111);
        printf("wait a client.............\n");
        
        fd_set readfds, tmpfds;
        FD_ZERO(&readfds);
        FD_SET(sockfd, &readfds);
        char buf[64];
        int i;
        int maxfd = sockfd;
        tmpfds = readfds;

    while(1)
    {
    	readfds = tmpfds;
    	int ret = select(maxfd+1, &readfds, NULL, NULL, NULL);
    	if(ret == -1)
    	{
    		perror("select");
    		return -1;
    	}
    	for(i = sockfd; i < maxfd+1; i++)
    	{
    		if(FD_ISSET(i, &readfds))
    		{
    			if(i == sockfd)
    			{
    			 	int connfd = accept(sockfd, NULL, NULL);
				if(connfd < 0)
				{
				    perror("accept");
				    return -1;
				}
        			printf("%d is link\n", connfd);	
        			FD_SET(connfd, &tmpfds);
        			if(maxfd < connfd)
        			{
        				maxfd = connfd;
        			}
        			else
        			{
        			memset(buf, 0, 64);
        			ret = recv(i, buf, sizeof(buf), 0);	
				if(ret < 0)
				{
				    close(i);
				    perror("recv");
				    return -1;
				}
				else if(ret == 0) //客户端关闭了
				{
				    printf("%d is unlink\n", i);
				    FD_CLR(i, &tmpfds);
				    close(i);
				    break;	
				}
				else
				{
				printf("message:%s\n", buf);
			    	}
			    	}		
    			}
    		}
    	}
    	
    }
    close(sockfd);
    return 0;
}



int tcpser_init(int port)
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("sockfd");
        return -1;
    }

    printf("sockfd = %d\n", sockfd);

    //端口复用函数:解决端口号被系统占用的情况
    int on = 1;
    int k = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    if(k == -1)
    {
        perror("setsockopt");
        return -1;
    }


    struct sockaddr_in serveraddr,clientaddr;
    memset(&serveraddr, 0 ,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(port);
    serveraddr.sin_addr.s_addr = inet_addr("0"); //自动路由,寻找IP地址

    int len = sizeof(serveraddr);

    int ret = bind(sockfd, (struct sockaddr *)&serveraddr, len);
    if(ret == -1)
    {
        perror("bind");
        return -1;
    }


    ret = listen(sockfd, 100);
    if(ret == -1)
    {
        perror("listen");
        return -1;
    }
    return sockfd;

}

3.select机制的缺点

select缺点:
(1)内核使用轮询的方式来检查文件描述符集合中的描述符是否就绪,文件描 述符越多,消耗的时间资源越多。
(2)文件描述符集合使用的数组,有大小限制,1024
(3)每次文件描述符集合更新时,重新拷贝到内核中

二、poll机制

1.poll机制的相关函数

        poll的实现和select非常相似,只是文件描述符fd集合的方式不同, poll使用struct pollfd结构而不是select的fd_set结构,其他的都差不多。
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能:poll机制
参数:
        fds: 结构体数组的数组名
        struct pollfd
        {
                int fd; /* file descriptor */
                short events; /* requested events */
                short revents; /* returned events */
        };
        
        fd: 文件描述符
        events: 请求的事件(读、写、异常)
        revents: 对请求事件的反馈 (读、写、异常)
        POLLIN :读事件
        POLLOUT:写事件
        
        eg: struct pollfd fds[];
        fds[0].fd;
        fds[0].events;
        fds[o].revents;
        
        nfds: 准备的文件描述符的个数
        timeout: -1 阻塞,直到文件描述符准备好
        
返回值:
        成功:准备好的文件描述符的个数
        失败:-1

2.poll机制的实例 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <poll.h>

int tcpser_init(int port);

int main(int argc, const char *argv[])
{  
    int sockfd = tcpser_init(11111);
    printf("wait a client.............\n");

    struct pollfd fds[100];
    memset(&fds, 0, sizeof(fds));

    fds[0].fd = sockfd;
    fds[0].events = POLLIN;

    int m = 1;
    int n = 1;
    int i, ret;
    char buf[64] = {0};

    while(1)          //轮巡检测结构体数组;
    {
        ret = poll(fds, m, -1);
        if(ret == -1)
        {
            perror("poll");
            exit(-1);
        }

        for(i = 0; i < m; i++)
        {
            if(fds[i].events & POLLIN)
            {
                int fd = fds[i].fd;

                if(fd == sockfd)   //使用accept函数建立连接
                {
                    int connfd = accept(fd, NULL, NULL);
                    printf("%d is link\n", connfd);

                    n = connfd - sockfd;

                    fds[n].fd = connfd;
                    fds[n].events = POLLIN;

                    if(m < connfd-2)
                    {
                        m = connfd-2;    						
                    }
                }
                else
                {
                    memset(buf, 0, 64);
                    ret = recv(fd, buf, 64, 0);
                    if(ret == -1)
                    {
                        perror("recv");
                        return -1;
                    }
                    else if(ret == 0)
                    {
                        printf("%d is unlink\n", fd);

                        memset(&fds[i], 0, sizeof(fds[i]));
                    }
                    else
                    {
                        printf("%d:message:%s\n", fd, buf);
                    }
                }
            }
        }
    }

    close(sockfd);
    return 0;
}



int tcpser_init(int port)
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("sockfd");
        return -1;
    }

    printf("sockfd = %d\n", sockfd);

    //端口复用函数:解决端口号被系统占用的情况
    int on = 1;
    int k = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    if(k == -1)
    {
        perror("setsockopt");
        return -1;
    }


    struct sockaddr_in serveraddr,clientaddr;
    memset(&serveraddr, 0 ,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(port);
    serveraddr.sin_addr.s_addr = inet_addr("0"); //自动路由,寻找IP地址

    int len = sizeof(serveraddr);

    int ret = bind(sockfd, (struct sockaddr *)&serveraddr, len);
    if(ret == -1)
    {
        perror("bind");
        return -1;
    }


    ret = listen(sockfd, 100);
    if(ret == -1)
    {
        perror("listen");
        return -1;
    }
    return sockfd;
}

3.poll机制的缺点

poll机制的fds结构体数组有大小限制

 

 三、epoll机制

1.epoll机制的相关函数

(1)epoll使用红黑树来管理所有的文件描述符集合,不受大小限制,更新集合时,不需
要重新拷贝整个集合,直接更新红黑树即可
(2)利用call back来知道文件描述符是否就绪,只关心已经就绪的文件描述符,不需要遍
历所有的文件描述符。  
epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait.
#include <sys/epoll.h>
int epoll_create(int size);
功能:创建一个树
参数:
        size:树的节点
返回值:
        成功:文件描述符epfd
        失败:-1
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:控制epoll
参数:
        epfd: epoll_create的返回值
        op:
                EPOLL_CTL_ADD: 添加文件描述符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 events */
                epoll_data_t data; /* User data variable */
                };
                
                events:
                EPOLLIN:读事件
                EPOLLOUT:写事件
                eg:
                struct epoll_event ev;
                ev.events; //事件
                 ev.data.fd; //文件描述符
返回值:
        成功:0
        失败:-1
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:等待文件描述符就绪
参数:
        epfd: epoll_create的返回值
        events:
        struct epoll_event evs[ SIZE ]; //结构体数组
        maxevents: [ SIZE ]
        timeout:-1 阻塞,等待文件描述符就绪
返回值:
        成功:准备好的文件描述符失败:-1

2.epoll机制的实例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <time.h>
#include <sys/epoll.h>

int tcpser_init(int port);
int main(int argc, const char *argv[])
{
	 int sockfd = tcpser_init(11111);
        printf("wait a client.............\n");
        
	//epoll机制,实现IO多路复用的并发服务器	
		
	int epfd = epoll_create(128);
	if(epfd == -1)
	{
		perror("epoll_create");
		return -1;
	}

	struct epoll_event ev, evs[128];

	ev.events = EPOLLIN;
	ev.data.fd = sockfd;

	epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);

	char buf[64] = {0};

	int m, i, ret;

	while(1)
	{
		m = epoll_wait(epfd, evs, 128, -1);
		if(m == -1)
		{
			perror("epoll_wait");
			return -1;
		}
	
		for(i=0 ; i<m; i++)
		{
			if(evs[i].events & EPOLLIN)
			{
				int fd = evs[i].data.fd;
			
				if(fd == sockfd)
				{
					//建立连接,用accept函数
					int connfd = accept(fd, NULL, NULL);
					printf("%d is link\n", connfd);
					
					
					ev.events = EPOLLIN;
					ev.data.fd = connfd;

					epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
				}
				else{
					memset(buf, 0 ,sizeof(buf));
				
					ret = recv(fd, buf, sizeof(buf), 0);
					if(ret == -1)
					{
						perror("recv");
						return -1;
					}
					else if(ret == 0)
					{
						printf("%d is unlink\n", fd);

						epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev);

						close(fd);
					}
					else{
					
						printf("%d:message=%s\n", fd, buf);
					}
				
				}
			
			}

		}
	}


	close(sockfd);

	return 0;
}

int tcpser_init(int port)
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("sockfd");
        return -1;
    }

    printf("sockfd = %d\n", sockfd);

    //端口复用函数:解决端口号被系统占用的情况
    int on = 1;
    int k = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    if(k == -1)
    {
        perror("setsockopt");
        return -1;
    }


    struct sockaddr_in serveraddr,clientaddr;
    memset(&serveraddr, 0 ,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(port);
    serveraddr.sin_addr.s_addr = inet_addr("0"); //自动路由,寻找IP地址

    int len = sizeof(serveraddr);

    int ret = bind(sockfd, (struct sockaddr *)&serveraddr, len);
    if(ret == -1)
    {
        perror("bind");
        return -1;
    }


    ret = listen(sockfd, 100);
    if(ret == -1)
    {
        perror("listen");
        return -1;
    }
    return sockfd;

}

3.epoll机制的缺点

暂无

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值