网络库:NetLib 的封装

目录

一:前言

二:Socket的封装

1:成员变量

2:服务端的函数

3:客户端的函数

4:触发的事件回调

5:发送和接收

三:事件循环

1:定时器

2:Event

3:epoll_wait

4:检查任务

四:net_lib的封装


一:前言

写过网络的小伙伴,一定写过长长的代码,来完成一个服务器的框架,长长的代码堆叠在一个main函数中,十分不美观,今天我们就将网络相关的代码全部封装成接口,使main函数中显示的代码十分简洁。

cpp 复制代码
int main()
{
    int ret = netlib_listen(http_ip_, http_port, (callback_t)Http_callback, NULL);
    if(ret == -1)
    {
        printf("listen %s:%s failed\n",http_ip_,http_port_);
        return -1;
    }

    LogInfo("server start listen on:For http://{}:{}", http_ip_, http_port_);

    LogInfo("now enter the event loop...");

    netlib_eventloop(1);

}

我们看上面的代码,发现我们只把服务端的监听给暴露出来了,以及事件循环的接口,其他一概不知,使代码十分简洁。下面我们开始进行封装。

二:Socket的封装

1:成员变量

首先甭管服务端还是客户端,他俩的函数其实都差不多,只有listen,accept,和connect的区别,其他基本一样,所以我们创建一个基类,在这个基类中我们将服务端和客户端的代码全部加进去,方便我们进行封装操作。

在下面的类中,我们看成员变量,包括最基本的远程本地的ip和port,还有我们存放任务的callback和callbackdata,以及表示当前的socket,和连接状态。对于将成员进行返回的函数,没啥好讲的,剩下的就是服务端和客户端的具体函数了

cpp 复制代码
//关于网络连接的最基本类
class CbaseSocket : public CRefObject {
    public:
    CbaseSocket();
    virtual ~CbaseSocket();     //纯虚函数

    void SetSocket (SOCKET fd) {socket_ = fd;}
    void SetState (uint8_t state) {state_ = state;}
    void SetCallback (callback_t callback){callback_ = callback ;}
    void SetCallbackData (void *data) {callbackdata_ = data;}
    void SetRemoteIP (char *ip) {remote_ip_ = ip;}
    void SetRemotePort (uint16_t port) {remote_port_ = port;}
    void SetSendBufSize(uint32_t send_size);
    void SetRecvBufSize(uint32_t recv_size);

    SOCKET GetSocket() {return socket_;}
    const char *GetRemoteIP() { return remote_ip_.c_str(); }
    uint16_t GetRemotePort() { return remote_port_; }
    const char *GetLocalIP() { return local_ip_.c_str(); }
    uint16_t GetLocalPort() { return local_port_; }

    public:
    //监听地址和端口
    int Listen(const char * server_ip,uint16_t server_port,callback_t callback,void * callback_data);

    //客户端进行连接的函数
    int Connect(const char * server_ip,uint16_t server_port,callback_t callback,void * callback_data);

    //发送数据
    int Send(void * buf ,int len);

    //接收数据
    int Recv(void * buf ,int len);

    //关闭连接
    int Close();

    public:
    //三种触发的事件
    void OnRead();
    void OnWrite();
    void OnClose();

    private: //私有函数以_ 开头
    int _GetErrorCode();
    bool _IsBlock(int error_code);

    void _SetNonblock(SOCKET fd);
    void _SetReuseAddr(SOCKET fd);
    void _SetNoDelay(SOCKET fd);
    void _SetAddr(const char *ip, const uint16_t port, sockaddr_in *addr);

    //接收客户端连接的地方
    void _AcceptNewSocket(); 

    private:
    //最基本的一些变量,源和目的 的 IP和端口 ,以及要传入进来的回调函数和参数。
    std::string remote_ip_;
    uint16_t remote_port_;
    std::string local_ip_;
    uint16_t local_port_;

    callback_t callback_;
    void *callbackdata_;

    uint8_t state_;
    SOCKET socket_;     //int
};

2:服务端的函数

服务端最重要的就是listen和accept函数,我们将关于他的代码全部放进去,封装起来,下面是listen和accept的函数。

