概述
Linux网络编程之epoll模型
文章目录
- Linux网络编程之epoll模型
- 前言
- 一、epoll模型相关的函数和结构体介绍
- 二、代码编写思路
- 三.完整代码
- 四.测试结果
前言
提示:这里可以添加本文要记录的大概内容:
I/O 多路复用使得程序能同时监听多个文件描述符,能够提高程序的性能, Linux 下实现 I/O 多路复用的系统调用主要有 select、 poll 和 epoll。其中,select有最大文件描述符个数1024,虽然可以通过更改系统配置文件进行更改,但是一般不建议修改系统配置文件,因为这样会影响所有的用户。而且使用select模型需要反复从用户区转到内核区,当fd很大时开销也会很大。poll原理和select差不多,其优点是突破了1024文件描述符的限制,但是编写的代码不能移值,因此,linux上很少使用。epoll克服了上述两种模型的缺点,因此在linux网络编程中应用较为广泛。
提示:以下是本篇文章正文内容,下面案例可供参考
一、epoll模型相关的函数和结构体介绍
1.epoll_create
函数原型为
int epoll_create(int size);
函数说明: 创建一个树根
参数说明:
size: 最大节点数, 此参数在linux 2.6.8已被忽略, 但必须传递一个大于0的数.
返回值:
成功: 返回一个大于0的文件描述符, 代表整个树的树根.
失败: 返 回-1, 并设置errno值.
2.两个重要的结构体(联合体)
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 */
};
epoll_event.events常用的有:
EPOLLIN: 读事件
EPOLLOUT: 写事件
EPOLLERR: 错误事件
EPOLLET: 边缘触发模式
epoll_event.data.fd: 要监控的事件对应的文件描述符
3.epoll_wait
函数原型为:
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);``
函数说明:等待内核返回事件发生
参数说明:
epfd: epoll树根
events: 传出参数, 其实是一个事件结构体数组
maxevents: 数组大小
timeout:
-1: 表示永久阻塞
0: 立即返回
>0: 表示超时等待事件
返回值:
成功: 返回发生事件的个数
失败: 若timeout=0, 没有事件发生则返回; 返回-1, 设置errno值,
epoll_wait的events是一个传出参数, 调用epoll_ctl传递给内核什么值, 当epoll_wait返回的时候, 内核就传回什么值,不会对struct event的结构体变量的值做任何修改.
4.epoll_ctl
函数原型如下:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
函数说明: 将要监听的节点在epoll树上添加, 删除和修改
参数说明:
epfd: epoll树根
op:
EPOLL_CTL_ADD: 添加事件节点到树上
EPOLL_CTL_DEL: 从树上删除事件节点
EPOLL_CTL_MOD: 修改树上对应的事件节点
fd: 事件节点对应的文件描述符
二、代码编写思路
1.创建socket,得到监听文件描述符lfd–socket()
//创建监听socket
// int socket(int domain, int type, int protocol);
int lfd = socket(AF_INET, SOCK_STREAM, 0);
2.设置端口复用–setsockopt()
//设置端口复用
// int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen)
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
3.绑定—bind()
//绑定端口号
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(12345);
seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(lfd, (struct sockaddr *)&seraddr, sizeof(struct sockaddr));
4.开始监听
//开始监听
listen(lfd, 128);
5.创建epoll实例
//创建epoll实例
int epfd = epoll_create(100);
6.创建一个epoll_event,并关联上监听文件描述符
//创建一个epoll_event,并关联上监听文件描述符
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
7 将监听文件描述符对应的epoll_event上epoll实例树
//将监听文件描述符对应的epoll_event上epoll实例树
// int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll_ctl(epfd, EPOLL_CTL_ADD,lfd, &epev);
8.创建一个epoll_event数组,用于关联用户socket
struct epoll_event epevs[1024];
9.while循环
9.1循环等待事件发生
//循环等待事件的发生
int ret = epoll_wait(epfd, epevs, sizeof(epevs), -1);
9.2错误处理
if (ret < 0)
{
perror("wait error:");
if (errno == EINTR)
continue;
exit(-1);
}
9.3遍历所发生的epoll事件
for (int i = 0; i < ret; i++)
9.3.1处理监听描述符事件
if (epevs[i].data.fd == lfd) //先看看监听文件描述符有没有发生变化
{
int cfd = accept(lfd, NULL, NULL);
epev.events = EPOLLIN;
epev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD,cfd, &epev);
continue;
}
9.3.2处理客户端发来的消息
else
{
char buf[1024] = {0}; //创建一个接收数据的数组
int n = read(epevs[i].data.fd, buf, sizeof(buf));
if (n < 0)
{
perror("read error:");
if (errno == EINTR)
{
continue;
}
}
else if (n == 0) //客户端下线
{
printf("有客户端下线!n");
epoll_ctl(epfd, EPOLL_CTL_DEL,epevs[i].data.fd, NULL);
}
else
{
printf("接收到数据:%sn", buf);
for (int j = 0; j < n; j++)
{
buf[j] = toupper(buf[j]);
}
//将数据反馈给客户端
int nr = write(epevs[i].data.fd, buf, sizeof(buf));
if(nr<0)
{
perror("write error:");
}
}
}
三.完整代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>
#include<ctype.h>
int main()
{
//创建监听socket
// int socket(int domain, int type, int protocol);
int lfd = socket(AF_INET, SOCK_STREAM, 0);
//设置端口复用
// int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen)
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
//绑定端口号
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(12345);
seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(lfd, (struct sockaddr *)&seraddr, sizeof(struct sockaddr));
//开始监听
listen(lfd, 128);
//创建epoll实例
int epfd = epoll_create(100);
//创建一个epoll_event,并关联上监听文件描述符
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
//将监听文件描述符对应的epoll_event上epoll实例树
// int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll_ctl(epfd, EPOLL_CTL_ADD,lfd, &epev);
struct epoll_event epevs[1024];
while (1)
{
//循环等待事件的发生
int ret = epoll_wait(epfd, epevs, sizeof(epevs), -1);
if (ret < 0)
{
perror("wait error:");
if (errno == EINTR)
continue;
exit(-1);
}
for (int i = 0; i < ret; i++)
{
if (epevs[i].data.fd == lfd) //先看看监听文件描述符有没有发生变化
{
int cfd = accept(lfd, NULL, NULL);
epev.events = EPOLLIN;
epev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD,cfd, &epev);
continue;
}
else
{
char buf[1024] = {0}; //创建一个接收数据的数组
int n = read(epevs[i].data.fd, buf, sizeof(buf));
if (n < 0)
{
perror("read error:");
if (errno == EINTR)
{
continue;
}
}
else if (n == 0) //客户端下线
{
printf("有客户端下线!n");
epoll_ctl(epfd, EPOLL_CTL_DEL,epevs[i].data.fd, NULL);
}
else
{
printf("接收到数据:%sn", buf);
for (int j = 0; j < n; j++)
{
buf[j] = toupper(buf[j]);
}
int nr = write(epevs[i].data.fd, buf, sizeof(buf));
if(nr<0)
{
perror("write error:");
}
}
}
}
}
close(epfd);
return 0;
}
四.测试结果
服务器:
客户端:
最后
以上就是高高哈密瓜为你收集整理的Linux网络编程之epoll模型Linux网络编程之epoll模型前言一、epoll模型相关的函数和结构体介绍二、代码编写思路三.完整代码四.测试结果的全部内容,希望文章能够帮你解决Linux网络编程之epoll模型Linux网络编程之epoll模型前言一、epoll模型相关的函数和结构体介绍二、代码编写思路三.完整代码四.测试结果所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复