C++负载均衡远程调用学习之 Dns-Route关系构建
目录
1.LARS-DNS-MYSQL环境搭建
2.LARSDNS-系统整体模块的简单说明
3.Lars-Dns-功能说明
4.Lars-Dns-数据表的创建
5.Lars-Dns-整体功能说明
6.Lars-DnsV0.1-Route类的单例实现
7.Lars-DnsV0.1-Route类的链接数据库方法实现
8.Lars-DnsV0.1-定义存放RouteData关系的map数据结构
9.课前回顾
10.Lars-DnsV0.1-将Route数据加载map中
11.Lars-Dns的proto协议定义
12.Lars-DnsV0.1-实现获取route信息功能
13.Lars-DnsV0.1-获取route hosts信息测试
14.Lars-DnsV0.1-总结
1.LARS-DNS-MYSQL环境搭建
四、Lars-DNS Service开发
 
 ## **1) 简介**
 
         负责接收各agent对某modid、cmdid的请求并返回该modid、cmdid下的所有节点,即为agent提供获取路由服务
 
 ### 1.1 架构
 
 
 
 ### **1.2 网络模块**
 
     DnsService服务模型采用了one loop per thread TCP服务器,主要是基于Lars-Reactor:
 
 - 主线程Accepter负责接收连接(agent端连接)
 - Thread loop们负责处理连接的请求、回复;(agent端发送查询请求,期望获取结果)
2.LARSDNS-系统整体模块的简单说明
### **1.3 双map模型** 
 
     DnsServer使用两个map存储路由数据(key = `modid<<32 + cmdid` , value = set of `ip<<32 + port`)
 
 - 一个`RouterDataMap_A`:主数据,查询请求在此map执行
 - 另一个`RouterDataMap_B`:后台线程周期性重加载路由到此map,作为最新数据替换掉上一个map
 
 这两个map分别由指针`data_pointer`与`temp_pointer`指向.
  
3.Lars-Dns-功能说明
### 1.4 Backend Thread守护线程
 
 **dns service还有个业务线程:** 
 
 1、负责周期性(default:1s)检查`RouteVersion`表版本号,如有变化,说明`RouteData`有变更,则重加载`RouteData`表内容;然后将`RouteChange`表中被变更的`modid`取出,根据订阅列表查出`modid`被哪些连接订阅后,向所有工作线程发送任务:要求订阅这些`modid`的连接推送`modid`路由到agent
 
 2、此外,还负责周期性(default:8s)重加载`RouteData`表内容
 
 **PS:重加载`RouteData`表内容的细节**
 
 重加载`RouteData`表内容到`temp_pointer`指向的`RouterDataMap_B`,而后上写锁,交换指针`data_pointer`与`temp_pointer`的地址,于是完成了路由数据更新
4.Lars-Dns-数据表的创建
### **主业务**
 
 1. 服务启动时,`RouteData`表被加载到`data_pointer`指向的`RouterDataMap_A`中, `temp_pointer`指向的`RouterDataMap_B`为空
 
           2. 服务启动后,agent发来Query for 请求某`modid/cmdid`,到其所在Thread Loop上,上读锁查询`data_pointer`指向的`RouterDataMap_A`,返回查询结果;
           3. 如果此`modid/cmdid`不存在,则把`agent ip+port`+`moid/cmdid`发送到Backend thread loop1的队列,让其记录到ClientMap
 
 后台线程Backend thread每隔10s清空`temp_pointer`指向的`RouterDataMap_B`,再加载`RouteData`表内容到`temp_pointer`指向的`RouterDataMap_B`,加载成功后交换指针`data_pointer`与`temp_pointer`指针内容,于是完成了路由数据的更新.
