02服务网络通信框架
参考:https://balloonwj.blog.csdn.net/article/details/71518551
每一个服务都使用了相同的网络通信框架,该通信框架可以单独拿出来做为一个通用的网络通信框架。该网络框架是在一个循环里面不断地检测IO事件,然后对检测到的事件进行处理。
- 使用
IO复用技术(epoll)分离网络IO。(这个多路IO复用技术的在不同平台上使用的方式不同,linux_epoll,window_select) - 对分离出来的网络IO进行操作,分为socket句柄可读、可写和出错三种情况。
当然再加上定时器事件,即检测一个定时器事件列表,如果有定时器到期,则执行该定时器事件。
1、整个框架的伪码大致如下:
while (running)
{
//IO multiplexing
nfds = epoll_wait(m_epfd, events, 1024, wait_timeout);
//事件处理,nfds次循环
if (某些socket可读)
{
pSocket->OnRead();
}
if (某些socket可写)
{
pSocket->OnWrite();
}
if (某些socket出错)
{
pSocket->OnClose();
}
//处理定时器事件
_CheckTimer();
}
2、处理定时器事件的代码如下:
即遍历一个定时器列表,将定时器对象与当前时间(curr_tick)做比较,如果当前时间已经大于或等于定时器设置的时间,则表明定时器时间已经到了,执行定时器对象对应的回调函数。
void CEventDispatch::_CheckTimer()
{
uint64_t curr_tick = get_tick_count();
list<TimerItem*>::iterator it;
for (it = m_timer_list.begin(); it != m_timer_list.end(); )
{
TimerItem* pItem = *it;
it++; // iterator maybe deleted in the callback, so we should increment it before callback
if (curr_tick >= pItem->next_tick)
{
pItem->next_tick += pItem->interval;
pItem->callback(pItem->user_data, NETLIB_MSG_TIMER, 0, NULL);
}
}
}
3、OnRead、OnWrite和OnClose这三个函数
在TeamTalk源码中每一个socket连接被封装成一个CBaseSocket对象,该对象是一个使用引用计数的类的子类,通过这种方法来实现生存期自动管理。
(1)OnRead():
/**
* @brief
* OnRead()方法根据状态标识m_state确定一个socket是侦听的socket还是普通与客户端连接的socket,
* 如果是侦听sokcet则接收客户端的连接;
* 如果是与客户端连接的socket,则先检测socket上有多少字节可读,
* 如果没有字节可读或者检测字节数时出错,则关闭socket,反之调用设置的回调函数。
*/
void CBaseSocket::OnRead()
{
if (m_state == SOCKET_STATE_LISTENING)
{
//accept(),新连接进来,产生一个客户端套接字
_AcceptNewSocket();
}
else //读数据的
{
u_long avail = 0;
// 下面等价于 ioctl(m_socket, FIONREAD, &avail);
// FIONREAD:获取接收缓存中数据的字节数,然后将字节数放入avail里面。
int ret = ioctlsocket(m_socket, FIONREAD, &avail);
if ( (SOCKET_ERROR == ret) || (avail == 0) )
{
// m_callback()是一个CBaseSocket对象的一个成员变量,
m_callback(m_callback_data, NETLIB_MSG_CLOSE, (net_handle_t)m_socket, NULL);
}
else
{
//这里就是对于一个已连接的连接来读数据的时候,调用的位置
m_callback(m_callback_data, NETLIB_MSG_READ, (net_handle_t)m_socket, NULL);
}
}
}
补充一个回调函数的细节:
//对于回调函数
//一开始的注册位置是
ret = netlib_listen(http_listen_ip_list.GetItem(i), http_port, http_callback, NULL);
//回调函数加入到套接字CBaseSocket对象的位置,CBaseSocket::Listen(.....);
m_callback = callback; // 监听的回调函数
//m_callback是作为成员变量
callback_t m_callback;
//其类型callback_t是函数指针
typedef void (*callback_t)(void* callback_data, uint8_t msg, uint32_t handle, void* pParam);
//对于函数指针的解释
//链接:http://c.biancheng.net/view/228.html
//如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。
//而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。
int(*p)(int, int);
//定义了一个指针变量 p,该指针变量可以指向返回值类型为 int 型,且有两个整型参数的函数。p 的类型为 int(*)(int,int)。
//如果省略了括号,就不是定义函数指针而是一个函数声明了,即声明了一个返回值类型为指针型的函数。int* p(int, int)。
(2)OnWrite():
/**
* @brief
* OnWrite()函数则根据m_state标识检测socket是否是尝试连接的socket(connect函数中的socket),
* 用于判断socket是否已经连接成功,反之则是与客户端保持连接的socket,调用预先设置的回调函数。
*/
void CBaseSocket::OnWrite()
{
#if ((defined _WIN32) || (defined __APPLE__))
CEventDispatch::Instance()->RemoveEvent(m_socket, SOCKET_WRITE);
#endif
if (m_state == SOCKET_STATE_CONNECTING)
int error = 0;
socklen_t len = sizeof(error);
#ifdef _WIN32
getsockopt(m_socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len);
#else
//getsockopt()查看选项的当前值
//SOL_SOCKET:通用的套接字层次选项
//SO_ERROR:返回挂起的套接字错误并清除
getsockopt(m_socket, SOL_SOCKET, SO_ERROR, (void*)&error, &len);
#endif
if (error)
{
//出错了
m_callback(m_callback_data, NETLIB_MSG_CLOSE, (net_handle_t)m_socket, NULL);
}
else
{
m_state = SOCKET_STATE_CONNECTED;
//NETLIB_MSG_CONFIRM:认证
m_callback(m_callback_data, NETLIB_MSG_CONFIRM, (net_handle_t)m_socket, NULL);
}
}
else
{
//常规的发送数据
m_callback(m_callback_data, NETLIB_MSG_WRITE, (net_handle_t)m_socket, NULL);
}
}
(3)OnClose():
/**
* @brief
* OnClose()方法将标识m_state设置为需要关闭状态,并调用预先设置的回调函数。
*/
void CBaseSocket::OnClose()
{
m_state = SOCKET_STATE_CLOSING;
m_callback(m_callback_data, NETLIB_MSG_CLOSE, (net_handle_t)m_socket, NULL);
}
4、hash_map管理所有的socket
每个服务程序都使用一个stl的hash_map来管理所有的socket,键是socket句柄,值是CBaseSocket对象指针:
typedef hash_map<net_handle_t, CBaseSocket*> SocketMap;
SocketMap g_socket_map;
//往g_socket_map添加套接字
void AddBaseSocket(CBaseSocket* pSocket)
{
//怎么加入的呢,key就是socket句柄fd,而val就是pSocket这个类
g_socket_map.insert(make_pair((net_handle_t)pSocket->GetSocket(), pSocket));
}
//从g_socket_map移除套接字
void RemoveBaseSocket(CBaseSocket* pSocket)
{
g_socket_map.erase((net_handle_t)pSocket->GetSocket());
}
//从g_socket_map中根据fd查找BaseSocket
CBaseSocket* FindBaseSocket(net_handle_t fd)
{
CBaseSocket* pSocket = NULL;
SocketMap::iterator iter = g_socket_map.find(fd);
if (iter != g_socket_map.end())
{
pSocket = iter->second;
//增加引用计数
pSocket->AddRef();
}
return pSocket;
}
5、CEventDispatch对于socket的管理
当采用epoll技术对socket进行管理时:
CEventDispatch类结构
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();
bool isRunning() {return running;}
static CEventDispatch* Instance(); //单例模式
protected:
CEventDispatch();
private:
void _CheckTimer();
void _CheckLoop();
typedef struct {
callback_t callback;
void* user_data;
uint64_t interval; // 间隔时间
uint64_t next_tick; // 下一次标记时间
} TimerItem;
private:
#ifdef _WIN32
fd_set m_read_set;
fd_set m_write_set;
fd_set m_excep_set;
#elif __APPLE__
int m_kqfd;
#else
int m_epfd;
#endif
CLock m_lock;
list<TimerItem*> m_timer_list; // 定时器事件list
list<TimerItem*> m_loop_list;
static CEventDispatch* m_pEventDispatch;
bool running;
};
//在epoll中添加事件
void CEventDispatch::AddEvent(SOCKET fd, uint8_t socket_event)
{
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLOUT | EPOLLET | EPOLLPRI | EPOLLERR | EPOLLHUP;//EPOLLET边沿触发方式
ev.data.fd = fd;
if (epoll_ctl(m_epfd, EPOLL_CTL_ADD, fd, &ev) != 0)//epoll_ctl控制添加事件
{
log_error("epoll_ctl() failed, errno=%d", errno);
}
}
//在epoll中删除事件
void CEventDispatch::RemoveEvent(SOCKET fd, uint8_t socket_event)
{
if (epoll_ctl(m_epfd, EPOLL_CTL_DEL, fd, NULL) != 0)
{
log_error("epoll_ctl failed, errno=%d", errno);
}
}
管理以上功能的是一个单例类CEventDispatch:
其中StartDispatch()和StopDispatcher()分别用于启动和停止整个循环流程。一般在程序初始化的时候StartDispatch(),在程序退出时StopDispatcher()。
void CEventDispatch::StartDispatch(uint32_t wait_timeout)
{
while (running)
{
//IO multiplexing
nfds = epoll_wait(m_epfd, events, 1024, wait_timeout);
//事件处理,nfds次循环
if (某些socket可读)
{
pSocket->OnRead();
}
if (某些socket可写)
{
pSocket->OnWrite();
}
if (某些socket出错)
{
pSocket->OnClose();
}
//处理定时器事件
_CheckTimer();
}
}

浙公网安备 33010602011771号