【Lars教程目錄】
Lars源代碼
https://github.com/aceld/Lars
【Lars系統(tǒng)概述】
第1章-概述
第2章-項(xiàng)目目錄構(gòu)建
【Lars系統(tǒng)之Reactor模型服務(wù)器框架模塊】
第1章-項(xiàng)目結(jié)構(gòu)與V0.1雛形
第2章-內(nèi)存管理與Buffer封裝
第3章-事件觸發(fā)EventLoop
第4章-鏈接與消息封裝
第5章-Client客戶端模型
第6章-連接管理及限制
第7章-消息業(yè)務(wù)路由分發(fā)機(jī)制
第8章-鏈接創(chuàng)建/銷毀Hook機(jī)制
第9章-消息任務(wù)隊(duì)列與線程池
第10章-配置文件讀寫功能
第11章-udp服務(wù)與客戶端
第12章-數(shù)據(jù)傳輸協(xié)議protocol buffer
第13章-QPS性能測(cè)試
第14章-異步消息任務(wù)機(jī)制
第15章-鏈接屬性設(shè)置功能
【Lars系統(tǒng)之DNSService模塊】
第1章-Lars-dns簡(jiǎn)介
第2章-數(shù)據(jù)庫創(chuàng)建
第3章-項(xiàng)目目錄結(jié)構(gòu)及環(huán)境構(gòu)建
第4章-Route結(jié)構(gòu)的定義
第5章-獲取Route信息
第6章-Route訂閱模式
第7章-Backend Thread實(shí)時(shí)監(jiān)控
【Lars系統(tǒng)之Report Service模塊】
第1章-項(xiàng)目概述-數(shù)據(jù)表及proto3協(xié)議定義
第2章-獲取report上報(bào)數(shù)據(jù)
第3章-存儲(chǔ)線程池及消息隊(duì)列
【Lars系統(tǒng)之LoadBalance Agent模塊】
第1章-項(xiàng)目概述及構(gòu)建
第2章-主模塊業(yè)務(wù)結(jié)構(gòu)搭建
第3章-Report與Dns Client設(shè)計(jì)與實(shí)現(xiàn)
第4章-負(fù)載均衡模塊基礎(chǔ)設(shè)計(jì)
第5章-負(fù)載均衡獲取Host主機(jī)信息API
第6章-負(fù)載均衡上報(bào)Host主機(jī)信息API
第7章-過期窗口清理與過載超時(shí)(V0.5)
第8章-定期拉取最新路由信息(V0.6)
第9章-負(fù)載均衡獲取Route信息API(0.7)
第10章-API初始化接口(V0.8)
第11章-Lars Agent性能測(cè)試工具
第12章- Lars啟動(dòng)工具腳本
7) tcp_server端集成tcp_conn鏈接屬性
? 現(xiàn)在我們已經(jīng)把server端所創(chuàng)建的套接字包裝成了tcp_conn類喝滞,那么我們就可以對(duì)他們進(jìn)行一定的管理,比如限制最大的連接數(shù)量等等膏秫。
7.1 定義鏈接管理相關(guān)屬性
lars_reactor/include/tcp_server.h
#pragma once
#include <netinet/in.h>
#include "event_loop.h"
#include "tcp_conn.h"
class tcp_server
{
public:
//server的構(gòu)造函數(shù)
tcp_server(event_loop* loop, const char *ip, uint16_t port);
//開始提供創(chuàng)建鏈接服務(wù)
void do_accept();
//鏈接對(duì)象釋放的析構(gòu)
~tcp_server();
private:
//基礎(chǔ)信息
int _sockfd; //套接字
struct sockaddr_in _connaddr; //客戶端鏈接地址
socklen_t _addrlen; //客戶端鏈接地址長度
//event_loop epoll事件機(jī)制
event_loop* _loop;
//---- 客戶端鏈接管理部分-----
public:
static void increase_conn(int connfd, tcp_conn *conn); //新增一個(gè)新建的連接
static void decrease_conn(int connfd); //減少一個(gè)斷開的連接
static void get_conn_num(int *curr_conn); //得到當(dāng)前鏈接的刻度
static tcp_conn **conns; //全部已經(jīng)在線的連接信息
private:
//TODO
//從配置文件中讀取
#define MAX_CONNS 2
static int _max_conns; //最大client鏈接個(gè)數(shù)
static int _curr_conns; //當(dāng)前鏈接刻度
static pthread_mutex_t _conns_mutex; //保護(hù)_curr_conns刻度修改的鎖
};
這里解釋一下關(guān)鍵成員
conns
:這個(gè)是記錄已經(jīng)建立成功的全部鏈接的struct tcp_conn*數(shù)組右遭。_curr_conns
:表示當(dāng)前鏈接個(gè)數(shù),其中increase_conn,decrease_conn,get_conn_num
三個(gè)方法分別是對(duì)鏈接個(gè)數(shù)增加、減少缤削、和獲取窘哈。_max_conns
:限制的最大鏈接數(shù)量。_conns_mutex
:保護(hù)_curr_conns的鎖亭敢。
? 好了滚婉,我們首先首先將這些靜態(tài)變量初始化,并且對(duì)函數(shù)見一些定義:
lars_reactor/src/tcp_server.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include "tcp_server.h"
#include "tcp_conn.h"
#include "reactor_buf.h"
// ==== 鏈接資源管理 ====
//全部已經(jīng)在線的連接信息
tcp_conn ** tcp_server::conns = NULL;
//最大容量鏈接個(gè)數(shù);
int tcp_server::_max_conns = 0;
//當(dāng)前鏈接刻度
int tcp_server::_curr_conns = 0;
//保護(hù)_curr_conns刻度修改的鎖
pthread_mutex_t tcp_server::_conns_mutex = PTHREAD_MUTEX_INITIALIZER;
//新增一個(gè)新建的連接
void tcp_server::increase_conn(int connfd, tcp_conn *conn)
{
pthread_mutex_lock(&_conns_mutex);
conns[connfd] = conn;
_curr_conns++;
pthread_mutex_unlock(&_conns_mutex);
}
//減少一個(gè)斷開的連接
void tcp_server::decrease_conn(int connfd)
{
pthread_mutex_lock(&_conns_mutex);
conns[connfd] = NULL;
_curr_conns--;
pthread_mutex_unlock(&_conns_mutex);
}
//得到當(dāng)前鏈接的刻度
void tcp_server::get_conn_num(int *curr_conn)
{
pthread_mutex_lock(&_conns_mutex);
*curr_conn = _curr_conns;
pthread_mutex_unlock(&_conns_mutex);
}
//...
//...
//...
7.2 創(chuàng)建鏈接集合初始化
? 我們?cè)诔跏蓟痶cp_server的同時(shí)也將conns
初始化.
lars_reactor/src/tcp_server.cpp
//server的構(gòu)造函數(shù)
tcp_server::tcp_server(event_loop *loop, const char *ip, uint16_t port)
{
bzero(&_connaddr, sizeof(_connaddr));
//忽略一些信號(hào) SIGHUP, SIGPIPE
//SIGPIPE:如果客戶端關(guān)閉帅刀,服務(wù)端再次write就會(huì)產(chǎn)生
//SIGHUP:如果terminal關(guān)閉让腹,會(huì)給當(dāng)前進(jìn)程發(fā)送該信號(hào)
if (signal(SIGHUP, SIG_IGN) == SIG_ERR) {
fprintf(stderr, "signal ignore SIGHUP\n");
}
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
fprintf(stderr, "signal ignore SIGPIPE\n");
}
//1. 創(chuàng)建socket
_sockfd = socket(AF_INET, SOCK_STREAM /*| SOCK_NONBLOCK*/ | SOCK_CLOEXEC, IPPROTO_TCP);
if (_sockfd == -1) {
fprintf(stderr, "tcp_server::socket()\n");
exit(1);
}
//2 初始化地址
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
inet_aton(ip, &server_addr.sin_addr);
server_addr.sin_port = htons(port);
//2-1可以多次監(jiān)聽,設(shè)置REUSE屬性
int op = 1;
if (setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op)) < 0) {
fprintf(stderr, "setsocketopt SO_REUSEADDR\n");
}
//3 綁定端口
if (bind(_sockfd, (const struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
fprintf(stderr, "bind error\n");
exit(1);
}
//4 監(jiān)聽ip端口
if (listen(_sockfd, 500) == -1) {
fprintf(stderr, "listen error\n");
exit(1);
}
//5 將_sockfd添加到event_loop中
_loop = loop;
//6 ============= 創(chuàng)建鏈接管理 ===============
_max_conns = MAX_CONNS;
//創(chuàng)建鏈接信息數(shù)組
conns = new tcp_conn*[_max_conns+3];//3是因?yàn)閟tdin,stdout,stderr 已經(jīng)被占用扣溺,再新開fd一定是從3開始,所以不加3就會(huì)棧溢出
if (conns == NULL) {
fprintf(stderr, "new conns[%d] error\n", _max_conns);
exit(1);
}
//===========================================
//7 注冊(cè)_socket讀事件-->accept處理
_loop->add_io_event(_sockfd, accept_callback, EPOLLIN, this);
}
? 這里有一段代碼:
conns = new tcp_conn*[_max_conns+3];
? 其中3是因?yàn)槲覀円呀?jīng)默認(rèn)打開的stdin,stdout,stderr3個(gè)文件描述符骇窍,因?yàn)槲覀冊(cè)赾onns管理的形式類似一個(gè)hash的形式,每個(gè)tcp_conn的對(duì)應(yīng)的數(shù)組下標(biāo)就是當(dāng)前tcp_conn的connfd文件描述符锥余,所以我們應(yīng)該開辟足夠的大的寬度的數(shù)組來滿足下標(biāo)要求腹纳,所以要多開辟3個(gè)。雖然這里0,1,2下標(biāo)在conns永遠(yuǎn)用不上。
7.3 創(chuàng)建鏈接判斷鏈接數(shù)量
? 我們?cè)趖cp_server在accept成功之后嘲恍,判斷鏈接數(shù)量足画,如果滿足需求將連接創(chuàng)建起來,并添加到conns中蛔钙。
lars_reactor/src/tcp_server.cpp
//開始提供創(chuàng)建鏈接服務(wù)
void tcp_server::do_accept()
{
int connfd;
while(true) {
//accept與客戶端創(chuàng)建鏈接
printf("begin accept\n");
connfd = accept(_sockfd, (struct sockaddr*)&_connaddr, &_addrlen);
if (connfd == -1) {
if (errno == EINTR) {
fprintf(stderr, "accept errno=EINTR\n");
continue;
}
else if (errno == EMFILE) {
//建立鏈接過多锌云,資源不夠
fprintf(stderr, "accept errno=EMFILE\n");
}
else if (errno == EAGAIN) {
fprintf(stderr, "accept errno=EAGAIN\n");
break;
}
else {
fprintf(stderr, "accept error");
exit(1);
}
}
else {
// ===========================================
//accept succ!
int cur_conns;
get_conn_num(&cur_conns);
//1 判斷鏈接數(shù)量
if (cur_conns >= _max_conns) {
fprintf(stderr, "so many connections, max = %d\n", _max_conns);
close(connfd);
}
else {
tcp_conn *conn = new tcp_conn(connfd, _loop);
if (conn == NULL) {
fprintf(stderr, "new tcp_conn error\n");
exit(1);
}
printf("get new connection succ!\n");
}
// ===========================================
break;
}
}
}
7.4 對(duì)鏈接數(shù)量進(jìn)行內(nèi)部統(tǒng)計(jì)
在tcp_conn創(chuàng)建時(shí),將tcp_server中的conns增加吁脱。
lars_reactor/src/tcp_conn.cpp
//初始化tcp_conn
tcp_conn::tcp_conn(int connfd, event_loop *loop)
{
_connfd = connfd;
_loop = loop;
//1. 將connfd設(shè)置成非阻塞狀態(tài)
int flag = fcntl(_connfd, F_GETFL, 0);
fcntl(_connfd, F_SETFL, O_NONBLOCK|flag);
//2. 設(shè)置TCP_NODELAY禁止做讀寫緩存桑涎,降低小包延遲
int op = 1;
setsockopt(_connfd, IPPROTO_TCP, TCP_NODELAY, &op, sizeof(op));//need netinet/in.h netinet/tcp.h
//3. 將該鏈接的讀事件讓event_loop監(jiān)控
_loop->add_io_event(_connfd, conn_rd_callback, EPOLLIN, this);
// ============================
//4 將該鏈接集成到對(duì)應(yīng)的tcp_server中
tcp_server::increase_conn(_connfd, this);
// ============================
}
在tcp_conn銷毀時(shí),將tcp_server中的conns減少兼贡。
lars_reactor/src/tcp_conn.cpp
//銷毀tcp_conn
void tcp_conn::clean_conn()
{
//鏈接清理工作
//1 將該鏈接從tcp_server摘除掉
tcp_server::decrease_conn(_connfd);
//2 將該鏈接從event_loop中摘除
_loop->del_io_event(_connfd);
//3 buf清空
ibuf.clear();
obuf.clear();
//4 關(guān)閉原始套接字
int fd = _connfd;
_connfd = -1;
close(fd);
}
7.5 完成Lars Reactor V0.5開發(fā)
? server和client 應(yīng)用app端的代碼和v0.4一樣攻冷,這里我們先修改tcp_server中的MAX_CONN宏為
lars_reacotr/include/tcp_server.h
#define MAX_CONNS 2
方便我們測(cè)試。這個(gè)這個(gè)數(shù)值是要在配置文件中可以配置的遍希。
我們啟動(dòng)服務(wù)端等曼,然后分別啟動(dòng)兩個(gè)client可以正常連接。
當(dāng)我們啟動(dòng)第三個(gè)就發(fā)現(xiàn)已經(jīng)連接不上凿蒜。然后server端會(huì)打出如下結(jié)果.
so many connections, max = 2
關(guān)于作者:
作者:Aceld(劉丹冰)
mail: danbing.at@gmail.com
github: https://github.com/aceld
原創(chuàng)書籍gitbook: http://legacy.gitbook.com/@aceld
原創(chuàng)聲明:未經(jīng)作者允許請(qǐng)勿轉(zhuǎn)載, 如果轉(zhuǎn)載請(qǐng)注明出處