找工作小项目:day8-将连接抽象出来

day8-将连接抽象出来

在之前的代码中,我们分离出了Acceptor、Reactor(EventLoop)以及Handler(Channel),但是在newConnection方法中我们缺少delete,会导致内存泄露的发生,考虑一下为什么会导致不使用delete释放呢?
个人理解是由于不知道该什么时候释放,这里使用智能指针的话可能会更好,不用就是为了我们理解。
最好的解决方式就是建立一个Connection类以方便管理。
那么今天的任务就来了,分离connection,能学习的点就在抽象对象更为通用且方便管理。

再明确一次socket:
客户端在于服务器建立链接的时候创建一个socket,通过这个socket可以进行数据传输
服务器在建立时就会带有一个socket,通过这个socket可以进行数据传输
通信时是利用两个socket进行数据传输。

1、错误检测机制(没变)

2、地址创建(没变)

3、socket创建(没变)

4、并发epoll(没变)

5、Channel(没变)

6、EventLoop(没变)

7、Acceptor

在这里插入图片描述

这里开始发生变化了,之前有说过Acceptor的作用就是建立连接的作用,但是什么时候释放这个连接所创建出来地址等信息是不明确的,应当是在关于该连接的一切行为都结束了在将这个连接delete,但是没有办法知道什么时候完全结束。。。
还是先从声明开始解读:

class EventLoop;
class Socket;
class Channel;
class Acceptor
{
private:
    EventLoop *loop;
    Socket *sock;
    Channel *acceptChannel;
    std::function<void(Socket*)> newConnectionCallback;
public:
    Acceptor(EventLoop *_loop);
    ~Acceptor();
    void acceptConnection();
    void setNewConnectionCallback(std::function<void(Socket*)>);
};

和之前的相比少了一个对于地址的创建方法,应该是全部交接给了Connection类来完成,从实现来看看尝试找到变化发生的依据(因为这部分没有添加其他功能,因此,应该就是将某些东西抽象出去了)
先看看构造函数和析构函数,没发生什么变化,只不过我们发现Acceptor的地址属性少了以后释放的位置改了。

Acceptor::Acceptor(EventLoop *_loop) : loop(_loop), sock(nullptr), acceptChannel(nullptr){
    sock = new Socket();
    InetAddress *addr = new InetAddress("127.0.0.1", 1234);
    sock->bind(addr);
    sock->listen(); 
    sock->setnonblocking();
    acceptChannel = new Channel(loop, sock->getFd());
    std::function<void()> cb = std::bind(&Acceptor::acceptConnection, this);
    acceptChannel->setCallback(cb);
    acceptChannel->enableReading();
    delete addr;
}

Acceptor::~Acceptor(){
    delete sock;
    delete acceptChannel;
}

之后是acceptConnection和setNewConnectionCallback,在原本acceptConnection是从服务器的方法上走的,现在将这个放在Acceptor中,使得服务器类更纯粹,可预见的是服务器中应当没有这个方法了。

void Acceptor::acceptConnection(){
    InetAddress *clnt_addr = new InetAddress();      
    Socket *clnt_sock = new Socket(sock->accept(clnt_addr));      
    printf("new client fd %d! IP: %s Port: %d\n", clnt_sock->getFd(), inet_ntoa(clnt_addr->getAddr().sin_addr), ntohs(clnt_addr->getAddr().sin_port));
    clnt_sock->setnonblocking();
    newConnectionCallback(clnt_sock);
    delete clnt_addr;
}

void Acceptor::setNewConnectionCallback(std::function<void(Socket*)> _cb){
    newConnectionCallback = _cb;
}

8、Connection

新的类,到目前应该还是懵的状态,因为并没看到Connection做了什么事情,分离了哪些功能。老规矩,看声明

class EventLoop;
class Socket;
class Channel;
class Connection
{
private:
    EventLoop *loop;
    Socket *sock;
    Channel *channel;
    std::function<void(Socket*)> deleteConnectionCallback;
public:
    Connection(EventLoop *_loop, Socket *_sock);
    ~Connection();
    
    void echo(int sockfd);
    void setDeleteConnectionCallback(std::function<void(Socket*)>);
};

是不是和Acceptor非常像,那么推测他们应该是并行关系,即一个用来完成connection的创建一个用来完成connection的释放,看看猜测是否正确。
构造函数和析构函数是很像的,但是在这部分中不需要创建服务器的任务,会少一部份工作,该部分的回调函数echo需要看一下

