手写TCP/IP协议栈——TCP数据发送(十五)

一、理论部分

TCP的数据通信是全双工通信,也就是可以同时发送和接收数据

发送数据的同时也能够通知对方,之前发送的数据我已经接收到了(ack序号)

TCP流量控制——滑动窗口实现

滑动窗口的发送端

数据分类:① 已发送,未确认 ② 已准备,未发送 ③ 未使用,空闲

滑动窗口的接收端

接收窗口

可以在接收数据时,由接收端主动发包通知窗口变化,或者由发送端定期去主动查询

二、代码实现

tcp_buf结构定义,缓存要发送的数据

data_count:未确认+待发送

unacked_count:未确认字节数

TCP发送队列,先进先服务,是一个循环队列,超过长度要注意回绕

// 定义TCP缓冲区大小
#define XTCP_CFG_RTX_BUF_SIZE	   2048

typedef struct _xtcp_buf_t
{
	uint16_t data_count, unacked_count;
	uint16_t front, tail, next;
	uint8_t data[XTCP_CFG_RTX_BUF_SIZE];
}xtcp_buf_t;

TCP控制块添加字段:

struct _xtcp_t
{
	xtcp_state_t state;
	uint16_t local_port, remote_port;
	xipaddr_t remote_ip;
	uint32_t unack_seq, next_seq;
	uint32_t ack;
	uint16_t remote_mss;
	uint16_t remote_win;
	xtcp_handler_t handler;
	xtcp_buf_t tx_buf;
};

在tcp_alloc中对新添加的字段进行初始化:

static xtcp_t* tcp_alloc(void)
{
	xtcp_t* tcp, * end;
	for (tcp = tcp_socket, end = tcp_socket + XTCP_CFG_MAX_TCP; tcp < end; tcp++)
	{
		if (tcp->state == XTCP_STATE_FREE)
		{
			tcp->state = XTCP_STATE_CLOSED;
			tcp->local_port = 0;
			tcp->remote_port = 0;
			tcp->remote_ip.addr = 0;
			tcp->handler = (xtcp_handler_t)0;
			tcp->remote_win = XTCP_MSS_DEFAULT;
			tcp->remote_mss = XTCP_MSS_DEFAULT;
			tcp->unack_seq = tcp->next_seq = tcp_get_init_seq();
			tcp->ack = 0;
			tcp_buf_init(&tcp->tx_buf);
			return tcp;
		}
	}

	return (xtcp_t*)0;
}

tcp_buf_init函数实现:

static void tcp_buf_init(xtcp_buf_t* tcp_buf)
{
	tcp_buf->tail = tcp_buf->next = tcp_buf->front = 0;
	tcp_buf->data_count = tcp_buf->unacked_count = 0;
}

tcp_process_accept函数添加代码:

new_tcp->unack_seq = new_tcp->next_seq = tcp_get_init_seq();

在TCP状态处理中添加数据处理部分代码:

序号只处理unacked_seq,至于next_seq在别的位置去处理

所有非 SYN 的报文都应该带ACK(Windows和Linux都是必须带ACK)

1)既然所有TCP包都有ACK,所以统一先处理ACK回复部分逻辑

2)回复当中优先处理FIN,其次如果有数据发送过来处理数据,如果对方没有传数据过来,判断自己是否有数据需要发送,如果也没有说明对方只是回复,无需进一步处理

remove_header(packet, tcp_hdr->hdr_flags.hdr_len * 4);
	switch (tcp->state)
	{
	case XTCP_STATE_SYNC_RECVD:
	{
		if (tcp_hdr->hdr_flags.flags & XTCP_FLAG_ACK)
		{
			tcp->unack_seq++;
			tcp->state = XTCP_STATE_ESTABLISHED;
			tcp->handler(tcp, XTCP_CONN_CONNECTED);
		}
		break;
	}
	case XTCP_STATE_ESTABLISHED:
	{
		if (tcp_hdr->hdr_flags.flags & (XTCP_FLAG_ACK | XTCP_FLAG_FIN))
		{
			//优先处理ACK字段
			if (tcp_hdr->hdr_flags.flags & XTCP_FLAG_ACK)
			{
				if ((tcp_hdr->ack > tcp->unack_seq) && (tcp_hdr->ack <= tcp->next_seq))
				{
					uint16_t curr_ack_size = tcp_hdr->ack - tcp->unack_seq;
					tcp_buf_add_acked_count(&tcp->tx_buf, curr_ack_size);
					tcp->unack_seq += curr_ack_size;
				}
			}

			if (tcp_hdr->hdr_flags.flags & XTCP_FLAG_FIN)
			{
				tcp->state = XTCP_STATE_LAST_ACK;
				tcp->ack++;
				tcp_send(tcp, XTCP_FLAG_FIN | XTCP_FLAG_ACK);
			}
			else if (tcp_buf_wait_send_count(&tcp->tx_buf))
			{
				tcp_send(tcp, XTCP_FLAG_ACK);
			}

		}
		break;
	}
    }

