目录
一:前言
写过网络的小伙伴,一定写过长长的代码,来完成一个服务器的框架,长长的代码堆叠在一个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