Connection::Connection(EventLoop *_loop, Socket *_sock) : loop(_loop), sock(_sock), channel(nullptr){
    channel = new Channel(loop, sock->getFd());
    std::function<void()> cb = std::bind(&Connection::echo, this, sock->getFd());
    channel->setCallback(cb);
    channel->enableReading();
}

Connection::~Connection(){
    delete channel;
    delete sock;
}

回调函数及设置回调函数的方法,这部分和我们想象的不一样,这不是将原本服务器中的read方法给拿过来了吗?但是有个改变是将原本的close(sockfd);换成了deleteConnectionCallback(sock);
发现了!!!!其是将读取事件是否完成作为标志,判断事件是否结束,而close(sockfd);只是关闭了socket对于其他信息并没有进行析构,判断deleteConnectionCallback应该会更加完善。

void Connection::echo(int sockfd){
    char buf[READ_BUFFER];
    while(true){    //由于使用非阻塞IO,读取客户端buffer,一次读取buf大小数据,直到全部读取完毕
        bzero(&buf, sizeof(buf));
        ssize_t bytes_read = read(sockfd, buf, sizeof(buf));
        if(bytes_read > 0){
            printf("message from client fd %d: %s\n", sockfd, buf);
            write(sockfd, buf, sizeof(buf));
        } else if(bytes_read == -1 && errno == EINTR){  //客户端正常中断、继续读取
            printf("continue reading");
            continue;
        } else if(bytes_read == -1 && ((errno == EAGAIN) || (errno == EWOULDBLOCK))){//非阻塞IO,这个条件表示数据全部读取完毕
            printf("finish reading once, errno: %d\n", errno);
            break;
        } else if(bytes_read == 0){  //EOF,客户端断开连接
            printf("EOF, client fd %d disconnected\n", sockfd);
            // close(sockfd);   //关闭socket会自动将文件描述符从epoll树上移除
            deleteConnectionCallback(sock);
            break;
        }
    }
}

void Connection::setDeleteConnectionCallback(std::function<void(Socket*)> _cb){
    deleteConnectionCallback = _cb;
}

9、服务器类

这部分应该是变化很大的,因为将几乎所有方法都抽象出去了,首先看看声明

class EventLoop;
class Socket;
class Acceptor;
class Connection;
class Server
{
private:
    EventLoop *loop;
    Acceptor *acceptor;
    std::map<int, Connection*> connections;
public:
    Server(EventLoop*);
    ~Server();

    void newConnection(Socket *sock);
    void deleteConnection(Socket *sock);
};

多了一个方法和一个map属性,connections应该记录的是socket和对应的Connection,并且我们对于多出来的deleteConnection还是十分感兴趣的想知道其和close(sockfd)有什么差别,还是看看实现。
首先是构造/析构函数,没什么变化,依旧是从Acceptor中创建并建立客户端连接

Server::Server(EventLoop *_loop) : loop(_loop), acceptor(nullptr){ 
    acceptor = new Acceptor(loop);
    std::function<void(Socket*)> cb = std::bind(&Server::newConnection, this, std::placeholders::_1);
    acceptor->setNewConnectionCallback(cb);
}

Server::~Server(){
    delete acceptor;
}

之后是newConnection,在这个方法中我们创建了一个Connection 用以保存事件,并将它写入connections数组,防止之后找不到该套接字。

void Server::newConnection(Socket *sock){
    Connection *conn = new Connection(loop, sock);
    std::function<void(Socket*)> cb = std::bind(&Server::deleteConnection, this, std::placeholders::_1);
    conn->setDeleteConnectionCallback(cb);
    connections[sock->getFd()] = conn;
}

然后是对于deleteConnection方法的定义,重点看看其和close有什么差别。

void Server::deleteConnection(Socket * sock){
    Connection *conn = connections[sock->getFd()];
    connections.erase(sock->getFd());
    delete conn;
}

首先将socket从connections中移除,然后将该Connection释放,close仅关闭了套接字,对于和socket相关的其他信息没有释放,而Connection 中包含了这些信息,仅释放就足够了。

10、总结

感觉还是挺乱的,那么总结一下这个最终变成什么关系了呢?
Server类目前只作为一个对外的接口,其功能包含创建服务器(Acceptor)、创建连接(Acceptor)、处理事件(Connection)以及关闭连接(Server)四个功能
main-Reactor(EventLoop)单独创建,其对每一个事件进行分配处理任务,即找到相关任务处理方。

  • 16
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值