其他分享
首页 > 其他分享> > 多路IO

多路IO

作者:互联网

1.高并发服务器实现方法

1.阻塞等待:进程开启线程去服务客户,每个线程服务一个客户,阻塞等待客户发送消息——消耗资源
2.非阻塞忙轮询:进程循环询问客户,问它上面是否有消息,有消息就进行处理。——消耗cpu
3.多路IO转接(多路IO复用): 内核监听多个文件描述符的属性(读写缓冲区)变化。如果某个文件描述符的读缓冲区变化了,这个时候就是可以读了,将这个事件告知应用层
三种多路IO

2.select函数

#include <sys/select.h>
 /* According to earlier standards */
#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);
功能: 监听多个文件描述符的属性变化(读,写,异常)
在集合中进行增删改查等操作:
       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);
参数:
    nfds  : 最大文件描述符+1。监听0~nfds的文件描述符
    readfds : 需要监听的读的文件描述符存放集合。事件:数据到达了读缓冲区,需要读出来了,触发读事件
    writefds :需要监听的写的文件描述符存放集合。事件:写缓存区满了,当写缓冲区有空余时,就触发可写事件。常设为NULL
    exceptfds : 需要监听的异常的文件描述符存放集合。常设为NULL
    timeout: 多长时间监听一次   固定的时间    NULL:永久监听
        struct timeval {
               long    tv_sec;         /* seconds */ 秒
               long    tv_usec;        /* microseconds */微妙
           };

返回值: 返回的是变化的文件描述符的个数
注意: 变化的文件描述符会存在监听的集合(readfds、writefds和exceptfds)中,未变化的文件描述符会从集合中删除

示例代码:

#include <stdio.h>
#include <sys/select.h>
#include <sys/types.h>
#include <unistd.h>
#include "wrap.h"
#include <sys/time.h>
#define PORT 8888
int main(int argc, char *argv[])
{
	//创建套接字,绑定
	int lfd = tcp4bind(PORT,NULL);
	//监听
	Listen(lfd,128);
	int maxfd = lfd;//最大的文件描述符
	fd_set oldset,rset;
	FD_ZERO(&oldset);
	FD_ZERO(&rset);
	//将lfd添加到oldset集合中
	FD_SET(lfd,&oldset);
	while(1)
	{	
		rset = oldset;//将oldset赋值给需要监听的集合rset
		
		int n = select(maxfd+1,&rset,NULL,NULL,NULL);
		if(n < 0)
		{
			perror("");
			break;
		}
		else if(n == 0)
		{
			continue;//如果没有变化,重新监听
		}
		else//监听到了文件描述符的变化
		{
			//lfd变化 代表有新的连接到来
			if( FD_ISSET(lfd,&rset))  // 变化的文件描述符都放在了rset集合中,如果lfd在rset中,就说明lfd变化了
			{
				struct sockaddr_in cliaddr;
				socklen_t len =sizeof(cliaddr);
				char ip[16]="";
				//提取新的连接
				int cfd = Accept(lfd,(struct sockaddr*)&cliaddr,&len);
				printf("new client ip=%s port=%d\n",inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),
						ntohs(cliaddr.sin_port));
				//将cfd添加至oldset集合中,以下次监听
				FD_SET(cfd,&oldset);
				//更新maxfd
				if(cfd > maxfd)
					maxfd = cfd;
				//如果只有lfd变化,直接continue
				if(--n == 0)
					continue;

			}


			//cfd  遍历lfd之后的文件描述符是否在rset集合中,如果在则cfd变化
			for(int i = lfd+1;i<=maxfd;i++) // 假设现在 4-1023个文件描述符需要监听,但是5-1000这些文件描述符关闭了
                                                        // 本代码对于这种情况,还是遍历了4-1023
			{
				//如果i文件描述符在rset集合中
				if(FD_ISSET(i,&rset)) 
				{
					char buf[1500]=""; // 因为网络中的最大传输单元为1500
					int ret = Read(i,buf,sizeof(buf));
					if(ret < 0)//出错,将cfd关闭,从oldset中删除cfd
					{
						perror("");
						close(i);
						FD_CLR(i,&oldset);
					}
					else if(ret == 0) // 客户关闭了连接
					{
						printf("client close\n");
						close(i);
						FD_CLR(i,&oldset);
						
					}
					else
					{
						printf("%s\n",buf);
						Write(i,buf,ret);
					
					}
				
				}
				
			
			}
			
		
		}

		
	
	}

	return 0;
}

