面试的时候,老是考阻塞IO和非阻塞IO的区别,其实在写代码的层面上,都是系统调用。这段代码摘自,非常的优雅,用了c++新特性,我倒是觉着,这些都是文档,到时候调他的包就好了,不懂再查。
上代码
接口
epoller.h
#pragma once
#include <vector>
#include <sys/epoll.h>
#include <fcntl.h> // fcntl()
#include <unistd.h> // close()
#include <assert.h> // close()
#include <vector>
#include <errno.h>
class Epoller {
public:
explicit Epoller(int maxEvent = 1024);
~Epoller();
bool AddFd(int fd, uint32_t events);
bool ModFd(int fd, uint32_t events);
bool DelFd(int fd);
int Wait(int timeoutMs = -1);
int GetEventFd(size_t i) const;
uint32_t GetEvents(size_t i) const;
private:
int epollFd_;
std::vector<struct epoll_event> events_;
};
epoller.cpp
#include "epoller.h"
Epoller::Epoller(int maxEvent):epollFd_(epoll_create(512)), events_(maxEvent){
assert(epollFd_ >= 0 && events_.size() > 0);
}
Epoller::~Epoller() {
close(epollFd_);
}
bool Epoller::AddFd(int fd, uint32_t events) {
if(fd < 0) return false;
epoll_event ev = {0};
ev.data.fd = fd;
ev.events = events;
return 0 == epoll_ctl(epollFd_, EPOLL_CTL_ADD, fd, &ev);
}
bool Epoller::ModFd(int fd, uint32_t events) {
if(fd < 0) return false;
epoll_event ev = {0};
ev.data.fd = fd;
ev.events = events;
return 0 == epoll_ctl(epollFd_, EPOLL_CTL_MOD, fd, &ev);
}
bool Epoller::DelFd(int fd) {
if(fd < 0) return false;
epoll_event ev = {0};
return 0 == epoll_ctl(epollFd_, EPOLL_CTL_DEL, fd, &ev);
}
int Epoller::Wait(int timeoutMs) {
return epoll_wait(epollFd_, &events_[0], static_cast<int>(events_.size()), timeoutMs);
}
int Epoller::GetEventFd(size_t i) const {
assert(i < events_.size() && i >= 0);
return events_[i].data.fd;
}
uint32_t Epoller::GetEvents(size_t i) const {
assert(i < events_.size() && i >= 0);
return events_[i].events;
}
这些接口就是在维护一个接口,将你需要监听的句柄存放在vector里,没有事件,epoller.wait会等待,有事件,epoller.wait返回。
例子
这里maim.cpp 举了一个例子。
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include "epoller.h"
using namespace std;
#define REDIS_DEFAULT_PORT 6379
class RedisServer {
public:
RedisServer() : epoll_(1024), listen_fd_(-1) {
listen_fd_ = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd_ == -1) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // 只监听本地
serv_addr.sin_port = htons(REDIS_DEFAULT_PORT);
if (bind(listen_fd_, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
perror("bind failed");
exit(EXIT_FAILURE);
}
if (listen(listen_fd_, SOMAXCONN) == -1) {
perror("listen failed");
exit(EXIT_FAILURE);
}
epoll_.AddFd(listen_fd_, EPOLLIN);
fcntl(listen_fd_, F_SETFL, fcntl(listen_fd_, F_GETFD, 0) | O_NONBLOCK);
}
~RedisServer() {
close(listen_fd_);
}
void Run() {
while (true) {
int event_count = epoll_.Wait();
for (int i = 0; i < event_count; ++i) {
int fd = epoll_.GetEventFd(i);
uint32_t events = epoll_.GetEvents(i);
if (fd == listen_fd_) {
AcceptConnection();
}else if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
CloseConnection(fd);
}
else if (events & EPOLLIN) {
HandleRequest(fd);
}
}
}
}
private:
void CloseConnection(int fd) {
cout << "Client[" << fd << "] disconnected!" << endl;
close(fd);
epoll_.DelFd(fd);
}
void AcceptConnection() {
struct sockaddr_in cli_addr;
socklen_t cli_addr_len = sizeof(cli_addr);
int conn_fd = accept(listen_fd_, (struct sockaddr*)&cli_addr, &cli_addr_len);
if (conn_fd == -1) {
perror("accept failed");
return;
}
std::cout << "Accepted new connection" << std::endl;
epoll_.AddFd(conn_fd, EPOLLIN);
}
void HandleRequest(int fd) {
char buffer[1024];
ssize_t bytes_read = recv(fd, buffer, sizeof(buffer), 0);
if (bytes_read == -1) {
perror("recv failed");
return;
}
std::cout << "Received request: " << std::string(buffer, bytes_read) << std::endl;
const char* response = "+OK\r\n";
ssize_t bytes_written = send(fd, response, strlen(response), 0);
if (bytes_written == -1) {
perror("send failed");
return;
}
}
Epoller epoll_;
int listen_fd_;
};
int main(){
RedisServer server;
server.Run();
return 0;
}
这里是建立了一个socket,监听本地6379端口,运行之后,可以在终端中输入,redis-cli INFO 、redis-cli PING发送数据,这边会打引出来。
这个模块,只需要写while循环里写对应的函数就可以了,比方说接收到连接的时候添加到vector里去,接收到请求的时候调用对应的处理函数。