## 9) 鏈接創建/銷毀Hook機制
? 下面我們來給鏈接注冊兩個hook節點的函數,即服務端有新的客戶端鏈接創建之后用戶可以注冊一個回調,有客戶端斷開鏈接的回調。還有客戶端在成功與服務端創建鏈接之后創建的回調,和客戶端與服務端斷開鏈接之前的回調。
### 9.1 tcp_server服務端添加鏈接Hook函數
#### A. 定義Hook函數原型
> lars_reactor/include/net_connection.h
```c
#pragma once
/*
*
* 網絡通信的抽象類,任何需要進行收發消息的模塊,都可以實現該類
*
* */
class net_connection
{
public:
net_connection() {}
//發送消息的接口
virtual int send_message(const char *data, int datalen, int msgid) = 0;
};
//創建鏈接/銷毀鏈接 要觸發的 回調函數類型
typedef void (*conn_callback)(net_connection *conn, void *args);
```
#### B. tcp_server定義相關hook函數的屬性
> lars_reactor/include/tcp_server.h
```c
#pragma once
#include <netinet/in.h>
#include "event_loop.h"
#include "tcp_conn.h"
#include "message.h"
class tcp_server
{
public:
//server的構造函數
tcp_server(event_loop* loop, const char *ip, uint16_t port);
//開始提供創建鏈接服務
void do_accept();
//鏈接對象釋放的析構
~tcp_server();
//注冊消息路由回調函數
void add_msg_router(int msgid, msg_callback *cb, void *user_data = NULL) {
router.register_msg_router(msgid, cb, user_data);
}
private:
//基礎信息
int _sockfd; //套接字
struct sockaddr_in _connaddr; //客戶端鏈接地址
socklen_t _addrlen; //客戶端鏈接地址長度
//event_loop epoll事件機制
event_loop* _loop;
public:
//---- 消息分發路由 ----
static msg_router router;
//---- 客戶端鏈接管理部分-----
public:
static void increase_conn(int connfd, tcp_conn *conn); //新增一個新建的連接
static void decrease_conn(int connfd); //減少一個斷開的連接
static void get_conn_num(int *curr_conn); //得到當前鏈接的刻度
static tcp_conn **conns; //全部已經在線的連接信息
// ------- 創建鏈接/銷毀鏈接 Hook 部分 -----
//設置鏈接的創建hook函數
static void set_conn_start(conn_callback cb, void *args = NULL) {
conn_start_cb = cb;
conn_start_cb_args = args;
}
//設置鏈接的銷毀hook函數
static void set_conn_close(conn_callback cb, void *args = NULL) {
conn_close_cb = cb;
conn_close_cb_args = args;
}
//創建鏈接之后要觸發的 回調函數
static conn_callback conn_start_cb;
static void *conn_start_cb_args;
//銷毀鏈接之前要觸發的 回調函數
static conn_callback conn_close_cb;
static void *conn_close_cb_args;
private:
//TODO
//從配置文件中讀取
#define MAX_CONNS 10000
static int _max_conns; //最大client鏈接個數
static int _curr_conns; //當前鏈接刻度
static pthread_mutex_t _conns_mutex; //保護_curr_conns刻度修改的鎖
};
```
#### C. tcp_conn在連接創建/銷毀調用Hook函數
```c
//初始化tcp_conn
tcp_conn::tcp_conn(int connfd, event_loop *loop)
{
_connfd = connfd;
_loop = loop;
//1. 將connfd設置成非阻塞狀態
int flag = fcntl(_connfd, F_GETFL, 0);
fcntl(_connfd, F_SETFL, O_NONBLOCK|flag);
//2. 設置TCP_NODELAY禁止做讀寫緩存,降低小包延遲
int op = 1;
setsockopt(_connfd, IPPROTO_TCP, TCP_NODELAY, &op, sizeof(op));//need netinet/in.h netinet/tcp.h
//2.5 如果用戶注冊了鏈接建立Hook 則調用
if (tcp_server::conn_start_cb) {
tcp_server::conn_start_cb(this, tcp_server::conn_start_cb_args);
}
//3. 將該鏈接的讀事件讓event_loop監控
_loop->add_io_event(_connfd, conn_rd_callback, EPOLLIN, this);
//4 將該鏈接集成到對應的tcp_server中
tcp_server::increase_conn(_connfd, this);
}
//...
//...
//銷毀tcp_conn
void tcp_conn::clean_conn()
{
// 如果注冊了鏈接銷毀Hook函數,則調用
if (tcp_server::conn_close_cb) {
tcp_server::conn_close_cb(this, tcp_server::conn_close_cb_args);
}
//鏈接清理工作
//1 將該鏈接從tcp_server摘除掉
tcp_server::decrease_conn(_connfd);
//2 將該鏈接從event_loop中摘除
_loop->del_io_event(_connfd);
//3 buf清空
ibuf.clear();
obuf.clear();
//4 關閉原始套接字
int fd = _connfd;
_connfd = -1;
close(fd);
}
```
### 9.2 tcp_client客戶端添加鏈接Hook函數
#### A. tcp_client添加Hook屬性
> lars_reactor/include/tcp_client.h
```c
#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "io_buf.h"
#include "event_loop.h"
#include "message.h"
#include "net_connection.h"
class tcp_client : public net_connection
{
public:
//初始化客戶端套接字
tcp_client(event_loop *loop, const char *ip, unsigned short port, const char *name);
//發送message方法
int send_message(const char *data, int msglen, int msgid);
//創建鏈接
void do_connect();
//處理讀業務
int do_read();
//處理寫業務
int do_write();
//釋放鏈接資源
void clean_conn();
~tcp_client();
//設置業務處理回調函數
//void set_msg_callback(msg_callback *msg_cb)
//{
//this->_msg_callback = msg_cb;
//}
//注冊消息路由回調函數
void add_msg_router(int msgid, msg_callback *cb, void *user_data = NULL) {
_router.register_msg_router(msgid, cb, user_data);
}
//----- 鏈接創建/銷毀回調Hook ----
//設置鏈接的創建hook函數
void set_conn_start(conn_callback cb, void *args = NULL)
{
_conn_start_cb = cb;
_conn_start_cb_args = args;
}
//設置鏈接的銷毀hook函數
void set_conn_close(conn_callback cb, void *args = NULL) {
_conn_close_cb = cb;
_conn_close_cb_args = args;
}
//創建鏈接之后要觸發的 回調函數
conn_callback _conn_start_cb;
void * _conn_start_cb_args;
//銷毀鏈接之前要觸發的 回調函數
conn_callback _conn_close_cb;
void * _conn_close_cb_args;
// ---------------------------------
bool connected; //鏈接是否創建成功
//server端地址
struct sockaddr_in _server_addr;
io_buf _obuf;
io_buf _ibuf;
private:
int _sockfd;
socklen_t _addrlen;
//處理消息的分發路由
msg_router _router;
//msg_callback *_msg_callback; //單路由模式去掉
//客戶端的事件處理機制
event_loop* _loop;
//當前客戶端的名稱 用戶記錄日志
const char *_name;
};
```
#### B. tcp_client在創建/銷毀調用Hook
> lars_reactor/src/tcp_client.c
```c
//創建鏈接
void tcp_client::do_connect()
{
// ...
// ...
int ret = connect(_sockfd, (const struct sockaddr*)&_server_addr, _addrlen);
if (ret == 0) {
//鏈接創建成功
connected = true;
//調用開發者客戶端注冊的創建鏈接之后的hook函數
if (_conn_start_cb != NULL) {
_conn_start_cb(this, _conn_start_cb_args);
}
// ...
// ...
}
}
//判斷鏈接是否是創建鏈接,主要是針對非阻塞socket 返回EINPROGRESS錯誤
static void connection_delay(event_loop *loop, int fd, void *args)
{
tcp_client *cli = (tcp_client*)args;
loop->del_io_event(fd);
int result = 0;
socklen_t result_len = sizeof(result);
getsockopt(fd, SOL_SOCKET, SO_ERROR, &result, &result_len);
if (result == 0) {
//鏈接是建立成功的
cli->connected = true;
printf("connect %s:%d succ!\n", inet_ntoa(cli->_server_addr.sin_addr), ntohs(cli->_server_addr.sin_port));
//調用開發者注冊的創建鏈接Hook函數
if (cli->_conn_start_cb != NULL) {
cli->_conn_start_cb(cli, cli->_conn_start_cb_args);
}
// ....
// ...
}
}
//釋放鏈接資源,重置連接
void tcp_client::clean_conn()
{
if (_sockfd != -1) {
printf("clean conn, del socket!\n");
_loop->del_io_event(_sockfd);
close(_sockfd);
}
connected = false;
//調用開發者注冊的銷毀鏈接之前觸發的Hook
if (_conn_close_cb != NULL) {
_conn_close_cb(this, _conn_close_cb_args);
}
//重新連接
this->do_connect();
}
```
### 9.3 完成Lars Reactor V0.7開發
> server.cpp
```c
#include "tcp_server.h"
#include <string.h>
//回顯業務的回調函數
void callback_busi(const char *data, uint32_t len, int msgid, net_connection *conn, void *user_data)
{
printf("callback_busi ...\n");
//直接回顯
conn->send_message(data, len, msgid);
}
//打印信息回調函數
void print_busi(const char *data, uint32_t len, int msgid, net_connection *conn, void *user_data)
{
printf("recv client: [%s]\n", data);
printf("msgid: [%d]\n", msgid);
printf("len: [%d]\n", len);
}
//新客戶端創建的回調
void on_client_build(net_connection *conn, void *args)
{
int msgid = 101;
const char *msg = "welcome! you online..";
conn->send_message(msg, strlen(msg), msgid);
}
//客戶端銷毀的回調
void on_client_lost(net_connection *conn, void *args)
{
printf("connection is lost !\n");
}
int main()
{
event_loop loop;
tcp_server server(&loop, "127.0.0.1", 7777);
//注冊消息業務路由
server.add_msg_router(1, callback_busi);
server.add_msg_router(2, print_busi);
//注冊鏈接hook回調
server.set_conn_start(on_client_build);
server.set_conn_close(on_client_lost);
loop.event_process();
return 0;
}
```
> client.cpp
```c
#include "tcp_client.h"
#include <stdio.h>
#include <string.h>
//客戶端業務
void busi(const char *data, uint32_t len, int msgid, net_connection *conn, void *user_data)
{
//得到服務端回執的數據
printf("recv server: [%s]\n", data);
printf("msgid: [%d]\n", msgid);
printf("len: [%d]\n", len);
}
//客戶端銷毀的回調
void on_client_build(net_connection *conn, void *args)
{
int msgid = 1;
const char *msg = "Hello Lars!";
conn->send_message(msg, strlen(msg), msgid);
}
//客戶端銷毀的回調
void on_client_lost(net_connection *conn, void *args)
{
printf("on_client_lost...\n");
printf("Client is lost!\n");
}
int main()
{
event_loop loop;
//創建tcp客戶端
tcp_client client(&loop, "127.0.0.1", 7777, "clientv0.6");
//注冊消息路由業務
client.add_msg_router(1, busi);
client.add_msg_router(101, busi);
//設置hook函數
client.set_conn_start(on_client_build);
client.set_conn_close(on_client_lost);
//開啟事件監聽
loop.event_process();
return 0;
}
```
運行結果
服務端
```bash
$ ./server
msg_router init...
add msg cb msgid = 1
add msg cb msgid = 2
begin accept
get new connection succ!
read data: Hello Lars!
call msgid = 1
callback_busi ...
=======
connection closed by peer
connection is lost !
```
客戶端:
```bash
$ ./client
msg_router init...
do_connect EINPROGRESS
add msg cb msgid = 1
add msg cb msgid = 101
connect 127.0.0.1:7777 succ!
do write over, del EPOLLOUT
call msgid = 101
recv server: [welcome! you online..]
msgid: [101]
len: [21]
=======
call msgid = 1
recv server: [Hello Lars!]
msgid: [1]
len: [11]
=======
^C
```
? 這樣我們的成功的將hook機制加入進去了。
---
### 關于作者:
作者:`Aceld(劉丹冰)`
mail: [danbing.at@gmail.com](mailto:danbing.at@gmail.com)
github: [https://github.com/aceld](https://github.com/aceld)
原創書籍: [http://www.hmoore.net/@aceld](http://www.hmoore.net/@aceld)

>**原創聲明:未經作者允許請勿轉載, 如果轉載請注明出處**
- 一、Lars系統概述
- 第1章-概述
- 第2章-項目目錄構建
- 二、Reactor模型服務器框架
- 第1章-項目結構與V0.1雛形
- 第2章-內存管理與Buffer封裝
- 第3章-事件觸發EventLoop
- 第4章-鏈接與消息封裝
- 第5章-Client客戶端模型
- 第6章-連接管理及限制
- 第7章-消息業務路由分發機制
- 第8章-鏈接創建/銷毀Hook機制
- 第9章-消息任務隊列與線程池
- 第10章-配置文件讀寫功能
- 第11章-udp服務與客戶端
- 第12章-數據傳輸協議protocol buffer
- 第13章-QPS性能測試
- 第14章-異步消息任務機制
- 第15章-鏈接屬性設置功能
- 三、Lars系統之DNSService
- 第1章-Lars-dns簡介
- 第2章-數據庫創建
- 第3章-項目目錄結構及環境構建
- 第4章-Route結構的定義
- 第5章-獲取Route信息
- 第6章-Route訂閱模式
- 第7章-Backend Thread實時監控
- 四、Lars系統之Report Service
- 第1章-項目概述-數據表及proto3協議定義
- 第2章-獲取report上報數據
- 第3章-存儲線程池及消息隊列
- 五、Lars系統之LoadBalance Agent
- 第1章-項目概述及構建
- 第2章-主模塊業務結構搭建
- 第3章-Report與Dns Client設計與實現
- 第4章-負載均衡模塊基礎設計
- 第5章-負載均衡獲取Host主機信息API
- 第6章-負載均衡上報Host主機信息API
- 第7章-過期窗口清理與過載超時(V0.5)
- 第8章-定期拉取最新路由信息(V0.6)
- 第9章-負載均衡獲取Route信息API(0.7)
- 第10章-API初始化接口(V0.8)
- 第11章-Lars Agent性能測試工具
- 第12章- Lars啟動工具腳本