Skip to content

Commit

Permalink
✨ 170 TCP 超时重传
Browse files Browse the repository at this point in the history
  • Loading branch information
StevenBaby committed Aug 13, 2023
1 parent 61f7365 commit e3a229e
Show file tree
Hide file tree
Showing 12 changed files with 333 additions and 17 deletions.
64 changes: 64 additions & 0 deletions docs/14 网络协议/170 TCP 超时重传.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# TCP 超时重传

## 重传超时

TCP 超时和重传的基础是怎样根据给定连接的 往返时间 RTT(Round Trip Time) 设置 RTO (Retransmission TimeOut)重传超时,以取的对特定连接比较合理的重传时间。

### 关键术语

- RTT: Round Trip Time 往返时间
- SRTT: Smoothed RTT 平滑的往返时间,(加权平均值)
- RTO: Retransmission TimeOut 重传超时
- RTTVAR: RTT 的残差(估计值)

### 经典方法

$$
\text{SRTT} = \alpha(\text{SRTT}) + (1 - \alpha)(\text{RTT})
$$

每次有新的 RTT 时,需要更新 SRTT,其中 $\alpha \in [0.8, 0.9]$,这种方法叫做指数加权移动平均 (Exponentially Weighted Moving Average EWMA),或者 低通滤波器(Low-Pass Filter),实现起来比较简单;

RFC793 推荐如下公式计算 RTO:

$$
\text{RTO} = \min(\text{ubound}, \max(\text{lbound}, \beta(\text{SRTT})))
$$

- lbound:RTO 下界,最小值;
- ubound:RTO 上界,最大值;
- $\beta$:时延离散因子,推荐值 1.3 ~ 2.0 (可以理解为 RTT 的方差);

### 标准方法

标准方法定义在 RFC6298;

$$
\begin{aligned}
\text{SRTT} &= (1 - g)(\text{SRTT}) + g(\text{RTT}) \\
\text{RTTVAR} &= (1 - h)(\text{RTTVAR}) + h(|\text{RTT} - \text{SRTT}|) \\
\text{RTO} &= \text{SRTT} + 4(\text{RTTVAR})
\end{aligned}
$$

经过简单的计算,可以的到如下的公式,更容易用于程序设计;

$$
\begin{aligned}
E &= \text{RTT} - \text{SRTT} \\
\text{SRTT} &= \text{SRTT} + gE \\
\text{RTTVAR} &= \text{RTTVAR} + h(|E| - \text{RTTVAR}) \\
\text{RTO} &= \text{SRTT} + 4(\text{RTTVAR})
\end{aligned}
$$

其中 $\displaystyle g = {1 \over 8}$,$\displaystyle h = {1 \over 4}$;

## 指数退避

每次重传间隔时间加倍称为二进制指数退避(binary exponential backoff);

## 参考

- TCP/IP 详解 卷1:协议
- TCP/IP 详解 卷2:实现
101 changes: 101 additions & 0 deletions src/builtin/tcp_surge.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#include <onix/types.h>
#include <onix/stdio.h>
#include <onix/syscall.h>
#include <onix/string.h>
#include <onix/fs.h>
#include <onix/net.h>

#define BUFLEN 4096

static char tx_buf[BUFLEN];
static char rx_buf[BUFLEN];

int main(int argc, char const *argv[])
{
sockaddr_in_t addr;

int fd = socket(AF_INET, SOCK_STREAM, PROTO_TCP);
if (fd < EOK)
{
printf("create client socket failure...\n");
return fd;
}

inet_aton("192.168.111.33", addr.addr);
addr.family = AF_INET;
addr.port = htons((u16)time());

int ret = bind(fd, (sockaddr_t *)&addr, sizeof(sockaddr_in_t));
printf("socket bind %d\n", ret);
if (ret < EOK)
goto rollback;

int val = 1;
ret = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, 4);
printf("socket keepalive %d\n", ret);
if (ret < EOK)
goto rollback;

val = 1;
ret = setsockopt(fd, SOL_SOCKET, SO_TCP_NODELAY, &val, 4);
printf("socket nodelay %d\n", ret);
if (ret < EOK)
goto rollback;

inet_aton("192.168.111.1", addr.addr);
addr.family = AF_INET;
addr.port = htons(7777);
ret = connect(fd, (sockaddr_t *)&addr, sizeof(sockaddr_in_t));
printf("socket connect %d\n", ret);

memset(tx_buf, 'A', sizeof(tx_buf));

u32 send_timing = time();
u32 recv_timing = time();
u32 send_count = 0;
u32 recv_count = 0;

pid_t pid = fork();