tcp_buf_add_acked_count函数实现:

static void tcp_buf_add_acked_count(xtcp_buf_t* tcp_buf, uint16_t size)
{
	tcp_buf->tail += size;
	if (tcp_buf->tail >= XTCP_CFG_RTX_BUF_SIZE)
		tcp_buf->tail = 0;
	tcp_buf->data_count -= size;
	tcp_buf->unacked_count -= size;
}

tcp_buf_wait_send_count函数实现:

static void tcp_buf_wait_send_count(xtcp_buf_t* tcp_buf)
{
	return tcp_buf->data_count - tcp_buf->unacked_count;
}

TCP数据发送部分处理

1)获取需要发送的字节流

uint16_t data_size = tcp_buf_wait_send_count(&tcp->tx_buf);

2)判断对方窗口是否有空闲(是否可发)

if (tcp->remote_win)
	{
		data_size = min(data_size, tcp->remote_win);
		data_size = min(data_size, tcp->remote_mss);
		if (data_size + opt_size > XTCP_DATA_MAX_SIZE)
			data_size = XTCP_DATA_MAX_SIZE - opt_size;
	}

3)将数据从发送缓冲区放到网络数据包当中

tcp_buf_read_for_send(&tcp->tx_buf, packet->data + opt_size + sizeof(xtcp_hdr_t), data_size);

4)调用ip_out函数将数据包发送出去

err = xip_out(XNET_PROTOCOL_TCP, &tcp->remote_ip, packet);

tcp_send完整代码实现:

#define XTCP_DATA_MAX_SIZE	(XNET_CFG_PACKET_MAX_SIZE \
		- sizeof(xether_hdr_t) - sizeof(xip_hdr_t) - sizeof(xtcp_hdr_t))

static xnet_err_t tcp_send(xtcp_t* tcp, uint8_t flags)
{
	xnet_packet_t* packet;
	xtcp_hdr_t* tcp_hdr;
	xnet_err_t err;
	uint16_t data_size = tcp_buf_wait_send_count(&tcp->tx_buf);
	uint16_t opt_size = (flags & XTCP_FLAG_SYN) ? 4 : 0;

    //判断当前允许发送的字节数
	if (tcp->remote_win)
	{
		data_size = min(data_size, tcp->remote_win);
		data_size = min(data_size, tcp->remote_mss);
		if (data_size + opt_size > XTCP_DATA_MAX_SIZE)
			data_size = XTCP_DATA_MAX_SIZE - opt_size;
	}
	else
	{
		data_size = 0;
	}

	packet = xnet_alloc_for_send(data_size + opt_size + sizeof(xtcp_hdr_t));
	tcp_hdr = (xtcp_hdr_t*)packet->data;
	tcp_hdr->src_port = swap_order16(tcp->local_port);
	tcp_hdr->dst_port = swap_order16(tcp->remote_port);
	tcp_hdr->seq = swap_order32(tcp->next_seq);
	tcp_hdr->ack = swap_order32(tcp->ack);
	tcp_hdr->hdr_flags.all = 0;
	tcp_hdr->hdr_flags.hdr_len = (opt_size + sizeof(xtcp_hdr_t)) / 4;
	tcp_hdr->hdr_flags.flags = flags;
	tcp_hdr->hdr_flags.all = swap_order16(tcp_hdr->hdr_flags.all);
	tcp_hdr->window = 1024;
	tcp_hdr->checksum = 0;
	tcp_hdr->urgent_ptr = 0;
	if (flags & XTCP_FLAG_SYN)
	{
		uint8_t* opt_data = packet->data + sizeof(xtcp_hdr_t);
		opt_data[0] = XTCP_KIND_MSS;
		opt_data[1] = 4;
		*(uint16_t*)(opt_data + 2) = swap_order16(XTCP_MSS_DEFAULT);
	}

	tcp_buf_read_for_send(&tcp->tx_buf, packet->data + opt_size + sizeof(xtcp_hdr_t), data_size);
	tcp_hdr->checksum = checksum_peso(&netif_ipaddr, &tcp->remote_ip, XNET_PROTOCOL_TCP, (uint16_t*)packet->data, packet->size);
	tcp_hdr->checksum = tcp_hdr->checksum ? tcp_hdr->checksum : 0xFFFF;

	err = xip_out(XNET_PROTOCOL_TCP, &tcp->remote_ip, packet);
	if (err < 0) return err;

	tcp->remote_win -= data_size;
	tcp->next_seq += data_size;
	tcp_buf_add_unacked_count(&tcp->tx_buf, data_size);

	if (flags & (XTCP_FLAG_SYN | XTCP_FLAG_FIN))
	{
		tcp->next_seq++;
	}

	return XNET_ERR_OK;
}

