lv8 嵌入式开发-网络编程开发 16 多路复用poll函数

目录

1 多路复用的多种实现方式

2 poll 

2.1 poll 函数应用

3 epoll 函数族(效率最高)

3.1 epoll_create 创建epoll句柄

3.2 epoll_ctl epoll句柄控制接口

3.3 epoll_wait 等待 epoll 文件描述符上的 I/O 事件 

3.4 epoll 函数应用


1 多路复用的多种实现方式

lv8 嵌入式开发-网络编程开发 16 多路复用poll函数_第1张图片

2 poll 

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd {
    int d;             /* 文件描述符 */
    short events;    /* 请求的事件 */
    short revents;   /* 返回的事件 */
};

 poll() 函数是一个系统调用,用于监视一组文件描述符,等待其中的一个或多个文件描述符变为就绪状态,从而进行读写操作。它与 select() 类似,但 poll() 没有最大文件描述符数量的限制,并且更加高效。以下是参数的含义:

  • fds:指向 pollfd 结构体数组的指针,其中存放了需要监视的文件描述符和每个文件描述符所关注的事件。
  • nfds:文件描述符的数量。
  • timeout:超时时间(单位是毫秒),如果为 0 表示立即返回,如果为 -1 表示永远等待。

poll() 的返回值表示就绪的文件描述符数量,如果返回值为 0,则表示超时。如果返回值为 -1,则表示发生错误,此时可以使用 errno 查看具体的错误信息。

事件类型 events:

  •         POLLIN:有数据可读
  •         POLLPRI:有紧急数据需要读取
  •         POLLOUT: 文件可写
  •          .....

2.1 poll 函数应用

server.c

#include "net.h"
#include 

#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){  //判断服务端的fd是否有数据到来即第0个,并且文件描述符是数据可读,那么再执行把客户端接进来
				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){   //服务端不算,需要1开始
				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(j=i; j

socket.c

#include "net.h"

void Argment(int argc, char *argv[]){
	if(argc < 3){
		fprintf(stderr, "%s\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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef 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

3 epoll 函数族(效率最高)

/*创建epoll句柄*/
int epoll_create(int size);  //size参数实际上已经被弃用

/*epoll句柄的控制接口*/
int epoll_ctl(int epfd, int op, int fd, 
                         struct epoll_event *event);

/*等待 epoll 文件描述符上的 I/O 事件*/
int epoll_wait(int epfd, struct epoll_event *events, 
                         int maxevents, int timeout);

3.1 epoll_create 创建epoll句柄

int epoll_create(int size);  //size参数实际上已经被弃用

epoll_create() 函数用于创建一个 epoll 实例,并返回一个与该实例相关联的文件描述符。以下是参数的含义:

  • size:已经被弃用,原本用于指定 epoll 实例的大小,但在内核版本2.6.8之后不再使用。

epoll_create() 的返回值为一个非负整数,表示与 epoll 实例相关联的文件描述符。如果返回值为 -1,则表示创建失败,此时可以使用 errno 查看具体的错误信息。

调用成功后,可以使用返回的文件描述符进行后续的操作,如注册、修改、等待文件描述符上的事件等。

3.2 epoll_ctl epoll句柄控制接口

int epoll_ctl(int epfd, int op, int fd, 
                         struct epoll_event *event);

epoll_ctl() 函数用于向 epoll 实例中注册、修改或删除文件描述符的事件。它是 epoll 系统调用的控制接口。以下是参数的含义:

  • epfd:epoll 实例的文件描述符,通过 epoll_create() 创建。
  • op:操作类型,可以是以下几种值之一:
    • EPOLL_CTL_ADD:将文件描述符 fd 添加到 epoll 实例中,并关联一个事件结构。
    • EPOLL_CTL_MOD:修改文件描述符 fd 在 epoll 实例中关联的事件结构。
    • EPOLL_CTL_DEL:从 epoll 实例中移除文件描述符 fd
  • fd:需要注册、修改或删除的文件描述符。
  • event:指向 struct epoll_event 结构体的指针,用于指定文件描述符关心的事件类型。

epoll_ctl() 的返回值为 0 表示操作成功,-1 表示操作失败,此时可以使用 errno 查看具体的错误信息。

epoll_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 可以用来存储与该事件相关的用户数据信息。*/
};
  • EPOLLIN :表示对应的文件描述符可以读(包括对端 SOCKET 正常关闭);
  • EPOLLOUT:表示对应的文件描述符可以写;
  • EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
  • EPOLLERR:表示对应的文件描述符发生错误;
  • EPOLLHUP:表示对应的文件描述符被挂断;
  • EPOLLET :将 EPOLL 设为边缘触发(Edge Trigger)模式,这是相对于水平触发(Level Trigger)来说的。
  • EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个 socket 的话,需要再次把这个 socket 加入到 EPOLL 队列里

3.3 epoll_wait 等待 epoll 文件描述符上的 I/O 事件 

int epoll_wait(int epfd, struct epoll_event *events, 
                         int maxevents, int timeout);

epoll_wait() 函数用于等待 epoll 实例中的文件描述符上发生事件,它会一直阻塞直到至少有一个文件描述符发生就绪事件。以下是参数的含义:

  • epfd:epoll 实例的文件描述符,通过 epoll_create() 创建。
  • events:指向 struct epoll_event 结构体数组的指针,用于返回触发事件的文件描述符信息。
  • maxevents:表示 events 数组的最大大小,即最多可以返回多少个事件。
  • timeout:超时时间(单位是毫秒),如果为 0 表示立即返回,如果为 -1 表示永远等待。

epoll_wait() 的返回值表示就绪的文件描述符数量,如果返回值为 0,则表示超时。如果返回值为 -1,则表示发生错误,此时可以使用 errno 查看具体的错误信息。在成功返回时,events 数组被填充了触发事件的文件描述符信息。

3.4 epoll 函数应用

server.c

#include "net.h"
#include 

#define MAX_SOCK_FD 1024

int 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)  //参数1是无意义得
		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.h"

void Argment(int argc, char *argv[]){
	if(argc < 3){
		fprintf(stderr, "%s\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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef 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

你可能感兴趣的:(嵌入式开发,网络,linux)