while (true)
{
if (pid)
{
send_count++;
ret = send(fd, tx_buf, sizeof(tx_buf), 0);
// printf("message sent %d...\n", ret);
if (ret < EOK)
goto rollback;

u32 now = time();
int offset = now - send_timing;
send_timing = now;
if (offset > 0)
{
printf("send speed: %dKB/s\n", send_count * 4 / offset);
send_count = 0;
}
}
else
{
recv_count++;
ret = recv(fd, rx_buf, BUFLEN, 0);
// printf("message recv %d...\n", ret);
if (ret < EOK)
goto rollback;

u32 now = time();
int offset = now - recv_timing;
recv_timing = now;
if (offset > 0)
{
printf("recv speed: %dKB/s\n", recv_count * 4 / offset);
recv_count = 0;
}
}
}

rollback:
if (fd > 0)
close(fd);
}
41 changes: 31 additions & 10 deletions src/include/onix/net/tcp.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,27 @@ enum

enum
{
TCP_TO_SYN = 5 * TCP_SLOWHZ, // 建立连接超时
TCP_TO_REXMIT = 2 * TCP_SLOWHZ, // 重传超时
TCP_TO_FIN_WAIT2 = 10 * 60 * TCP_SLOWHZ, // FIN_WAIT2 超时
TCP_TO_TIMEWAIT = 4 * 60 * TCP_SLOWHZ, // TIMEWAIT 超时
TCP_TO_PERSMIN = 5 * TCP_SLOWHZ, // 最小持续超时
TCP_TO_PERMAX = 60 * TCP_SLOWHZ, // 最大持续超时
TCP_TO_KEEP_IDLE = 2 * TCP_SLOWHZ, // 保活启动
TCP_TO_KEEP_INTERVAL = 2 * TCP_SLOWHZ, // 保活测试间隔
TCP_TO_KEEPCNT = 8, // 保活测试次数
TCP_MAXRXTCNT = 12, // 最大重传次数
TCP_TO_SYN = 75 * TCP_SLOWHZ, // 建立连接超时
TCP_TO_REXMIT = 2 * TCP_SLOWHZ, // 重传超时
TCP_TO_FIN_WAIT2 = 10 * 60 * TCP_SLOWHZ, // FIN_WAIT2 超时
TCP_TO_TIMEWAIT = 10 * TCP_SLOWHZ, // TIMEWAIT 超时
TCP_TO_PERSMIN = 5 * TCP_SLOWHZ, // 最小持续超时
TCP_TO_PERMAX = 60 * TCP_SLOWHZ, // 最大持续超时
TCP_TO_KEEP_IDLE = 120 * 60 * TCP_SLOWHZ, // 保活启动
TCP_TO_KEEP_INTERVAL = 75 * TCP_SLOWHZ, // 保活测试间隔
TCP_TO_KEEPCNT = 8, // 保活测试次数
TCP_TO_RTT_DEFAULT = 1 * TCP_SLOWHZ, // 默认重传时间
TCP_TO_MIN = 1 * TCP_SLOWHZ, // 最小重传时间
TCP_TO_REXMIT_MAX = 64 * TCP_SLOWHZ, // 最大重传时间
TCP_MAXRXTCNT = 12, // 最大重传次数
};

#define TCP_RTT_SHIFT 3
#define TCP_RTT_SCALE (1 << TCP_RTT_SHIFT)
#define TCP_RTTVAR_SHIFT 2
#define TCP_RTTVAR_SACLE (1 << TCP_RTTVAR_SHIFT)
#define TCP_REXMIT_THREASH 3

