首先看到报错:
2025-06-16 14:56:27.855 ERROR listenfd=6 accept error: Too many open files:24 [nio.c:172:nio_accept]
2025-06-16 14:56:27.855 ERROR listenfd=6 accept error: Too many open files:24 [nio.c:172:nio_accept]
2025-06-16 14:56:27.856 INFO [2087-2087][127.0.0.1:39090][POST /api/BackEndVersion]=>[200 OK] [HttpHandler.cpp:326:onMessageComplete]
2025-06-16 14:56:27.856 ERROR listenfd=6 accept error: Too many open files:24 [nio.c:172:nio_accept]
2025-06-16 14:56:27.864 INFO [2087-2087][127.0.0.1:39106][POST /api/indoorStatus]=>[200 OK] [HttpHandler.cpp:326:onMessageComplete]
2025-06-16 14:56:27.937 ERROR listenfd=6 accept error: Too many open files:24 [nio.c:172:nio_accept]
2025-06-16 14:56:27.938 INFO [2087-2087][127.0.0.1:39122][POST /api/preview]=>[200 OK] [HttpHandler.cpp:326:onMessageComplete]
2025-06-16 14:56:27.938 ERROR connect 172.16.23.27:80 failed! [HttpClient.cpp:216:http_client_connect]
2025-06-16 14:56:27.938 ERROR connect 172.16.23.27:80 failed! [HttpClient.cpp:216:http_client_connect]
2025-06-16 14:56:27.939 INFO [2087-2087][127.0.0.1:39118][POST /api/panTilt/otaCheck]=>[200 OK] [HttpHandler.cpp:326:onMessageComplete]
拆解:
-
listenfd=6
:说明监听 socket 的文件描述符是 6 -
accept error
:说明在调用accept()
时出错了 -
Too many open files:24
:系统返回错误号24
,表示文件描述符已达上限 -
nio.c:172:nio_accept
:出错的源码位置
查看进程:
进入文件描述符的目录
sudo ls /proc/2640/fd
sudo ls /proc/2640/fd/6
结果返回:
说明这里开始报错。
理解:fd6 是监听 socket,但不是错误的根源,fd6是受害者
点 | 是否 |
---|---|
listenfd=6 是出错的 FD 吗? | ❌ 否(它是监听 socket,一直存在) |
是谁导致了错误? | ✅ 是其他 FD 泄漏或资源未释放导致系统 FD 耗尽 |
错误是不是 accept 本身? | ✅ 是 accept() 创建新连接时无法分配新的 FD 引发错误 |
复习一下文件描述符的使用场景:
场景 | 示例函数 | fd 用法 |
---|---|---|
文件读写 | open() , read() , write() , close() | 文件用 fd 表示 |
Socket 编程 | socket() , accept() , send() , recv() | 网络连接也是 fd |
管道 | pipe() , dup() , fork() | 管道的两端是 fd |
事件监听 | select() , poll() , epoll() | 所有监听的对象用 fd 表示 |
标准输入输出 | stdin=0 , stdout=1 , stderr=2 | 它们本质就是 fd |
文件重定向 | dup2(1, fd) | 改变某个 fd 的输出位置 |
查看一下fd常见信息
/proc/2640$ sudo ls -l ./fd |wc -l
/proc/2640$ sudo lsof -p 2640
返回:
............
Laserbird 2640 root mem REG 259,1 1188160 4857522 /usr/lib/aarch64-linux-gnu/libsqlite3.so.0.8.6
Laserbird 2640 root mem REG 259,1 146432 4862729 /usr/lib/aarch64-linux-gnu/ld-2.31.so
Laserbird 2640 root 0r CHR 1,3 0t0 6 /dev/null
Laserbird 2640 root 1w REG 259,1 42488 15088371 /var/log/laserbird.log
Laserbird 2640 root 2w REG 259,1 42488 15088371 /var/log/laserbird.log
Laserbird 2640 root 3w REG 259,1 114668 19 /libhv.20250617.log
Laserbird 2640 root 4u unix 0xffff3d4fc8bd3c00 0t0 57583 /tmp/unix_socket_path type=STREAM
Laserbird 2640 root 6u REG 259,1 16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root 7u IPv4 54674 0t0 TCP *:8089 (LISTEN)
Laserbird 2640 root 8u a_inode 0,13 0 8947 [eventpoll]
Laserbird 2640 root 9u a_inode 0,13 0 8947 [eventfd]
Laserbird 2640 root 10u REG 259,1 16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root 11u REG 259,1 16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root 12u REG 259,1 16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root 13u REG 259,1 16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root 14u REG 259,1 16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root 15u REG 259,1 16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root 16u REG 259,1 16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root 17u REG 259,1 16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root 18u REG 259,1 16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root 19u REG 259,1 16384 10769944 /opt/sqlite3/device_status.db
问题:大量重复打开了 /opt/sqlite3/device_status.db
好的,去找程序寻找哪里打开了这么多数据库没关。
找到了罪魁祸首:
成功打开了数据库,已经分配了fd,但由于正忙,直接goto到info_reopen,导致fd爆炸。
在 sqlite3_prepare_v2()
返回 SQLITE_BUSY
时:
-
连接
db
已经成功打开,文件描述符也分配了。 -
然而没有
sqlite3_close(db)
就跳到了info_reopen
。 -
每次
goto
后都再次sqlite3_open
,造成新的 fd 分配。 -
导致 文件描述符泄漏,最终 fd 用尽(达到
ulimit -n
),数据库彻底打不开。
//更新DEVICE_STATUS的值
bool Database::updateValueByColumn(const char* column, int newValue, const char* attribute) {
sqlite3* db;
sqlite3_stmt* stmt;
info_reopen:
int rc = sqlite3_open(dbName, &db);
if (rc) {
hloge("Can't open database: %s",sqlite3_errmsg(db));
sqlite3_close(db);
return false;
}
std::string sql = "UPDATE DEVICE_STATUS SET " + std::string(column) + " = ? WHERE ATTRIBUTE = ?";
rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
if (rc != SQLITE_OK) {
if(rc == SQLITE_BUSY){ // 如果失败原因是数据库忙
sleep(0.5); // 等待 0.5 秒
goto info_reopen; // 重新尝试打开数据库
}
hloge("Failed to prepare statement: %s",sqlite3_errmsg(db));
sqlite3_close(db);
return false;
}
sqlite3_bind_int(stmt, 1, newValue);
sqlite3_bind_text(stmt, 2, attribute, -1, SQLITE_STATIC);
while(rc = sqlite3_step(stmt) !=SQLITE_DONE){
if(rc == SQLITE_BUSY || rc == 1){
//hloge("sqlite was busy\n");
sleep(0.2);
}
else{
sqlite3_finalize(stmt);
sqlite3_close(db);
std::cout << "rc:"<<rc<<std::endl; //这里容易失败
hloge("update error\n");
perror("error update");
return false;
}
}
sqlite3_finalize(stmt);
sqlite3_close(db);
return true;
}