说来惭愧,编程也有一年半的时间了,今天在实现epoll这个多路转接的代码时,写了个bug,个人认为还是很不好发现的一个错误。
首先,在这里先给大家说说多路转接。所谓的多路转接就是在IO的时候提高了效率,原来我们在写套接字编程的时候或是管道等等,都需要等对方,而现在虽然也要等,但是单位时间内的效率是很高的。像管道,他只能等一个文件描述符,而多路转接可以等多个文件描述符。所以,这里明显提高了效率。
大概知道了什么是多路转接以后,在整体给大家说说思路,其实很简单,就是在客户端不断地链接服务端的时候,我们只需要这些套接字依次放到多路转接的等待时间就绪的数组中就行。这就是思路。很简单,但是实现起来的时候还是有些难度的。
先跟大家分享一下我在这里犯的几个错误吧!希望大家不要犯这种错误!
首先就是:为了提高代码的可读性我们一般都会把每个模块的代码强解耦,这样不仅是可读性高,而且在出现错误的时候很好改,只需要改某一个错误的模块就行。而这样的行为就会让我们不断地封装,这里就出现了以前总说定义不要再头文件。
其次,就是智能指针,像epoll中为了正确的读写,我们给每一个sock都配对了一个属于自己的缓冲区,而此时为了更好的管理我们new出来的缓冲区,我们最好用智能指针,但是这里应该用什么呢?
后面会说。先来看看代码吧!
#pragma once
#include <memory>
#include <unordered_map>
#include <cassert>
#include "sock.hpp"
#include "epoll_operator.hpp"
#define NUM 256
// 声明函数与类
struct IO;
class EpollSock;
void handevent(epoll_event *event, int num, int sock, std::unordered_map<int, std::unique_ptr<IO> &> &ptr);
void Func(int sock, std::string &request, std::unordered_map<int, std::unique_ptr<IO> &> &ptr);
void Accept(int sock, std::unordered_map<int, std::unique_ptr<IO> &> &ptr);
void Recv(int sock, std::unordered_map<int, std::unique_ptr<IO> &> &ptr);
void Write(int sock, std::unordered_map<int, std::unique_ptr<IO> &> &ptr);
bool setblock(int sock);
using fun_c = std::function<void(epoll_event *, int, int, std::unordered_map<int, std::unique_ptr<IO> &> &)>;
using fun_t = std::function<void(int, std::string &, std::unordered_map<int, std::unique_ptr<IO> &> &)>;
struct IO
{
std::string _inbuffer; // 读缓冲区
std::string _outbuffer; // 写缓冲区
Sock *p; // 服务器
EpollOper *ep; // epoll操作
EpollSock *esp; // epoll服务器
};
class EpollSock
{
public:
EpollSock() : _handl(handevent)
{
// 创建套接字
_serve.Bind();
_sock = _serve.Listen();
// 插入到epoll中
_epoll.EpollCtl(EPOLL_CTL_ADD, _sock, EPOLLET | EPOLLIN);
// 建立对应关系
IO *p = new IO;
static std::unique_ptr<IO> s(p);
s->p = &_serve;
s->ep = &_epoll;
s->esp = this;
std::pair<int, std::unique_ptr<IO> &> op(_sock, s);
_mapsock.insert(op);
}
void Change(int sock,bool answe)
{
if(answe)
{
_epoll.EpollCtl(EPOLL_CTL_MOD, sock, EPOLLIN | EPOLLOUT | EPOLLET);
}
else
{
_epoll.EpollCtl(EPOLL_CTL_MOD, sock, EPOLLIN | EPOLLET);
}
}
void start()
{
while (true)
{
int n = _epoll.EpollWit(_arr_event, NUM, 1000);
switch (n)
{
case -1:
std::cout << "epoll_wait fail:" << strerror(errno) << std::endl;
break;
case 0:
std::cout << "timeout ..." << std::endl;
break;
default:
_handl(_arr_event, n, _sock, _mapsock);
break;
}
}
}
private:
int _sock;
Sock _serve;
fun_c _handl;
EpollOper _epoll;
epoll_event _arr_event[NUM];
std::unordered_map<int, std::unique_ptr<IO> &> _mapsock;
};
#pragma once
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/epoll.h>
//封装epoll函数
class EpollOper
{
public:
EpollOper() : _flag(126)
{
// 创建epfd
_epfd = epoll_create(_flag);
if (_epfd < 0)
{
std::cout << "epoll_create fail:" << strerror(errno) << std::endl;
exit(6);
}
}
void EpollCtl(int oper, int sock, uint32_t event)
{
epoll_event node;
node.data.fd = sock;
node.events = event;
int ret = epoll_ctl(_epfd, oper, sock, &node);
if (ret < 0)
{
std::cout << "epoll_ctl fail:" << strerror(errno) << std::endl;
exit(7);
}
}
int EpollWit(epoll_event *event, int maxnum, int timeout)
{
int n = epoll_wait(_epfd, event, maxnum, timeout);
if (n < 0)
{
std::cout << "epoll_wait fail:" << strerror(errno) << std::endl;
exit(8);
}
return n;
}
~EpollOper()
{
if(_epfd>0) close(_epfd);
}
private:
int _epfd;
const int _flag;
};
#include "eopllsever.hpp"
void Accept(int sock, std::unordered_map<int, std::unique_ptr<IO> &> &ptr)
{
// 找到对应的节点
auto cur = ptr.find(sock);
assert(cur != ptr.end());
// 构建映射关系
IO *pp = new IO;
static std::unique_ptr<IO> s(pp);
s->p = cur->second->p;
s->ep = cur->second->ep;
s->esp = cur->second->esp;
// 正确读取
while (true)
{
int serversock = cur->second->p->Accept();
if (serversock >= 0)
{
std::pair<int, std::unique_ptr<IO> &> pa(serversock, s);
// 首先设置非阻塞读取
setblock(serversock);
// 插入对应关系
ptr.insert(pa);
// 插入epoll模型
s->ep->EpollCtl(EPOLL_CTL_ADD, serversock, EPOLLET | EPOLLIN);
std::cout << "insert success sock:" << serversock << std::endl;
}
if (serversock < 0)
{
if (errno == EWOULDBLOCK || errno == EAGAIN)
{
std::cout << "没数据,请再试一次" << std::endl;
break;
}
else if (errno == EINTR)
{
std::cout << "被中断,请再试一次" << std::endl;
continue;
}
else
{
std::cout << "accept fail:" << strerror(errno) << std::endl;
break;
}
}
}
}
void Recv(int sock, std::unordered_map<int, std::unique_ptr<IO> &> &ptr)
{
auto cur = ptr.find(sock);
assert(cur != ptr.end());
while (true)
{
char buffer[1024];
ssize_t s = recv(sock, buffer, 1023, 0);
if (s > 0)
{
buffer[s] = '\0';
cur->second->_inbuffer += buffer;
}
if (s == 0)
{
std::cout << "客户端退出" << std::endl;
// 从epoll中删除节点
cur->second->ep->EpollCtl(EPOLL_CTL_DEL, sock, 0);
// 删除对应关系
ptr.erase(sock);
// 关闭文件描述符
close(sock);
break;
}
if (s < 0)
{
if (errno == EWOULDBLOCK || errno == EAGAIN)
{
std::cout << "没数据,请再试一次" << std::endl;
fun_t fun(Func);
fun(sock, cur->second->_inbuffer, ptr);
break;
}
else if (errno == EINTR)
{
std::cout << "被中断,请再试一次" << std::endl;
continue;
}
else
{
std::cout << "recv fail:" << strerror(errno) << std::endl;
break;
}
}
}
}
void Write(int sock, std::unordered_map<int, std::unique_ptr<IO> &> &ptr)
{
auto cur = ptr.find(sock);
assert(cur != ptr.end());
while (true)
{
ssize_t s = send(sock, cur->second->_outbuffer.c_str(), cur->second->_outbuffer.size(), 0);
if (s > 0)
{
std::cout << "send success" << std::endl;
cur->second->_outbuffer.erase(0, s);
if (cur->second->_outbuffer.empty())
break;
}
else
{
if (errno == EWOULDBLOCK || errno == EAGAIN)
{
std::cout << "没空间" << std::endl;
break;
}
else if (errno == EINTR)
{
std::cout << "被中断" << std::endl;
continue;
}
else
{
std::cout << "Write fail:" << strerror(errno) << std::endl;
break;
}
}
}
}
void handevent(epoll_event *event, int num, int sock, std::unordered_map<int, std::unique_ptr<IO> &> &ptr)
{
for (size_t i = 0; i < num; i++)
{
std::cout << event[i].events << std::endl;
// 读事件就绪
if ((ptr.find(event[i].data.fd) != ptr.end()) && (event[i].events & EPOLLIN))
{
if (event[i].data.fd == sock)
{
Accept(event[i].data.fd, ptr);
}
else
{
Recv(event[i].data.fd, ptr);
}
}
// 写事件就绪
if ((ptr.find(event[i].data.fd) != ptr.end()) && (event[i].events & EPOLLOUT))
{
Write(event[i].data.fd, ptr);
auto cur = ptr.find(sock);
cur->second->esp->Change(sock, false);
std::cout << "已关闭" << std::endl;
}
}
}
bool setblock(int sock)
{
int fl = fcntl(sock, F_GETFL);
if (fl < 0)
return false;
fcntl(sock, F_SETFL, fl | O_NONBLOCK);
return true;
}
void Func(int sock, std::string &request, std::unordered_map<int, std::unique_ptr<IO> &> &ptr)
{
// 反序列化
std::cout << request << std::endl;
// 处理业务
// 构建响应
std::string respon = request;
request.clear();
// 发送给客户端
auto cur = ptr.find(sock);
assert(cur != ptr.end());
cur->second->_outbuffer = respon;
cur->second->esp->Change(sock, true);
}
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
bool setblock(int sock);
#define QUEUE_NUM 20 // 监听队列长度
class Sock
{
public:
Sock()
: _listen_queue(QUEUE_NUM)
{
int sock = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字
if (sock < 0)
{
std::cout << "socket fail:" << strerror(errno) << std::endl;
exit(1);
}
// 设置端口复用
int opt = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
// 设置非阻塞读取
_sock = sock;
setblock(_sock);
}
void Bind(uint16_t port = 8080, const std::string &ip = "0.0.0.0")
{
sockaddr_in sever; // 创建sockaddr_in结构体
memset(&sever, 0, sizeof sever);
sever.sin_addr.s_addr = inet_addr(ip.c_str());
sever.sin_port = htons(port);
sever.sin_family = AF_INET;
socklen_t len = sizeof sever;
int ret = bind(_sock, (sockaddr *)(&sever), len); // 绑定
if (ret < 0)
{
std::cout << "bind fail:" << strerror(errno) << std::endl;
exit(2);
}
}
int Listen()
{
int lret = listen(_sock, _listen_queue); // 监听
if (lret < 0)
{
std::cout << "listen fail:" << strerror(errno) << std::endl;
exit(3);
}
return _sock;
}
void Connect(int sock, uint16_t port, const std::string &ip)
{
sockaddr_in sever;
memset(&sever, 0, sizeof sever);
sever.sin_addr.s_addr = inet_addr(ip.c_str());
sever.sin_port = htons(port);
sever.sin_family = AF_INET;
int ret = connect(sock, (sockaddr *)(&sever), sizeof sever);
if (ret < 0)
{
std::cout << "connect fail:" << strerror(errno) << std::endl;
exit(4);
}
}
int Accept()
{
sockaddr_in client;
memset(&client, 0, sizeof client);
socklen_t len = sizeof client;
int sock = accept(_sock, (sockaddr *)(&client), &len);
if (sock > 0)
{
return sock;
}
return -1;
}
~Sock()
{
if (_sock > 0)
close(_sock);
}
int _sock; // 套接字
private:
const int _listen_queue; // 监听队列长度
};
#include "eopllsever.hpp"
int main()
{
EpollSock s;
s.start();
return 0;
}
总体代码如上,其实我最后懒的写线程池和自己定制协议了,但是也不影响。我们先来说了上面的问题之后,就来说说原因吧!(没有会所其他地方,个人感觉是没有必要的,如果有兄弟也在写epoll,可以私信交流)。
先说最后一个原因吧,我把智能指针定义成为static的原因是,如果不定义静态的,那么就会出现局部变量出了作用域就会销毁,所以,要是打算用智能指针的话,千万别定义成局部变量。其实在这里我也想了很久,如果定义成静态的话,势必要浪费资源,而服务器又是无限循环的,但是如果不用的话,那么我们一不注意就会造成内存泄漏,后果就不用收了吧,我对比了一下,相对于内存泄漏,这个还是比较内存泄漏好。
其次就是第二个问题的原因:为什么我要说定义不要再头文件中呢?这里说起来很惭愧,C++用久了之后,忘了很多C的语法了。但是直到今天我才弄明白一个很久以前的知识点。那就是定义最好不要在头文件中。再用C++写的时候,大部分解耦都很实现成类,这个不用说,毕竟是面向对象的编程嘛,但是我今天的写的时候,他给我报了个很疑惑的错误,从来没见过,如下图:
这个错误一出来我就很疑惑,因为代码被我改正确了,所以错误不一样,但是大体就是这样的 ,还有就是显示的是没有这个类型名,我看了看我自己的头文件,都包含了,但是还是显示错误,然后我就上网查了一下,网上说的大概是头文件重复包含了,但是我的每个头文件都有#pragma once防止重复包含的,所以我就一直在看,以为编译过不了,连日志也不能打,只能干看,最后才发现是有几个函数的定义我放在了头文件,导致两个文件互相包含,这样的话,连声明函数都不知道怎么声明,后来才写成来两个.cpp的文件。
最后,不知道大家有没有和我一样犯过这些错误的,如果有,希望下次咱们都不会犯了,如果感觉对你有用话,就点一下赞吧!!!(改了一天的bug,希望大家能够支持)