226 lines
7.4 KiB
C
226 lines
7.4 KiB
C
#define DBG_TAG "[LFT_INET]"
|
||
|
||
#include "private/lftpd_inet.h"
|
||
#include "dbg_log.h"
|
||
#include "net_socket.h"
|
||
#include <string.h>
|
||
|
||
/*
|
||
* E-02 头阻塞修复(2026-09-22 M3 台架实证):
|
||
* netTask 是全部 socket 操作的唯一执行者;lftpd 原先用默认 30s 的**阻塞**
|
||
* net_recv 等待对端下一行/下一块数据 → 只要客户端连上却不发完整行,netTask
|
||
* 就被长期占用,其余任务(含远端显示)的网络操作整体冻结(实测一次空闲 FTP
|
||
* 登录让 LCDMEM 整帧等待 17.7s)。
|
||
* 修法:把等待切成 ≤LFTPD_NET_SLICE_MS 的片(每片 netTask 占用有界),
|
||
* 调用方累计总预算 LFTPD_TOTAL_BUDGET_MS —— **总超时语义与旧实现一致**,
|
||
* 但任何时刻都不再有"秒级冻结"。单片超时由 net_recv_to 返回 -2 区分(无 errno 竞态)。
|
||
* 分片长度即"其他任务最坏等待":实测 100ms 片下受压帧仍达 ~750ms(多片排队),
|
||
* 收到 5ms 后受压帧降到 ~10ms 量级(见 m3_data 修复后数据)。
|
||
*/
|
||
#define LFTPD_NET_SLICE_MS 5U
|
||
#define LFTPD_TOTAL_BUDGET_MS 30000U
|
||
|
||
int lftpd_inet_listen(int port)
|
||
{
|
||
int retry;
|
||
|
||
for (retry = 0; retry < 5; retry++) {
|
||
int s = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
if (s < 0) {
|
||
if (retry == 0) DBG_ERROR("create socket");
|
||
vTaskDelay(pdMS_TO_TICKS(500));
|
||
continue;
|
||
}
|
||
|
||
struct net_sockaddr_in addr;
|
||
addr.sin_family = NET_AF_INET;
|
||
addr.sin_port = net_htons((uint16_t)port);
|
||
addr.sin_addr.s_addr = 0;
|
||
|
||
if (net_bind(s, (struct net_sockaddr *)&addr, sizeof(addr)) < 0) {
|
||
DBG_ERROR("bind port %d", port);
|
||
net_close(s);
|
||
return -1;
|
||
}
|
||
|
||
if (net_listen(s) >= 0) {
|
||
return s;
|
||
}
|
||
|
||
net_close(s);
|
||
if (retry < 4) {
|
||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||
}
|
||
}
|
||
|
||
DBG_ERROR("listen failed after 5 retries (total 10s wait)");
|
||
return -1;
|
||
}
|
||
|
||
int lftpd_inet_get_socket_port(int socket)
|
||
{
|
||
struct net_sockaddr_in addr;
|
||
int addrlen = sizeof(addr);
|
||
|
||
if (net_getsockname(socket, (struct net_sockaddr *)&addr, &addrlen) < 0) {
|
||
DBG_ERROR("error getting socket port number");
|
||
return -1;
|
||
}
|
||
|
||
return (int)net_ntohs(addr.sin_port);
|
||
}
|
||
|
||
int lftpd_inet_accept(int socket)
|
||
{
|
||
/* 网络重建(net_init)后旧 Socket 已失效:检测到代次变化即返回 -1,
|
||
* 由 lftpd_start 外层循环重建监听(网络未就绪时其 listen 会重试) */
|
||
uint32_t gen = net_get_generation();
|
||
|
||
while (1) {
|
||
int s = net_accept(socket, NULL, NULL);
|
||
if (s >= 0) {
|
||
return s;
|
||
}
|
||
if (net_get_generation() != gen) {
|
||
DBG_ERROR("network re-init: abort accept, relisten");
|
||
return -1;
|
||
}
|
||
vTaskDelay(pdMS_TO_TICKS(100));
|
||
}
|
||
}
|
||
|
||
int lftpd_inet_accept_timeout(int socket, int timeout_ms)
|
||
{
|
||
int elapsed = 0;
|
||
|
||
while (elapsed < timeout_ms) {
|
||
int s = net_accept(socket, NULL, NULL);
|
||
if (s >= 0) {
|
||
return s;
|
||
}
|
||
/* 数据连接已被对端关闭时提前退出,不等待超时 */
|
||
net_sock_t *p_sock = net_get_sock(socket);
|
||
if (p_sock == NULL || p_sock->state != NET_SOCK_STATE_LISTENING) {
|
||
DBG_ERROR("data socket closed, accept aborted");
|
||
return -1;
|
||
}
|
||
vTaskDelay(pdMS_TO_TICKS(100));
|
||
elapsed += 100;
|
||
}
|
||
|
||
DBG_ERROR("accept timeout (%dms)", timeout_ms);
|
||
return -1;
|
||
}
|
||
|
||
int lftpd_inet_read_line(int socket, char *buffer, size_t buffer_len)
|
||
{
|
||
TickType_t t0 = xTaskGetTickCount();
|
||
|
||
memset(buffer, 0, buffer_len);
|
||
int total_read_len = 0;
|
||
|
||
while (total_read_len < (int)buffer_len) {
|
||
/* 分片有界读(E-02):单片 netTask 占用 ≤ LFTPD_NET_SLICE_MS */
|
||
int read_len = net_recv_to(socket,
|
||
buffer + total_read_len,
|
||
(int)(buffer_len - total_read_len - 1),
|
||
LFTPD_NET_SLICE_MS);
|
||
if (read_len == 0) {
|
||
return -1; /* 对端关闭 */
|
||
}
|
||
if (read_len == -2) {
|
||
/* 本片无数据:累计总预算,未超则继续等。
|
||
* **必须显式让出 CPU**:若某片因 net_poll 自身耗时而在首轮就返回 -2,
|
||
* 本任务(Normal)立即重排队会形成忙循环 → 饿死 BelowNormal 的 uiTask
|
||
* → 远端显示请求数秒无人拾取(2026-09-22 M3 实测 2.4~3.4s 周期尖刺)。
|
||
* vTaskDelay(1) 保证低优先级任务每毫秒都有运行机会。 */
|
||
if ((xTaskGetTickCount() - t0) >= pdMS_TO_TICKS(LFTPD_TOTAL_BUDGET_MS)) {
|
||
return -1; /* 总预算耗尽(语义同旧 30s 超时) */
|
||
}
|
||
vTaskDelay(pdMS_TO_TICKS(1));
|
||
continue;
|
||
}
|
||
if (read_len < 0) {
|
||
return read_len; /* 真错误 */
|
||
}
|
||
|
||
total_read_len += read_len;
|
||
|
||
char *p = strstr(buffer, "\r\n");
|
||
if (p != NULL) {
|
||
*p = '\0';
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
return -1;
|
||
}
|
||
|
||
int lftpd_inet_write_string(int socket, const char *message)
|
||
{
|
||
const char *p = message;
|
||
int length = (int)strlen(message);
|
||
|
||
while (length > 0) {
|
||
int write_len = net_send(socket, p, length, 0);
|
||
/* write_len == 0:发送超时且零进展(对端未消费数据);不判为致命则
|
||
* length 永不减少,将在此空转(任务死锁)。返回错误交会话层拆除
|
||
* 连接(lftpd.c handle_control_channel 的 cleanup 路径)。 */
|
||
if (write_len <= 0) {
|
||
DBG_ERROR("write error, ret=%d", write_len);
|
||
return -1;
|
||
}
|
||
p += write_len;
|
||
length -= write_len;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int lftpd_inet_write(int socket, const void *buf, int len)
|
||
{
|
||
const char *p = (const char *)buf;
|
||
int remaining = len;
|
||
|
||
while (remaining > 0) {
|
||
int n = net_send(socket, p, remaining, 0);
|
||
/* n == 0:发送超时且零进展(对端停滞/断链),判为致命:否则 remaining
|
||
* 永不减少 → 死循环。调用方(lftpd.c send_file)收到 < 0 后终止传输
|
||
* 并向上报 450 错误,会话保持可用。 */
|
||
if (n <= 0) {
|
||
DBG_ERROR("write error, ret=%d", n);
|
||
return -1;
|
||
}
|
||
p += n;
|
||
remaining -= n;
|
||
}
|
||
|
||
return len;
|
||
}
|
||
|
||
int lftpd_inet_read(int socket, void *buf, int len)
|
||
{
|
||
/* 分片有界读(E-02):总预算 30s 不变,但每片 netTask 占用 ≤ LFTPD_NET_SLICE_MS,
|
||
* 避免空闲数据连接(客户端开了 PASV 却不发数据)冻结全网 */
|
||
TickType_t t0 = xTaskGetTickCount();
|
||
|
||
for (;;) {
|
||
int n = net_recv_to(socket, buf, len, LFTPD_NET_SLICE_MS);
|
||
if (n >= 0) {
|
||
return n; /* >0 数据 / 0 EOF */
|
||
}
|
||
if (n == -1) {
|
||
return -1; /* 真错误 */
|
||
}
|
||
if ((xTaskGetTickCount() - t0) >= pdMS_TO_TICKS(LFTPD_TOTAL_BUDGET_MS)) {
|
||
return -1; /* 总预算耗尽(语义同旧 30s 超时) */
|
||
}
|
||
vTaskDelay(pdMS_TO_TICKS(1)); /* 让出 CPU:防空闲时忙循环饿死低优先级任务 */
|
||
}
|
||
}
|
||
|
||
int lftpd_inet_close(int socket)
|
||
{
|
||
return net_close(socket);
|
||
}
|