cpp 复制代码
//监听地址和端口,总的来说就是设置socket,bind地址,最后listen。
int CbaseSocket::Listen(const char * server_ip,uint16_t server_port,callback_t callback,void * callback_data)
{
    //这里的listen监听函数,其中包含了回调函数,是客户端要传进来的参数。当然服务器也是需要配置一个socket的
    local_ip_ = server_ip;
    local_port_ = server_port;
    callback_ = callback;
    callbackdata_ = callback_data;

    socket_ = socket(AF_INET,SOCK_STREAM,0 );
    if(socket_ == INVALID_SOCKET)
    {
        printf("socket failed, err_code=%d, server_ip=%s, port=%u",_GetErrorCode(), server_ip, server_port);
        return NETLIB_ERROR;
    }

    _SetReuseAddr(socket_);
    _SetNonblock(socket_);

    sockaddr_in serv_addr;
    _SetAddr(server_ip,server_port,&serv_addr);

    int ret = bind(socket_,(sockaddr *)&serv_addr,sizeof(serv_addr));
    if(ret == SOCKET_ERROR)
    {
        printf("bind failed, err_code=%d, server_ip=%s, port=%u", _GetErrorCode(), server_ip, server_port);
        closesocket(socket_);
        return NETLIB_ERROR;
    }

    ret = listen(socket_,64);
    if(ret == SOCKET_ERROR)
    {
        printf("listen failed, err_code=%d, server_ip=%s, port=%u", _GetErrorCode(), server_ip, server_port);
        closesocket(socket_);
        return NETLIB_ERROR;
    }

    state_ = SOCKET_STATE_LISTENING;

    printf("listen success, server_ip=%s, port=%u", server_ip, server_port);

    Addsocket(this);    //将服务端自己添加进去
    CEventDispatch::Instance()->AddEvent(socket_, SOCKET_READ | SOCKET_EXCEP);      //此处是进行分发事件
    return NETLIB_OK;

}

我们accept是要一直阻塞等待的,所以实用while循环,当客户端链接上来,那就给他分配一个socket,然后就是添加到事件循环等等。最后我们看,这个客户端还执行了传入来的回调函数,这个回调函数里面还是重新设置回调函数,将业务代码添加进去。

