现在我们已经把server端所创建的套接字包装成了tcp_conn类,那么我们就可以对他们进行一定的管理,比如限制最大的连接数量等等。
7.1 定义链接管理相关属性
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的构造函数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 2static 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 <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"// ==== 链接资源管理 ====//全部已经在线的连接信息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_conntcp_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_connvoid 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