5.Lars-Dns-整体功能说明
## **2) 数据库创建**
 
 * 表`RouteData`: 保存了所有mod路由信息.
 
 | 字段       | 数据类型         | 是否可以为空 | 主键 | 默认 | 附加   | 说明         |
 | ---------- | ---------------- | ------------ | ---- | ---- | ------ | ------------ |
 | id         | int(10) unsigned | No           | 是   | NULL | 自增长 | 该条数据ID   |
 | modid      | int(10) unsigned | No           |      | NULL |        | 模块ID       |
 | cmdid      | int(10) unsigned | No           |      | NULL |        | 指令ID       |
 | serverip   | int(10) unsigned | No           |      | NULL |        | 服务器IP地址 |
 | serverport | int(10) unsigned | No           |      | NULL |        | 服务器端口   |
 
 
 
 * 表`RouteVersion`: 当前`RouteData`路由版本号,每次管理端修改某mod的路由,`RouteVersion`表中的版本号都被更新为当前时间戳
 
 | 字段    | 数据类型         | 是否可以为空 | 主键 | 默认 | 附加   |
 | ------- | ---------------- | ------------ | ---- | ---- | ------ |
 | id      | int(10) unsigned | No           | 是   | NULL | 自增长 |
 | version | int(10) unsigned | No           |      | NULL |        |
 
 
 
 * 表`RouteChange`: 每次管理端修改某mod的路由,会记录本次对哪个mod进行修改(增、删、改),以便指示最新的`RouteData`路由有哪些mod变更了。
 
 | 字段    | 数据类型            | 是否可以为空 | 主键 | 默认 | 附加   |
 | ------- | ------------------- | ------------ | ---- | ---- | ------ |
 | id      | int(10) unsigned    | No           | 是   | NULL | 自增长 |
 | modid   | int(10) unsigned    | No           |      | NULL |        |
 | cmdid   | int(10) unsigned    | No           |      | NULL |        |
 | version | bigint(20) unsigned | No           |      | NULL |        |
 
 
 
 相关创建表格的sql语句如下`lars_dns.sql`
 
 ```sql
 DROP DATABASE if exists lars_dns;
 CREATE DATABASE lars_dns;
 USE lars_dns;
 
 DROP TABLE IF EXISTS `RouteData`;
 CREATE TABLE `RouteData` (
     `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
     `modid` int(10) unsigned NOT NULL,
     `cmdid` int(10) unsigned NOT NULL,
     `serverip` int(10) unsigned NOT NULL,
     `serverport` int(10) unsigned NOT NULL,
     PRIMARY KEY (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=116064 DEFAULT CHARSET=utf8;
 
 
 DROP TABLE IF EXISTS `RouteVersion`;
 CREATE TABLE RouteVersion (
     `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
     `version` int(10) unsigned NOT NULL,
     PRIMARY KEY (`id`)
 );
 INSERT INTO RouteVersion(version) VALUES(0);
 
 DROP TABLE IF EXISTS `RouteChange`;
 CREATE TABLE RouteChange (
     `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
     `modid` int(10) unsigned NOT NULL,
     `cmdid` int(10) unsigned NOT NULL,
     `version` bigint(20) unsigned NOT NULL,
     PRIMARY KEY (`id`)
 );
 ```
 
         我们创建一个基础目录`Lars/base`来存放一些公共的工具和资源.
 
 cd到`Lars/base`, 我们`mkdir sql`, 然后将`lars_dns.sql`拷贝到`sql/`文件夹下。
 
 然后执行创建表格
 
 ```sql
 $mysql -u root -p
 Enter password: 
 Welcome to the MySQL monitor.  Commands end with ; or \g.
 Your MySQL connection id is 18
 Server version: 5.7.27-0ubuntu0.18.04.1 (Ubuntu)
 
 Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
 
 Oracle is a registered trademark of Oracle Corporation and/or its
 affiliates. Other names may be trademarks of their respective
 owners.
 
 Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
 
 mysql> \. lars_dns.sql
 Query OK, 0 rows affected, 1 warning (0.01 sec)
 
 Query OK, 1 row affected (0.00 sec)
 
 Database changed
 Query OK, 0 rows affected, 1 warning (0.00 sec)
 
 Query OK, 0 rows affected (0.08 sec)
 
 Query OK, 0 rows affected, 1 warning (0.01 sec)
 
 Query OK, 0 rows affected (0.06 sec)
 
 Query OK, 1 row affected (0.01 sec)
 
 Query OK, 0 rows affected, 1 warning (0.01 sec)
 
 Query OK, 0 rows affected (0.13 sec)
 ```
6.Lars-DnsV0.1-Route类的单例实现
## 3) dns serivce模块目录构建
 
 ### 3.1 集成lars_reactor模块
 
         首先我们给dns模块创建一个项目文件夹,与`lars_reactor`并列,在`Lars/`下创建
 
 ```bash
 $mkdir Lars/lars_dns
 ```
 
 
 
 在`lars_dns`中,我们可以先创建基本的项目必须文件夹和文件,目录结构如下
 
 ```bash
 lars_dns/
     ├── bin/
     ├── conf/
     │   └── lars_dns.conf
     ├── include/
     ├── Makefile
     └── src/
         └── dns_service.cpp
 ```
 
 
 
 > conf/lars_dns.conf
 
 ```ini
 [reactor]
 maxConn = 1024
 threadNum = 5
 ip = 127.0.0.1
 port = 7778
 ```
 
 
 
 > src/dns_service.cpp
 
 ```c
 #include "lars_reactor.h"
 
 int main(int argc, char **argv)
 {
     event_loop loop;
 
     //加载配置文件
     config_file::setPath("conf/lars_dns.conf");
     std::string ip = config_file::instance()->GetString("reactor", "ip", "0.0.0.0");
     short port = config_file::instance()->GetNumber("reactor", "port", 7778);
 
 
     //创建tcp服务器
     tcp_server *server = new tcp_server(&loop, ip.c_str(), port);
 
     //注册路由业务
     
 
     //开始事件监听    
     printf("lars dns service ....\n");
     loop.event_process();
 
     return 0;
 }
 ```
 
 
 
 > Makefile
 
 ```makefile
 TARGET= bin/lars_dns
 CXX=g++
 CFLAGS=-g -O2 -Wall -Wno-deprecated
 
 BASE=../base
 BASE_H=$(BASE)/include
 LARS_REACTOR=../lars_reactor
 LARS_REACTOR_H =$(LARS_REACTOR)/include
 LARS_REACTOR_LIB=$(LARS_REACTOR)/lib  -llreactor
 
 OTHER_LIB = -lpthread
 SRC= ./src
 INC= -I./include -I$(BASE_H) -I$(LARS_REACTOR_H)
 
 LIB= -L$(LARS_REACTOR_LIB) $(OTHER_LIB)
 
 
 OBJS = $(addsuffix .o, $(basename $(wildcard $(SRC)/*.cpp)))
 
 $(TARGET): $(OBJS)
         mkdir -p bin
         $(CXX) $(CFLAGS) -o $(TARGET) $(OBJS) $(INC) $(LIB)
 
 %.o: %.cpp
         $(CXX) $(CFLAGS) -c -o $@ $< $(INC) 
 
 .PHONY: clean
 
 clean:
         -rm -f src/*.o $(TARGET)
 ```
 
 这里主要注意一下`Makefile`的编写,我们需要连接libreactor库还有libpthread库等,还有一些头文件的文件目录不要写错。
 
 
 
 接下来进行make,我们会在`bin/`得到dns的可执行程序,并且可以成功运行.
7.Lars-DnsV0.1-Route类的链接数据库方法实现
### 3.2 集成mysql模块
 
         我们需要使用libmysqlclient开发者第三方库,当然可以从mysql官方网站下载与你当前mysql版本匹配的so或者a文件,这里我们提供一个已经编译好的libmysqlclient.a和对应的头文件,代码参见:
 
 <https://github.com/aceld/Lars/tree/master/base/mysql-connector-c>
 
         我们把`mysql-connector-c`文件夹放在了`Lars/base/`下,作为公共包使用。
 
         接下来我们要重新修改一下`Makefile`
 
 > Lars/lars_dns/Makefile
 
 ```makefile
 TARGET= bin/lars_dns
 CXX=g++
 CFLAGS=-g -O2 -Wall -Wno-deprecated
 
 BASE=../base
 BASE_H=$(BASE)/include
 LARS_REACTOR=../lars_reactor
 LARS_REACTOR_H =$(LARS_REACTOR)/include
 LARS_REACTOR_LIB=$(LARS_REACTOR)/lib  -llreactor
 
 MYSQL=$(BASE)/mysql-connector-c
 MYSQL_H=$(MYSQL)/include
 MYSQL_LIB=$(MYSQL)/lib/libmysqlclient.a
 
 OTHER_LIB = -lpthread -ldl
 SRC= ./src
 INC= -I./include -I$(BASE_H) -I$(LARS_REACTOR_H) -I$(MYSQL_H)
 
 LIB= $(MYSQL_LIB) -L$(LARS_REACTOR_LIB) $(OTHER_LIB) 
 
 
 OBJS = $(addsuffix .o, $(basename $(wildcard $(SRC)/*.cpp)))
 
 $(TARGET): $(OBJS)
         mkdir -p bin
         $(CXX) $(CFLAGS) -o $(TARGET) $(OBJS) $(INC) $(LIB)
 
 %.o: %.cpp
         $(CXX) $(CFLAGS) -c -o $@ $< $(INC) 
 
 .PHONY: clean
 
 clean:
         -rm -f src/*.o $(TARGET)
 ```
 
         加上mysqlclient库的关联。注意,libmysqlclient.a依赖libdl库, 所以我们在 OTHER_LIB变量中加上`-ldl`, 然后我们尝试使用mysql库的接口。
 
 > dns_service.cpp
 
 ```c
 #include "lars_reactor.h"
 #include "mysql.h"
 
 int main(int argc, char **argv)
 {
     event_loop loop;
 
     //加载配置文件
     config_file::setPath("conf/lars_dns.conf");
     std::string ip = config_file::instance()->GetString("reactor", "ip", "0.0.0.0");
     short port = config_file::instance()->GetNumber("reactor", "port", 7778);
 
 
     //创建tcp服务器
     tcp_server *server = new tcp_server(&loop, ip.c_str(), port);
 
     //注册路由业务
     
     //测试mysql接口
     MYSQL dbconn;
     mysql_init(&dbconn);
 
     //开始事件监听    
     printf("lars dns service ....\n");
     loop.event_process();
 
     return 0;
 }
 ```
8.Lars-DnsV0.1-定义存放RouteData关系的map数据结构
首先我们将Route类设计成单例,我们创建头文件和cpp文件.
 
 > lars_dns/include/dns_route.h
 
 ```c
 #pragma once
 
 class Route
 {
 public:
     //创建单例的方法
     static void init() {
         _instance = new Route(); 
     }
 
     static Route *instance() {
         //保证init方法在这个进程执行中,只执行一次
         pthread_once(&_once, init);
         return _instance;
     }
     
 private:
     //构造函数私有化
     Route();
     Route(const Route&);
     const Route& operator=(const Route&);
 
     //单例
     static Route* _instance;
     //单例锁
     static pthread_once_t _once;
 
     /* ---- 属性 ---- */
     //...
 };
 ```
 
 
 
 > lars_dns/src/dns_route.cpp
 
 ```c
 #include "dns_route.h"
 
 //单例对象
 Route * Route::_instance = NULL;
 
 //用于保证创建单例的init方法只执行一次的锁
 pthread_once_t Route::_once = PTHREAD_ONCE_INIT;
 ```
9.课前回顾
### 4.2 Route中的map数据类型定义
 
         **这里的Route并非reactor中的router,这里的Route我们是把`modid/cmdid`与需要管理的远程服务器的`serverip/serverport`的一条对应关系叫一个`Route`。**
 
         我们用map来存储这些关系,其中key是modid/cmdid的一个二进制偏移量处理,而map的value是一个set集合,因为一个modid/cmdid可能对应多个host主机的ip和端口。具体的表现数据结构形式如下。
 
         
 
         接下来,我们来定义一个相关代码:
 
 > lars_dns/include/dns_route.h
 
 ```c
 #pragma once
 
 #include <pthread.h>
 #include <ext/hash_map>
 #include <ext/hash_set>
 #include "mysql.h"
 
 using __gnu_cxx::hash_map;
 using __gnu_cxx::hash_set;
 
 //定义用来保存modID/cmdID与host的IP/host的port的对应的关系 数据类型  
 typedef hash_map< uint64_t, hash_set<uint64_t> > route_map;
 typedef hash_map< uint64_t, hash_set<uint64_t> >::iterator route_map_it;
 
 //定义用来保存host的IP/host的port的的集合 数据类型
 typedef hash_set<uint64_t> host_set;
 typedef hash_set<uint64_t>::iterator host_set_it;
 
 class Route
 {
 public:
     //创建单例的方法
     static void init() {
         _instance = new Route(); 
     }
 
     static Route *instance() {
         //保证init方法在这个进程执行中,只执行一次
         pthread_once(&_once, init);
         return _instance;
     }
     
 private:
     //构造函数私有化
     Route();
     Route(const Route&);
     const Route& operator=(const Route&);
 
     //单例
     static Route* _instance;
     //单例锁
     static pthread_once_t _once;
 
     /* ---- 属性 ---- */
     //数据库
     MYSQL _db_conn;  //mysql链接  
     char _sql[1000]; //sql语句
 
     //modid/cmdid---ip/port 对应的route关系map
     route_map *_data_pointer; //指向RouterDataMap_A 当前的关系map
     route_map *_temp_pointer; //指向RouterDataMap_B 临时的关系map
     pthread_rwlock_t _map_lock;
 };
 ```
10.Lars-DnsV0.1-将Route数据加载map中
### 4.3 Route初始化
 
 
 
 > lars_dns/src/dns_route.cpp
 
 ```c
 #include <string>
 #include <stdlib.h>
 #include <unistd.h>
 #include "lars_reactor.h"
 #include "dns_route.h"
 #include "string.h"
 
 using namespace std;
 
 //单例对象
 Route * Route::_instance = NULL;
 
 //用于保证创建单例的init方法只执行一次的锁
 pthread_once_t Route::_once = PTHREAD_ONCE_INIT;
 
 
 
 Route::Route()
 {
     //1 初始化锁
     pthread_rwlock_init(&_map_lock, NULL);
 
     //2 初始化map
     _data_pointer = new route_map();//RouterDataMap_A
     _temp_pointer = new route_map();//RouterDataMap_B
 
     //3 链接数据库
     this->connect_db();
 
     //4 查询数据库,创建_data_pointer 与 _temp_pointer 两个map
     this->build_maps();
 }
 
 void Route::connect_db() 
 {
     // --- mysql数据库配置---
     string db_host = config_file::instance()->GetString("mysql", "db_host", "127.0.0.1");
     short db_port = config_file::instance()->GetNumber("mysql", "db_port", 3306);
     string db_user = config_file::instance()->GetString("mysql", "db_user", "root");
     string db_passwd = config_file::instance()->GetString("mysql", "db_passwd", "aceld");
     string db_name = config_file::instance()->GetString("mysql", "db_name", "lars_dns");
 
     mysql_init(&_db_conn);
 
     //超时断开
     mysql_options(&_db_conn, MYSQL_OPT_CONNECT_TIMEOUT, "30");
     //设置mysql链接断开后自动重连
     my_bool reconnect = 1; 
     mysql_options(&_db_conn, MYSQL_OPT_RECONNECT, &reconnect);
 
     if (!mysql_real_connect(&_db_conn, db_host.c_str(), db_user.c_str(), db_passwd.c_str(), db_name.c_str(), db_port, NULL, 0)) {
         fprintf(stderr, "Failed to connect mysql\n");
         exit(1);
     }
 }
 
 void Route::build_maps()
 {
     int ret = 0;
 
     snprintf(_sql, 1000, "SELECT * FROM RouteData;");
     ret = mysql_real_query(&_db_conn, _sql, strlen(_sql));
     if ( ret != 0) {
         fprintf(stderr, "failed to find any data, error %s\n", mysql_error(&_db_conn));
         exit(1);
     }
 
     //得到结果集
     MYSQL_RES *result = mysql_store_result(&_db_conn);
     
     //得到行数
     long line_num = mysql_num_rows(result);
 
     MYSQL_ROW row;
     for (long i = 0; i < line_num; i++) {
         row = mysql_fetch_row(result);
         int modID = atoi(row[1]);
         int cmdID = atoi(row[2]);
         unsigned ip = atoi(row[3]);
         int port = atoi(row[4]);
 
         //组装map的key,有modID/cmdID组合
         uint64_t key = ((uint64_t)modID << 32) + cmdID;
         uint64_t value = ((uint64_t)ip << 32) + port;
 
         printf("modID = %d, cmdID = %d, ip = %lu, port = %d\n", modID, cmdID, ip, port);
 
         //插入到RouterDataMap_A中
         (*_data_pointer)[key].insert(value);
     }
 
     mysql_free_result(result);
 }
 ```
11.Lars-Dns的proto协议定义
### 4.4 测试Route的构造及map加载-V0.1
 
 完成lars dns-service V0.1版本测试
 
 我们在`Lars/base/sql`加入几个简单插入数据的sql语句,方便数据库里有一些测试数据,我们之后应该会提供一个web管理端来操作数据库。
 
 
 
 > Lars/base/sql/dns_route_insert.sql
 
 ```sql
 USE lars_dns;
 
 INSERT INTO RouteData(modid, cmdid, serverip, serverport) VALUES(1, 1, 3232235953, 7777);
 INSERT INTO RouteData(modid, cmdid, serverip, serverport) VALUES(1, 2, 3232235954, 7776);
 INSERT INTO RouteData(modid, cmdid, serverip, serverport) VALUES(1, 2, 3232235955, 7778);
 INSERT INTO RouteData(modid, cmdid, serverip, serverport) VALUES(1, 2, 3232235956, 7779);
 
 UPDATE RouteVersion SET version = UNIX_TIMESTAMP(NOW()) WHERE id = 1;
 ```
 
 
 
 > Lars/base/sql/dns_route_drop.sql
 
 ```sql
 USE lars_dns;
 
 DELETE FROM RouteData;
 UPDATE RouteVersion SET version = UNIX_TIMESTAMP(NOW()) WHERE id = 1;
 ```
 
 
 
         先将测试数据导入数据库。然后回到`lars_dns`下编译。执行
 
 ```bash
 $./bin/lars_dns 
 msg_router init...
 create 0 thread
 create 1 thread
 create 2 thread
 create 3 thread
 create 4 thread
 modID = 1, cmdID = 1, ip = 3232235953, port = 7777
 modID = 1, cmdID = 2, ip = 3232235954, port = 7776
 modID = 1, cmdID = 2, ip = 3232235955, port = 7778
 modID = 1, cmdID = 2, ip = 3232235956, port = 7779
 lars dns service ....
 ```
12.Lars-DnsV0.1-实现获取route信息功能
## 5) 获取Route信息
 
 ### 5.1 proto协议定义
 
         获取Route信息,根据之前的架构图,可以看出来应该是Agent来获取,这里我们并没有实现Agent,所以用一个其他简单的客户端来完成单元测试。但是无论用什么,总需要一个传递数据,需要一定的消息协议,lars-dns也需要设置不同纤细的分发消息路由机制,所以我们需要先定义一些proto协议。
 
         在`Lars/base`下创建`proto/`文件夹.
 
 > Lars/base/proto/lars.proto
 
 ```protobuf
 syntax = "proto3";
 
 package lars;
 
 /* Lars系统的消息ID */
 enum MessageId {
     ID_UNKNOW                = 0;  //proto3 enum第一个属性必须是0,用来占位
     ID_GetRouteRequest       = 1;  //向DNS请求Route对应的关系的消息ID
     ID_GetRouteResponse      = 2;  //DNS回复的Route信息的消息ID
 }
 
 //一个管理的主机的信息
 message HostInfo {
     int32 ip = 1;
     int32 port = 2;
 }
 
 //请求lars-dns route信息的消息内容
 message GetRouteRequest {
     int32 modid = 1; 
     int32 cmdid = 2;
 }
 
 //lars-dns 回复的route信息消息内容
 message GetRouteResponse {
     int32 modid = 1;    
     int32 cmdid = 2;
     repeated HostInfo host = 3;
 }
 ```
 
 
 
         然后我们将proto文件编译成对应的C++文件, 我们还是提供一个脚本
 
 > Lars/base/proto/build.sh
 
 ```bash
 #!/bin/bash
 
 # proto编译
 protoc --cpp_out=. ./*.proto
 
 
 # 将全部的cc 文件 变成 cpp文件
 oldsuffix="cc"
 newsuffix="cpp"
 dir=$(eval pwd)
 for file in $(ls $dir | grep .${oldsuffix})
 do
     name=$(ls ${file} | cut -d. -f1,2)
     mv $file ${name}.${newsuffix}
 done
 echo "build proto file successd!"
 ```
 
         因为protoc会自动生成cc后缀的文件,为了方便我们Makefile的编译,所以将cc文件改成cpp的。
13.Lars-DnsV0.1-获取route hosts信息测试
### 5.2 proto编译环境集成
 
         现在我们将`lars-dns`的Makefile加入针对proto文件的编译
 
 > Lars/lars_dns/Makefile
 
 ```makefile
 TARGET= bin/lars_dns
 CXX=g++
 CFLAGS=-g -O2 -Wall -Wno-deprecated
 
 BASE=../base
 BASE_H=$(BASE)/include
 
 PROTO = $(BASE)/proto
 PROTO_H = $(BASE)/proto
 
 LARS_REACTOR=../lars_reactor
 LARS_REACTOR_H =$(LARS_REACTOR)/include
 LARS_REACTOR_LIB=$(LARS_REACTOR)/lib  -llreactor
 
 MYSQL=$(BASE)/mysql-connector-c
 MYSQL_H=$(MYSQL)/include
 MYSQL_LIB=$(MYSQL)/lib/libmysqlclient.a
 
 OTHER_LIB = -lpthread -ldl -lprotobuf
 SRC= ./src
 INC= -I./include -I$(BASE_H) -I$(LARS_REACTOR_H) -I$(MYSQL_H) -I$(PROTO_H)
 
 LIB= $(MYSQL_LIB) -L$(LARS_REACTOR_LIB) $(OTHER_LIB) 
 
 
 OBJS = $(addsuffix .o, $(basename $(wildcard $(SRC)/*.cpp)))
 OBJS += $(PROTO)/lars.pb.o
 
 $(TARGET): $(OBJS)
         mkdir -p bin
         $(CXX) $(CFLAGS) -o $(TARGET) $(OBJS) $(INC) $(LIB)
 
 %.o: %.cpp
         $(CXX) $(CFLAGS) -c -o $@ $< $(INC) 
 
 .PHONY: clean
 
 clean:
         -rm -f src/*.o $(TARGET)
 ```
 
         添加了两个部分一个`OBJS`增添一个`lars.pb.o`的依赖,然后再`OTHER_LIB`增加`-lprotobuf`动态库的连接。
14.Lars-DnsV0.1-总结
### 5.3 实现Route获取
 
         接下来我们来实现针对`ID_GetRouteRequest`消息指令的业务处理.
 
 > lars_dns/src/dns_service.cpp
 
 ```c
 #include "lars_reactor.h"
 #include "dns_route.h"
 #include "lars.pb.h"
 
 void get_route(const char *data, uint32_t len, int msgid, net_connection *net_conn, void *user_data)
 {
     //1. 解析proto文件
     lars::GetRouteRequest req;
 
     req.ParseFromArray(data, len);
 
      
     //2. 得到modid 和 cmdid
     int modid, cmdid;
 
     modid = req.modid();
     cmdid = req.cmdid();
     
     //3. 根据modid/cmdid 获取 host信息
     host_set hosts = Route::instance()->get_hosts(modid, cmdid);
 
     //4. 将数据打包成protobuf
     lars::GetRouteResponse rsp;
 
     rsp.set_modid(modid);
     rsp.set_cmdid(cmdid);
     
     for (host_set_it it = hosts.begin(); it != hosts.end(); it ++) {
         uint64_t ip_port = *it;
         lars::HostInfo host;
         host.set_ip((uint32_t)(ip_port >> 32));
         host.set_port((int)(ip_port));
         rsp.add_host()->CopyFrom(host);
     }
     
     //5. 发送给客户端
     std::string responseString;
     rsp.SerializeToString(&responseString);
     net_conn->send_message(responseString.c_str(), responseString.size(), lars::ID_GetRouteResponse)    ;
 }
 
 int main(int argc, char **argv)
 {
     event_loop loop;
 
     //加载配置文件
     config_file::setPath("conf/lars_dns.conf");
     std::string ip = config_file::instance()->GetString("reactor", "ip", "0.0.0.0");
     short port = config_file::instance()->GetNumber("reactor", "port", 7778);
 
 
     //创建tcp服务器
     tcp_server *server = new tcp_server(&loop, ip.c_str(), port);
 
     //注册路由业务
     server->add_msg_router(lars::ID_GetRouteRequest, get_route);
 
     //开始事件监听    
     printf("lars dns service ....\n");
     loop.event_process();
 
     return 0;
 }
 ```
 
         需要给`Route`类,实现一个get_host()方法,来针对modid/cmdid取出对应的value
 
 > lars_dns/src/dns_route.cpp
 
 ```c
 //获取modid/cmdid对应的host信息
 host_set Route::get_hosts(int modid, int cmdid)
 {
     host_set hosts;     
 
     //组装key
     uint64_t key = ((uint64_t)modid << 32) + cmdid;
 
     pthread_rwlock_rdlock(&_map_lock);
     route_map_it it = _data_pointer->find(key);
     if (it != _data_pointer->end()) {
         //找到对应的ip + port对
         hosts = it->second;
     }
     pthread_rwlock_unlock(&_map_lock);
 
     return hosts;
 }
 ```
