写点什么

epoll 服务器解析

发布于: 2020 年 10 月 22 日
epoll服务器解析

准备工作:

如果一开始没有很好的理解操作系统的话可以先看博客

https://segmentfault.com/a/1190000003063859



epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。



优点:没有文件描述符的限制

epoll的API

epoll_create() 创建出epfd

epoll_ctl(epfd,op,fd,struct epoll_event *event)这个函数,向epoll添加读事件,写事件

epoll_wait(epfd,events,maxevents,timeout) event是系统告知 哪些文件描述符会产生影响,

maxevents是每次返回返回多少事件;timeout定义一个超时器,超时之后再来一轮 。



默认的epoll是水平触发



这个就是 epoll_data数据结构体

typedef union epoll_data{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
struct epoll_event{
uint32_t events;
epoll_data_t data;
}






socket_fd和accept_fd是典型的文件描述符。文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。



服务端实现代码:

#include <iostream>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#define PORT 8111
#define MESSAGE_LEN 1024
#define MAX_EVENTS 20
#define TIMEOUT 500
using std::cout;
using std::endl;
int main(int arc, char *argv[]) {
int ret = -1;
int socket_fd , accept_fd;
int on = 1;
int backlog = 10;
int flags = 1;
char in_buff[MESSAGE_LEN] = {0,};
struct sockaddr_in localaddr,remoteaddr;
struct epoll_event ev,events[MAX_EVENTS];
int epoll_fd;
int event_number;
//1 创建一个socket
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd == -1) {
cout << "Failed to create socket!" << endl;
exit(-1);
}
flags = fcntl(socket_fd,F_GETFL,0);//设置为非阻塞
fcntl(socket_fd,F_SETFL,flags| O_NONBLOCK);
//2 SO_REUSEADDR 允许套接口和一个已在使用中的地址捆绑
ret = setsockopt(socket_fd,
SOL_SOCKET,
SO_REUSEADDR,
&flags,
sizeof(flags));
if (ret == -1) {
cout << "Failed to set socket options" << endl;
}
// 3 选择协议端口
localaddr.sin_family = AF_INET;
localaddr.sin_port = PORT;
localaddr.sin_addr.s_addr = INADDR_ANY;
// 绑定
ret = bind(socket_fd, (struct sockaddr *) &localaddr, sizeof(struct sockaddr));
if (ret == -1) {
cout << "Failed to bind addr!" << endl;
exit(-1);
}
// 侦听
ret = listen(socket_fd, backlog);
if (ret == -1) {
cout << " Failed to listen socket!" << endl;
exit(-1);
}
//4 使用epoll添加事件
//一个新的事件发生了,是EPOLLIN写入操作;知道data.fd是哪个文件发生,如果新的连接来了就要做创建连接准备
epoll_fd = epoll_create(256);
ev.events = EPOLLIN;
ev.data.fd = socket_fd;
// 添加侦听的描述符
epoll_ctl(epoll_fd,EPOLL_CTL_ADD,socket_fd,&ev);
for (;;) {
//有多少个文件描述符发生events(事件了)
event_number = epoll_wait(epoll_fd,events,MAX_EVENTS,TIMEOUT);
for (int i = 0; i < event_number; ++i) {
if (events[i].data.fd == socket_fd){//说明是一个侦听的socket,需要创建新的连接
socklen_t addr_len = sizeof(struct sockaddr_in);
accept_fd = accept(socket_fd,
(struct sockaddr *) &remoteaddr,
&addr_len);
// 设置为 非阻塞
flags = fcntl(accept_fd,F_GETFL,0);
fcntl(accept_fd,F_SETFL,flags| O_NONBLOCK);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = accept_fd;
epoll_ctl(epoll_fd,EPOLL_CTL_ADD,accept_fd,&ev);
}else if (events[i].events & EPOLLIN){
do {
memset(in_buff,0,MESSAGE_LEN);
//接受网络数据并打印
ret = recv(events[i].data.fd, (void *) in_buff, MESSAGE_LEN, 0);
if (ret == 0)//没有发数据和我们断开连接了
{
close(events[i].data.fd);
break;
}
if (ret == MESSAGE_LEN){//缓冲区满了
cout<< "maybe have data..."<<endl;
}
}while (ret<-1 && errno == EINTR);
if (ret <0){
switch (errno) {
case EAGAIN: //说明暂时已经没有数据了,要等通知
break;
case EINTR: //被终断了,再来一次
printf("recv EINTR... \n");
ret = recv(events[i].data.fd, &in_buff, MESSAGE_LEN, 0);
break;
default:
printf("the client is closed, fd:%d\n", events[i].data.fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, &ev);
close(events[i].data.fd);
;
}
}
cout << "rev message:" << in_buff << endl;
send(events[i].data.fd, (void *) in_buff, MESSAGE_LEN, 0);
}
}
}
// 把socket关闭
cout<<"quit server...\n"<<endl;
close(socket_fd);
return 0;
}

这是一个服务端代码 通过这种简易的方式



里面有一个设置非阻塞的过程:

flags = fcntl(socket_fd,F_GETFL,0);//设置为非阻塞
fcntl(socket_fd,F_SETFL,flags| O_NONBLOCK);



正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。



发布于: 2020 年 10 月 22 日阅读数: 36
用户头像

一个孤独的撰写者 2020.07.30 加入

主攻云计算、云安全,c++、python、java均有涉猎

评论

发布
暂无评论
epoll服务器解析