【Lars教程目录】
Lars源代码
https://github.com/aceld/Lars
【Lars系统概述】
第1章-概述
第2章-项目目录构建
【Lars系统之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启动工具脚本
7) tcp_server端集成tcp_conn链接属性
现在我们已经把server端所创建的套接字包装成了tcp_conn类,那么我们就可以对他们进行一定的管理,比如限制最大的连接数量等等。
7.1 定义链接管理相关属性
lars_reactor/include/tcp_server.h
#pragma once
#include
#include "event_loop.h"
#include "tcp_conn.h"
class tcp_server
{
public:
//server的构造函数
tcp_server(event_loop* loop, const char *ip, uint16_t port);
//开始提供创建链接服务
void do_accept();
//链接对象释放的析构
~tcp_server();
private:
//基础信息
int _sockfd; //套接字
struct sockaddr_in _connaddr; //客户端链接地址
socklen_t _addrlen; //客户端链接地址长度
//event_loop epoll事件机制
event_loop* _loop;
//---- 客户端链接管理部分-----
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; //全部已经在线的连接信息
private:
//TODO
//从配置文件中读取
#define MAX_CONNS 2
static int _max_conns; //最大client链接个数
static int _curr_conns; //当前链接刻度
static pthread_mutex_t _conns_mutex; //保护_curr_conns刻度修改的锁
};
这里解释一下关键成员
conns
:这个是记录已经建立成功的全部链接的struct tcp_conn*数组。_curr_conns
:表示当前链接个数,其中increase_conn,decrease_conn,get_conn_num
三个方法分别是对链接个数增加、减少、和获取。_max_conns
:限制的最大链接数量。_conns_mutex
:保护_curr_conns的锁。
好了,我们首先首先将这些静态变量初始化,并且对函数见一些定义:
lars_reactor/src/tcp_server.cpp
#include
#include
#include
#include
#include
#include
#include /* See NOTES */
#include
#include
#include
#include "tcp_server.h"
#include "tcp_conn.h"
#include "reactor_buf.h"
// ==== 链接资源管理 ====
//全部已经在线的连接信息
tcp_conn ** tcp_server::conns = NULL;
//最大容量链接个数;
int tcp_server::_max_conns = 0;
//当前链接刻度
int tcp_server::_curr_conns = 0;
//保护_curr_conns刻度修改的锁
pthread_mutex_t tcp_server::_conns_mutex = PTHREAD_MUTEX_INITIALIZER;
//新增一个新建的连接
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);
}
//减少一个断开的连接
void tcp_server::decrease_conn(int connfd)
{
pthread_mutex_lock(&_conns_mutex);
conns[connfd] = NULL;
_curr_conns--;
pthread_mutex_unlock(&_conns_mutex);
}
//得到当前链接的刻度
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 创建链接集合初始化
我们在初始化tcp_server的同时也将conns
初始化.
lars_reactor/src/tcp_server.cpp
//server的构造函数
tcp_server::tcp_server(event_loop *loop, const char *ip, uint16_t port)
{
bzero(&_connaddr, sizeof(_connaddr));
//忽略一些信号 SIGHUP, SIGPIPE
//SIGPIPE:如果客户端关闭,服务端再次write就会产生
//SIGHUP:如果terminal关闭,会给当前进程发送该信号
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. 创建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可以多次监听,设置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 监听ip端口
if (listen(_sockfd, 500) == -1) {
fprintf(stderr, "listen error\n");
exit(1);
}
//5 将_sockfd添加到event_loop中
_loop = loop;
//6 ============= 创建链接管理 ===============
_max_conns = MAX_CONNS;
//创建链接信息数组
conns = new tcp_conn*[_max_conns+3];//3是因为stdin,stdout,stderr 已经被占用,再新开fd一定是从3开始,所以不加3就会栈溢出
if (conns == NULL) {
fprintf(stderr, "new conns[%d] error\n", _max_conns);
exit(1);
}
//===========================================
//7 注册_socket读事件-->accept处理
_loop->add_io_event(_sockfd, accept_callback, EPOLLIN, this);
}
这里有一段代码:
conns = new tcp_conn*[_max_conns+3];
其中3是因为我们已经默认打开的stdin,stdout,stderr3个文件描述符,因为我们在conns管理的形式类似一个hash的形式,每个tcp_conn的对应的数组下标就是当前tcp_conn的connfd文件描述符,所以我们应该开辟足够的大的宽度的数组来满足下标要求,所以要多开辟3个。虽然这里0,1,2下标在conns永远用不上。
7.3 创建链接判断链接数量
我们在tcp_server在accept成功之后,判断链接数量,如果满足需求将连接创建起来,并添加到conns中。
lars_reactor/src/tcp_server.cpp
//开始提供创建链接服务
void tcp_server::do_accept()
{
int connfd;
while(true) {
//accept与客户端创建链接
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 判断链接数量
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 对链接数量进行内部统计
在tcp_conn创建时,将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设置成非阻塞状态
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
//3. 将该链接的读事件让event_loop监控
_loop->add_io_event(_connfd, conn_rd_callback, EPOLLIN, this);
// ============================
//4 将该链接集成到对应的tcp_server中
tcp_server::increase_conn(_connfd, this);
// ============================
}
在tcp_conn销毁时,将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 关闭原始套接字
int fd = _connfd;
_connfd = -1;
close(fd);
}
7.5 完成Lars Reactor V0.5开发
server和client 应用app端的代码和v0.4一样,这里我们先修改tcp_server中的MAX_CONN宏为
lars_reacotr/include/tcp_server.h
#define MAX_CONNS 2
方便我们测试。这个这个数值是要在配置文件中可以配置的。
我们启动服务端,然后分别启动两个client可以正常连接。
当我们启动第三个就发现已经连接不上。然后server端会打出如下结果.
so many connections, max = 2
关于作者:
作者:Aceld(刘丹冰)
mail: [email protected]
github: https://github.com/aceld
原创书籍gitbook: http://legacy.gitbook.com/@aceld
原创声明:未经作者允许请勿转载, 如果转载请注明出处