cpp 复制代码
//接收客户端连接的地方,服务端最开始只需要进行监听即可,在只读中开始接收客户端的连接。
void CbaseSocket::_AcceptNewSocket()
{
    SOCKET fd =0;
    sockaddr_in peer_addr;
    socklen_t addr_len = sizeof(sockaddr_in);
    char ip_str[64];
    while((fd = accept(socket_,(sockaddr *)&peer_addr,&addr_len))!=INVALID_SOCKET) 
    {   //连接上来了,我们分配一个socket给他
        CbaseSocket * psocket = new CbaseSocket();
        uint32_t ip = ntohl(peer_addr.sin_addr.s_addr);
        uint16_t port = ntohs(peer_addr.sin_port);
        snprintf(ip_str, sizeof(ip_str), "%d.%d.%d.%d", ip >> 24,(ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);

        psocket->SetSocket(fd);
        psocket->SetRemoteIP(ip_str);
        psocket->SetRemotePort(port);
        psocket->SetCallback(callback_);
        psocket->SetCallbackData(callbackdata_);
        psocket->SetState(SOCKET_STATE_CONNECTED);

        _SetNoDelay(fd);
        _SetNonblock(fd);
        Addsocket(psocket);
        CEventDispatch::Instance()->AddEvent(fd,SOCKET_READ | SOCKET_EXCEP);

        //这里的回调函数也就是重新设置这个客户端的一些回调函数,以便后面使用业务相关的一些代码。
        //因为第一次只读状态肯定是客户端进行连接,而第二次只读状态就是业务相关的代码。
        callback_(callbackdata_, NETLIB_MSG_CONNECT, (net_handle_t)fd, NULL);
    } 
}

3:客户端的函数

我们服务端进行接收的操作,那我们客户端就进行连接操作,和正常的连接操作没什么不同。

cpp 复制代码
//客户端进行连接的函数
int CbaseSocket::Connect(const char * server_ip,uint16_t server_port,callback_t callback,void * callback_data)
{
    //这里是客户端的
    printf("");
    remote_ip_=server_ip;
    remote_port_=server_port;
    callback_=callback;
    callbackdata_=callback_data;

    socket_ = socket(AF_INET,SOCK_STREAM,0);
    if(socket_==INVALID_SOCKET)
    {
        printf("socket failed, err_code=%d, server_ip=%s, port=%u", _GetErrorCode(), server_ip, server_port);
        return NETLIB_ERROR;
    }

    _SetNonblock(socket_);
    _SetNoDelay(socket_);
    sockaddr_in serv_addr;
    _SetAddr(server_ip, server_port, &serv_addr);

    int ret = connect(socket_, (sockaddr *)&serv_addr, sizeof(serv_addr));
    if((ret==SOCKET_ERROR)&&(!_IsBlock(_GetErrorCode())))
    {
        printf("connect failed, err_code=%d, server_ip=%s, port=%u", _GetErrorCode(), server_ip, server_port);
        close(socket_);
        return NETLIB_INVALID_HANDLE;
    }

    state_ = SOCKET_STATE_CONNECTING;
    Addsocket(this);
    CEventDispatch::Instance()->AddEvent(socket_, SOCKET_READ | SOCKET_EXCEP);
    return (net_handle_t)socket_;

}

4:触发的事件回调

首先在Onread中,我们看到有两个地方,首先就是触发了服务端的事件,表示客户端连接上来了,然后我们进行连接的操作。另一个就是客户端触发了读事件,执行自己的回调函数。对于只写和关闭,我们还是执行客户端的回调函数。

cpp 复制代码
//三种触发的事件
void CbaseSocket::OnRead()
{
    //我们服务端的只读状态是当客户端连接上来的时候。
    if(state_==SOCKET_STATE_LISTENING)
    {
        _AcceptNewSocket();
    }
    else
    {
        //除了连接,就是客户端发来数据的只读状态。还有要关闭连接的时候。
        u_long avail = 0;
        int ret = ioctlsocket(socket_, FIONREAD, &avail);
        if((ret == SOCKET_ERROR)||(avail ==0))
        {
            callback_(callbackdata_,NETLIB_MSG_CLOSE,(net_handle_t)socket_,NULL);
        }
        else
        {
            callback_(callbackdata_,NETLIB_MSG_READ,(net_handle_t)socket_,NULL);
        }
    }
    
}

void CbaseSocket::OnWrite()
{
    if(state_ == SOCKET_STATE_CONNECTING)
    {
        callback_(callbackdata_,NETLIB_MSG_CLOSE,(net_handle_t)socket_,NULL);

    }else 
    {
        callback_(callbackdata_,NETLIB_MSG_WRITE,(net_handle_t)socket_,NULL);

    }
}

void CbaseSocket::OnClose()
{
    state_ = SOCKET_STATE_CLOSING;
    callback_(callbackdata_,NETLIB_MSG_CLOSE,(net_handle_t)socket_,NULL);
}

5:发送和接收

我们在把这些send和recv 也给封装起来。

cpp 复制代码
//发送数据
int CbaseSocket::Send(void * buf ,int len)
{
    //发送数据之前,我们要保证,连接是连接状态
    if(state_ != SOCKET_STATE_CONNECTED)
        return NETLIB_ERROR;

    int ret = send(socket_,(char *) buf,len,0);
    if(ret == SOCKET_ERROR) 
    {
        int err_code = _GetErrorCode();
        printf("send failed, err_code=%d, len=%d", err_code, len);
    }

    return ret;
}

//接收数据
int CbaseSocket::Recv(void * buf ,int len)
{
    return recv(socket_,(char *) buf,len,0);
}

//关闭连接
int CbaseSocket::Close()
{
    CEventDispatch::Instance()->RemoveEvent(socket_, SOCKET_READ | SOCKET_EXCEP);   //移除这个事件
    RemoveSocket(this);//将自己进行删除
    closesocket(socket_);
    ReleaseRef();
    return 0;
}

三:事件循环

这里的事件循环就是主要封装epoll的,并且还加入了我们制作的定时器。下面这个类中我们的成员变量就是epoll的相关fd,以及定时器存放的容器和事件触发的容器。

cpp 复制代码
//我们使用单例模式
class CEventDispatch 
{
    public:
    virtual ~CEventDispatch();

    void AddEvent(SOCKET fd, uint8_t socket_event);
    void RemoveEvent(SOCKET fd , uint8_t socket_event);

    void AddTimer(callback_t callback,void * user_data,uint64_t interval);
    void RemoveTimer(callback_t callback,void * user_data);

    void Addloop(callback_t callback ,void * user_data);

    void StartDispatch(uint32_t wait_timeout = 100);        //开始循环等待
    void StopDispatch();

    static CEventDispatch * Instance();
    bool IsRunning() { return running_; }

    protected:
    CEventDispatch();

    private:
    void _CheckTimer();
    void _CheckLoop();

    typedef struct 
    {
        callback_t callback;
        void * user_data;
        uint64_t interval;
        uint64_t next_tick;
    }TimerItem;

    private:
    int epfd_;
    CLock lock_;
    list<TimerItem *> timer_list_;
    list<TimerItem *> loop_list_;

    static CEventDispatch *event_dispatch_;     //单例模式
    bool running_;
};

1:定时器

这个定时器比较简单,并没有使用像时间轮那种复杂度很高的定时器,我们这个定时器方案,能够保证可以使用即可。我们将定时任务放入到定时器中,然后每次当触发的事件结束后,就进行检查操作。

cpp 复制代码
void CEventDispatch::AddTimer(callback_t callback,void * user_data,uint64_t interval)
{
    list<TimerItem *>::iterator it;
    //如果有一样的回调函数
    for(it = timer_list_.begin();it!=timer_list_.end();it++)
    {
        TimerItem *ptimer=*it;
        if(ptimer->callback ==callback&&ptimer->user_data ==user_data)
        {
            ptimer->interval = interval;
            ptimer->next_tick = GetTickCount() + interval;
            return ;
        }
    }

    TimerItem * ptimer = new TimerItem;
    ptimer->callback = callback;
    ptimer->interval = interval;
    ptimer->user_data = user_data;
    ptimer->next_tick = GetTickCount() + interval;
    timer_list_.push_back(ptimer);

    return ;
}

void CEventDispatch::RemoveTimer(callback_t callback,void * user_data)
{
    list<TimerItem *>::iterator it;
    for(it = timer_list_.begin();it!=timer_list_.end();it++)
    {
        TimerItem *ptimer=*it;
        if(ptimer->callback ==callback&&ptimer->user_data ==user_data)
        {
            timer_list_.erase(it);
            delete ptimer;
            return ;
        }
    }
}

2:Event

简单的加入操作

cpp 复制代码
void CEventDispatch::AddEvent(SOCKET fd, uint8_t socket_event)
{
    struct epoll_event ev;
    ev.events =EPOLLIN | EPOLLOUT | EPOLLET | EPOLLPRI | EPOLLERR | EPOLLHUP;
    ev.data.fd = fd;
    int ret = epoll_ctl(epfd_,EPOLL_CTL_ADD,fd,&ev);
    if(ret <0)
    {
        printf("EPOLL_CTL_ADD faild fd:%d \n",fd);
    }
}

void CEventDispatch::RemoveEvent(SOCKET fd , uint8_t socket_event)
{
    int ret = epoll_ctl(epfd_,EPOLL_CTL_DEL,fd,NULL);
    if(ret <0)
    {
        printf("EPOLL_CTL_DEL faild fd:%d \n",fd);
    }
}

3:epoll_wait

我们在这里等待事件的触发,当触发后,执行相应的代码,最后检查定时器和loop的任务。

cpp 复制代码
void CEventDispatch::StartDispatch(uint32_t wait_timeout )
{
    if(running_)
        return;
    running_ = true;
    int ret =0;
    struct epoll_event events[1024];

    while(running_)
    {
        ret =epoll_wait(epfd_,events,1024,wait_timeout);
        for(int i=0;i<ret;i++)
        {
            //拿取这个fd的全部信息,包括一些回调函数
            int ret1=events[i].data.fd;
            CbaseSocket * basesocket = FindCbaseSocket(ret1);
            if(!basesocket)
                continue;

            if(events[i].events &EPOLLIN)
            {
                basesocket->OnRead();
            }

            if(events[i].events &EPOLLOUT)
            {
                basesocket->OnWrite();
            }

            if(events[i].events &EPOLLRDHUP)
            {
                basesocket->OnClose();
            }

            if (events[i].events & (EPOLLPRI | EPOLLERR | EPOLLHUP)) 
            {
                basesocket->OnClose();         
            }

            basesocket->ReleaseRef();
        }

        //当上述的全部信息执行完毕,我们开始将多线程的回复信息发送出去,以及定时器的触发。
        _CheckTimer();
        _CheckLoop();
    }
}

4:检查任务

我们开始检查任务是否超时,以及将任务返回的消息发送出去。

cpp 复制代码
void CEventDispatch::_CheckTimer()
{
    for(list<TimerItem*>::iterator it = loop_list_.begin();it!=loop_list_.end();)
    {
        TimerItem *ptimer = *it;
        it++;   //防止在迭代器内,将itdelete掉
        if(ptimer->next_tick <=GetTickCount() )
        {
            ptimer->next_tick +=ptimer->interval;       //因为这个定时器,并未被删除,所以要加入这个时间。
            ptimer->callback(ptimer->user_data,NETLIB_MSG_LOOP,0,NULL);
        }
    }
}

void CEventDispatch::_CheckLoop()
{
    list<TimerItem*>::iterator it;
    for(it = loop_list_.begin();it!=loop_list_.end();it++)
    {
        TimerItem *ptimer = *it;
        ptimer->callback(ptimer->user_data,NETLIB_MSG_LOOP,0,NULL);
    }
}

int GetTickCount1()
{
    struct timeval tval;
    uint64_t ret_tick;
    gettimeofday(&tval,NULL);
    ret_tick =tval.tv_sec * 1000L + tval.tv_usec /1000L;
    return ret_tick;
}

四:net_lib的封装

我们的net_lib封装就是在上面的两个类中,在进行封装一层,就是我们可以直接调用,不用再写多余的代码。

cpp 复制代码
#ifdef __cplusplus
extern "C"
{
#endif
    int netlib_init();
	int netlib_destroy();
	int netlib_listen(const char *server_ip,uint16_t port,callback_t callback,void *callback_data);
	net_handle_t netlib_connect(const char *server_ip,uint16_t port,callback_t callback,void *callback_data);
	int netlib_send(net_handle_t handle, void *buf, int len);
	int netlib_recv(net_handle_t handle, void *buf, int len);
	int netlib_close(net_handle_t handle);
	int netlib_option(net_handle_t handle, int opt, void *optval);
	int netlib_register_timer(callback_t callback, void *user_data, uint64_t interval);
	int netlib_delete_timer(callback_t callback, void *user_data);
	int netlib_add_loop(callback_t callback, void *user_data);
	void netlib_eventloop(uint32_t wait_timeout = 100);
	void netlib_stop_event();
	bool netlib_is_running();

#ifdef __cplusplus
}
#endif

下面我直接给出全部代码,因为都是直接封装的另一层,并没有添加多余的代码,而且还很好理解。

cpp 复制代码
#include "netlib.h"
#include "base_socket.h"
#include "event_dispatch.h"

int netlib_init()
{
    return 0;
}

int netlib_destroy()
{
    return 0;
}

int netlib_listen(const char *server_ip,uint16_t port,callback_t callback,void *callback_data)
{
    CbaseSocket * psocket = new CbaseSocket();
    if(!psocket)
        return -1;

    int ret = psocket->Listen(server_ip, port, callback, callback_data);
    if(ret == -1)
    {
        delete psocket;
        return -1;
    }
    return ret;
}

net_handle_t netlib_connect(const char *server_ip,uint16_t port,callback_t callback,void *callback_data)
{
    CbaseSocket * psocket = new CbaseSocket();
    if(!psocket)
        return -1;
    
    int ret = psocket->Connect(server_ip,port,callback,callback_data);
    if(ret == -1)
    {
        delete psocket;
        return -1;
    }
    return ret;
}

int netlib_send(net_handle_t handle, void *buf, int len)
{
    CbaseSocket * psocket = FindCbaseSocket(handle);
    if(!psocket)
        return -1;
    int ret = psocket->Send(buf, len);
    psocket->ReleaseRef();
    return ret;
}

int netlib_recv(net_handle_t handle, void *buf, int len)
{
    CbaseSocket * psocket = FindCbaseSocket(handle);
    if(!psocket)
        return -1;
    int ret = psocket->Recv(buf, len);
    psocket->ReleaseRef();
    return ret;
}

int netlib_close(net_handle_t handle)
{
    CbaseSocket * psocket = FindCbaseSocket(handle);
    if(!psocket)
        return -1;
    int ret = psocket->Close();
    psocket->ReleaseRef();
    return ret;
}


int netlib_register_timer(callback_t callback, void *user_data, uint64_t interval)
{
    CEventDispatch::Instance()->AddTimer(callback, user_data, interval);
    return 0;
}

int netlib_delete_timer(callback_t callback, void *user_data)
{
    CEventDispatch::Instance()->RemoveTimer(callback, user_data);
    return 0;
}

int netlib_add_loop(callback_t callback, void *user_data)
{
    CEventDispatch::Instance()->Addloop(callback, user_data);
    return 0;
}

void netlib_eventloop(uint32_t wait_timeout )
{
    CEventDispatch::Instance()->StartDispatch(wait_timeout);
}

void netlib_stop_event()
{
    CEventDispatch::Instance()->StopDispatch();
}

bool netlib_is_running()
{
    return CEventDispatch::Instance()->IsRunning();
}

今天讲解了net_lib的封装,十分简洁。https://github.com/0voice

相关推荐
2401_857439692 小时前
SSM 架构下 Vue 电脑测评系统:为电脑性能评估赋能
开发语言·php
SoraLuna3 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos
xlsw_3 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
Dream_Snowar4 小时前
速通Python 第三节
开发语言·python
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
信号处理学渣5 小时前
matlab画图,选择性显示legend标签
开发语言·matlab
红龙创客5 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
fantasy_arch5 小时前
CPU性能优化-磁盘空间和解析时间
网络·性能优化
jasmine s5 小时前
Pandas
开发语言·python
biomooc5 小时前
R 语言 | 绘图的文字格式(绘制上标、下标、斜体、文字标注等)
开发语言·r语言