其中这里设置了三重保障:

1)发送的数据长度 < 对方允许接收的最大数据量(流量控制)

2)发送的数据长度 < 对方的MSS(可避免IP分片导致的数据通信效率下降)(128B以下不分片)

3)发送的数据长度 < 本地MTU

此外,data_size为0,不发送数据不能直接返回,因为还可以只发送标志位

if (tcp->remote_win)
{
	data_size = min(data_size, tcp->remote_win);
	data_size = min(data_size, tcp->remote_mss);
	if (data_size + opt_size > XTCP_DATA_MAX_SIZE)
		data_size = XTCP_DATA_MAX_SIZE - opt_size;
}
else
{
    data_size=0;
}

tcp_buf_read_for_send函数实现:

其主要功能是将发送缓冲队列当中的数据放到packet当中,准备调用ip_out发送出去

static uint16_t tcp_buf_read_for_send(xtcp_buf_t* tcp_buf, uint8_t* to, uint16_t size)
{
	int i;
	uint16_t wait_send_count = tcp_buf->data_count - tcp_buf->unacked_count;

	size = min(size, wait_send_count);
	for (i = 0; i < size; i++)
	{
		*to++ = tcp_buf->data[tcp_buf->next++];
		if (tcp_buf->next >= XTCP_CFG_RTX_BUF_SIZE)
		{
			tcp_buf->next = 0;
		}
	}

	return size;
}

tcp_buf_add_unacked_count函数实现:

static void tcp_buf_add_unacked_count(xtcp_buf_t* tcp_buf, uint16_t size) 
{
	tcp_buf->unacked_count += size;
}

在http_server.c当中添加发送数据的逻辑

static uint8_t tx_buffer[1024];

static xnet_err_t http_handler(xtcp_t* tcp, xtcp_conn_state_t state)
{
	static char* num = "0123456789ABCDEF";

	if (state == XTCP_CONN_CONNECTED)
	{
		int i;

		for (i = 0; i < sizeof(tx_buffer); i++)
		{
			tx_buffer[i] = num[i % 16];
		}
		xtcp_write(tcp, tx_buffer, sizeof(tx_buffer));
	}
		
	else if (state == XTCP_CONN_CLOSED)
		printf("http closed.\n");

	return XNET_ERR_OK;
}

xtcp_write函数的定义与实现:

int xtcp_write(xtcp_t* tcp, uint8_t* data, uint16_t size);

xnet_tinny.c

由于此时已经建立TCP连接了,所以所有的数据发送一律添加ACK标签

int xtcp_write(xtcp_t* tcp, uint8_t* data, uint16_t size)
{
	uint16_t send_size;

	if ((tcp->state != XTCP_STATE_ESTABLISHED))
	{
		return -1;
	}
	send_size = tcp_buf_write(&tcp->tx_buf, data.size);
	if (send_size)
	{
		tcp_send(tcp, XTCP_FLAG_ACK);
	}

	return send_size;
}

其中tcp_buf_write函数实现:

static uint16_t tcp_buf_write(xtcp_buf_t* tcp_buf, uint8_t* from, uint16_t size)
{
	int i;

	size = min(size, tcp_buf_free_count(tcp_buf));
	for (i = 0; i < size; i++)
	{
		tcp_buf->data[tcp_buf->front++] = *from++;
		if (tcp_buf->front >= XTCP_CFG_RTX_BUF_SIZE)
		{
			tcp_buf->front = 0;
		}
	}
	tcp_buf->data_count += size;

	return size;
	
}

tcp_buf_free_count函数实现:

static uint16_t tcp_buf_free_count(xtcp_buf_t* tcp_buf)
{
	return XTCP_CFG_RTX_BUF_SIZE - tcp_buf->data_count;
}

三、测试部分

ok,今天的你就到此为止吧,明天还要接着🐺啊!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值