select优点: 跨平台
select缺点:

假设现在 4-1023个文件描述符需要监听,但是5-1000这些文件描述符关闭了?——本代码修改后,可以解决。解决如下:

//进阶版select,通过数组防止遍历1024个描述符
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>

#include "wrap.h"

#define SERV_PORT 8888

int main(int argc, char *argv[])
{
    int i, j, n, maxi;

    int nready, client[FD_SETSIZE];                 /* 自定义数组client, 防止遍历1024个文件描述符  FD_SETSIZE默认为1024 */
    int maxfd, listenfd, connfd, sockfd;
    char buf[BUFSIZ], str[INET_ADDRSTRLEN];         /* #define INET_ADDRSTRLEN 16 */
    struct sockaddr_in clie_addr, serv_addr;
    socklen_t clie_addr_len;
    fd_set rset, allset;                            /* rset 读事件文件描述符集合 allset用来暂存 */

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    //端口复用
    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));


    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family= AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port= htons(SERV_PORT);


    Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    Listen(listenfd, 128);

    maxfd = listenfd;                                           /* 起初 listenfd 即为最大文件描述符 */

    maxi = -1;                                                  /* 将来用作client[]的下标, 初始值指向0个元素之前下标位置 */
    for (i = 0; i < FD_SETSIZE; i++)
        client[i] = -1;                                         /* 用-1初始化client[] */

    FD_ZERO(&allset);
    FD_SET(listenfd, &allset);                                  /* 构造select监控文件描述符集 */

    while (1) {   
        rset = allset;                                          /* 每次循环时都从新设置select监控信号集 */

        nready = select(maxfd+1, &rset, NULL, NULL, NULL);      //2  1--lfd  1--connfd
        if (nready < 0)
            perr_exit("select error");

        if (FD_ISSET(listenfd, &rset)) {                        /* 说明有新的客户端链接请求 */

            clie_addr_len = sizeof(clie_addr);
            connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);       /* Accept 不会阻塞 */
            printf("received from %s at PORT %d\n",
                    inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),
                    ntohs(clie_addr.sin_port));

            for (i = 0; i < FD_SETSIZE; i++)
                if (client[i] < 0) {                            /* 找client[]中没有使用的位置 */
                    client[i] = connfd;                         /* 保存accept返回的文件描述符到client[]里 */
                    break;
                }

            if (i == FD_SETSIZE) {                              /* 达到select能监控的文件个数上限 1024 */
                fputs("too many clients\n", stderr);
                exit(1);
            }

            FD_SET(connfd, &allset);                            /* 向监控文件描述符集合allset添加新的文件描述符connfd */

            if (connfd > maxfd)
                maxfd = connfd;                                 /* select第一个参数需要 */

            if (i > maxi)
                maxi = i;                                       /* 保证maxi存的总是client[]最后一个元素下标 */

            if (--nready == 0)
                continue;
        } 

        for (i = 0; i <= maxi; i++) {                               /* 检测哪个clients 有数据就绪 */

            if ((sockfd = client[i]) < 0)
                continue;//数组内的文件描述符如果被释放有可能变成-1
            if (FD_ISSET(sockfd, &rset)) {

                if ((n = Read(sockfd, buf, sizeof(buf))) == 0) {    /* 当client关闭链接时,服务器端也关闭对应链接 */
                    Close(sockfd);
                    FD_CLR(sockfd, &allset);                        /* 解除select对此文件描述符的监控 */
                    client[i] = -1;
                } else if (n > 0) {
                    for (j = 0; j < n; j++)
                        buf[j] = toupper(buf[j]);
                    Write(sockfd, buf, n);
                    Write(STDOUT_FILENO, buf, n);
                }
                if (--nready == 0)
                    break;                                          /* 跳出for, 但还在while中 */
            }
        }
    }

    Close(listenfd);

    return 0;
}

虽然本代码还是需要遍历,但是效率提高了一些。

3.poll

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能: 监听多个文件描述符的属性变化
参数:
    fds : 监听的数组的首元素地址
    nfds: 数组有效元素的最大下标+1
    timeout : 超时时间 -1是永久监听 >=0 限时等待

