ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 7) tcp_server端集成tcp_conn链接属性 ​ 现在我们已经把server端所创建的套接字包装成了tcp_conn类,那么我们就可以对他们进行一定的管理,比如限制最大的连接数量等等。 ### 7.1 定义链接管理相关属性 > lars_reactor/include/tcp_server.h ```c #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 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 ```c #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 ```c //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); } ``` ​ 这里有一段代码: ```c 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 ```c //开始提供创建链接服务 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 ```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 //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 ```c //销毁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 ```c #define MAX_CONNS 2 ``` 方便我们测试。这个这个数值是要在配置文件中可以配置的。 我们启动服务端,然后分别启动两个client可以正常连接。 当我们启动第三个就发现已经连接不上。然后server端会打出如下结果. ```bash so many connections, max = 2 ``` --- ### 关于作者: 作者:`Aceld(刘丹冰)` mail: [danbing.at@gmail.com](mailto:danbing.at@gmail.com) github: [https://github.com/aceld](https://github.com/aceld) 原创书籍: [https://www.kancloud.cn/@aceld](https://www.kancloud.cn/@aceld) ![](https://img.kancloud.cn/b0/d1/b0d11a21ba62e96aef1c11d5bfff2cf8_227x227.jpg) >**原创声明:未经作者允许请勿转载, 如果转载请注明出处**