02服务网络通信框架

参考:https://balloonwj.blog.csdn.net/article/details/71518551

每一个服务都使用了相同的网络通信框架,该通信框架可以单独拿出来做为一个通用的网络通信框架。该网络框架是在一个循环里面不断地检测IO事件,然后对检测到的事件进行处理。

  1. 使用IO复用技术(epoll)分离网络IO。(这个多路IO复用技术的在不同平台上使用的方式不同,linux_epoll,window_select)
  2. 对分离出来的网络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(); 
    }
}
posted @ 2022-06-09 21:19  豪崽_ZH  阅读(89)  评论(0)    收藏  举报