数组元素:
            struct pollfd {
               int   fd;         /* file descriptor */ 需要监听的文件描述符
               short events;     /* requested events */需要监听文件描述符什么事件  EPOLLIN 读事件   EPOLLOUT写事件,其他事件可以通过man poll查看
               short revents;    /* returned events */ 返回监听到的事件    EPOLLIN 读事件   EPOLLOUT写事
           };

poll相对与sellect的优缺点
优点:

poll相对于select没有最大1024文件描述符限制,大小限制。但poll其实也是有最大文件描述符限制,如下:

ulimit -a                  # 查看最多打开多少个文件(open files)
ulimit -n 2048             # 将最多打开文件个数修改成2048
cat /proc/sys/fs/file-max  # ulimit -n最大可修改的量

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include<netinet/in.h>
#include<poll.h>
#include<errno.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>

#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 8000
#define OPEN_MAX 1024


int main(int argc, char *argv[])
{
	int i, j, maxi, listenfd, connfd, sockfd;
	int nready;   // 接收poll返回值,记录满足监听事件的fd个数
	ssize_t n;

	char buf[MAXLINE],str[INET_ADDRSTRLEN];
	socklen_t clilen;

	struct pollfd client[OPEN_MAX] ;//定义pol1的数组
	struct sockaddr_in cliaddr,servaddr;

	listenfd = Socket(AF_INET, SOCK_STREAM, 0);
	int opt = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,&opt, sizeof(opt));//设置端口复用

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);

	Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
	Listen(listenfd,128);

	client[0].fd = listenfd;   /*要监听的第一一个文件描述符存入client[0]*/
	client[0].events = POLLIN; /* listenfd监听普通读事件*/
	for (i = 1; i < OPEN_MAX; i++)
		client[i].fd = -1;				// 用-1初始化client[]里剩下元素 0也是文件描述符,
	maxi = 0;                            /* client[]数组有效元素中最大元素下标*/




	for (;;) { // 等价与while循环 
		nready = poll(client, maxi + 1, -1); /* 阻塞监听是否有客户端链接请求*/
		if (client[0].revents & POLLIN) {      // listenfd有 读事件就绪。这里用&,而不用==
			clilen = sizeof(cliaddr);
			connfd = Accept(listenfd, (struct sockaddr *) &cliaddr, &clilen);
			printf("received from %s at PORT %d\n",
				inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
				ntohs(cliaddr.sin_port));
			for (i = 1; i < OPEN_MAX; i++)
				if (client[i].fd < 0) {
					client[i].fd = connfd;  // 找到client []中空闲的位置,存放accept返回的connfd
					break;
				}
			if (i == OPEN_MAX)  /*达到了最大客户端数*/
				perr_exit("too many clients");

			client[i].events = POLLIN;  /*设置刚刚返回的connfd, 监控读事件*/
			if (i > maxi)
				maxi = i;             /*更新client[]中最大元素下标*/
			if (--nready <= 0)
				continue;            /*没有更多就绪事件时,继续回到po11阻塞*/
		}
		for (i = 1; i <= maxi; i++) {

			if ((sockfd = client[i].fd) < 0)
				continue;				//找到第一-个大于0的

			if (client[i].revents & POLLIN) {
				if ((n = Read(sockfd, buf, MAXLINE)) < 0) {
					if (errno == ECONNRESET) { /* 收到RST标志,代表对方想要关闭连接*/
						printf("client[%d] aborted connection\n", i);
						Close(sockfd);
						client[i].fd = -1;    // poll中不监控该文件描述符,直接置为-1即可,不用像se
					}
					else
						perr_exit("read error");
				}
				else if (n == 0) {   /*说明客户端先关闭链接*/
					printf("client[%d] closed connection\n",i);
					Close(sockfd);
					client[i].fd = -1;
				}
				else {
					for (j = 0; j < n; j++)
						buf[j] = toupper(buf[j]);
					Writen(sockfd, buf, n);
				}
				if (--nready <= 0)
					break;
			}
		}
	}
				
	return 0;

}

标签:文件,多路,int,描述符,client,IO,include,监听
来源: https://www.cnblogs.com/codingbigdog/p/16255825.html