typedef struct tcp_t
{
u16 sport; // 源端口号
Expand Down Expand Up @@ -147,6 +156,12 @@ typedef struct tcp_pcb_t
u32 rto; // 当前重传时限 RTO(Retransmission TimeOut)
u32 rtx_cnt; // 重传次数

u32 rtt; // round trip time 往返时间
u32 rtt_seq; // 用于计时的序列号
int srtt; // 已平滑的 RTT 估计器
int rttvar; // 已平滑的 RTT 平均偏差估计器
u32 rttmin; // 最小往返时间

list_t acclist; // 客户端 PCB 链表
int backlog; // 客户端数量
int backcnt; // 当前数量
Expand Down Expand Up @@ -209,4 +224,10 @@ err_t tcp_parse_option(tcp_pcb_t *pcb, tcp_t *tcp);
// 写入 TCP 选项
err_t tcp_write_option(tcp_pcb_t *pcb, tcp_t *tcp);

// 跟新 TCP RTO
u32 tcp_update_rto(tcp_pcb_t *pcb, int backoff);

// 重新计算往返时间
void tcp_xmit_timer(tcp_pcb_t *pcb, u32 rtt);

#endif
1 change: 1 addition & 0 deletions src/include/onix/stdlib.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#define MAX(a, b) (a < b ? b : a)
#define MIN(a, b) (a < b ? a : b)
#define ABS(a) (a < 0 ? -a : a)

void delay(u32 count);
void hang();
Expand Down
1 change: 1 addition & 0 deletions src/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ BUILTIN_APPS := \
$(BUILD)/builtin/tcp_server.out \
$(BUILD)/builtin/tcp_client.out \
$(BUILD)/builtin/tcp_nagle.out \
$(BUILD)/builtin/tcp_surge.out \

$(BUILD)/builtin/%.out: $(BUILD)/builtin/%.o \
$(BUILD)/lib/libc.o \
Expand Down
8 changes: 8 additions & 0 deletions src/net/netif.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <onix/stdio.h>
#include <onix/string.h>
#include <onix/assert.h>
#include <onix/syscall.h>
#include <onix/debug.h>
#include <onix/errno.h>

Expand Down Expand Up @@ -197,6 +198,13 @@ static void neto_thread()

netif->nic_output(netif, pbuf);

// if (ntohs(pbuf->eth->type) == ETH_TYPE_IP &&
// pbuf->eth->ip->proto == IP_PROTOCOL_TCP)
// {
// // 测试 RTT
// task_sleep((time() & 0x7) * 500);
// }

// LOGK("ETH SEND [%04X]: %m -> %m %d\n",
// ntohs(pbuf->eth->type),
// pbuf->eth->src,
Expand Down
11 changes: 8 additions & 3 deletions src/net/tcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -324,13 +324,16 @@ static int tcp_recvmsg(socket_t *s, msghdr_t *msg, u32 flags)
pbuf_t *pbuf = element_entry(pbuf_t, tcpnode, node);
node = node->next;

if (pbuf->total)
{
pbuf->length = pbuf->size;
pbuf->total = 0;
}

int len = (left < pbuf->size) ? left : pbuf->size;
ret = iovec_write(msg->iov, msg->iovlen, pbuf->data, len);
assert(ret == len);

pcb->rcv_wnd += len;
assert(pcb->rcv_wnd <= TCP_WINDOW);

left -= ret;

if (len < pbuf->size)
Expand All @@ -344,6 +347,8 @@ static int tcp_recvmsg(socket_t *s, msghdr_t *msg, u32 flags)
assert(pbuf->count == 1);
list_remove(&pbuf->tcpnode);
pbuf_put(pbuf);
pcb->rcv_wnd += pbuf->length;
assert(pcb->rcv_wnd <= TCP_WINDOW);
}
}
return size - left;
Expand Down
5 changes: 5 additions & 0 deletions src/net/tcp_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ static void tcp_update_ack(tcp_pcb_t *pcb, tcp_t *tcp)
return;
}

if (pcb->rtt && tcp->ackno == pcb->rtt_seq)
tcp_xmit_timer(pcb, pcb->rtt - 1);

pcb->rtt = 1;
pcb->snd_una = tcp->ackno;
pcb->rtx_cnt = 0;

Expand Down Expand Up @@ -88,6 +92,7 @@ static void tcp_update_ack(tcp_pcb_t *pcb, tcp_t *tcp)
{
task_unblock(pcb->tx_waiter, EOK);
pcb->tx_waiter = NULL;
pcb->timers[TCP_TIMER_REXMIT] = 0;
}
}

Expand Down
11 changes: 9 additions & 2 deletions src/net/tcp_output.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <onix/net.h>
#include <onix/task.h>
#include <onix/string.h>
#include <onix/syscall.h>
#include <onix/assert.h>
#include <onix/debug.h>

Expand Down Expand Up @@ -65,7 +66,7 @@ err_t tcp_enqueue(tcp_pcb_t *pcb, void *data, size_t size, int flags)
tcp->dport = htons(pcb->rport);
tcp->seqno = htonl(pcb->snd_nbb);
tcp->urgent = 0;
tcp->flags = 0;
tcp->flags = flags;

int hlen = tcp_write_option(pcb, tcp);
pbuf->data = ip->payload + hlen;
Expand Down Expand Up @@ -122,7 +123,13 @@ err_t tcp_output(tcp_pcb_t *pcb)
if (TCP_SEQ_GT(snd_nxt, pcb->snd_una + wnd))
break;

tcp_t *tcp = pbuf->eth->ip->tcp;
// 记录 rtt
if (pcb->rtt && TCP_SEQ_LEQ(pcb->rtt_seq, pcb->snd_una))
{
pcb->rtt = 1;
pcb->rtt_seq = snd_nxt;
}

pcb->snd_nxt = snd_nxt;
if (TCP_SEQ_GT(snd_nxt, pcb->snd_max))
pcb->snd_max = snd_nxt;
Expand Down
7 changes: 6 additions & 1 deletion src/net/tcp_pcb.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ tcp_pcb_t *tcp_pcb_get()
pcb->snd_wnd = TCP_DEFAULT_MSS;
pcb->snd_nbb = pcb->snd_nxt;

pcb->rto = TCP_TO_REXMIT;
pcb->rtt = 0;
pcb->rtt_seq = pcb->snd_una;
pcb->srtt = 0;
pcb->rttvar = TCP_TO_RTT_DEFAULT;
pcb->rttmin = TCP_TO_MIN;
pcb->rto = tcp_update_rto(pcb, 1);

list_init(&pcb->unsent);
list_init(&pcb->unacked);
Expand Down
Loading

0 comments on commit e3a229e

Please sign in to comment.