【1】TCP的初始序号由发送SYN或发送FIN时决定。 【2】TCP建立连接时,PC机发送SYN时设置初始序号,AVR返回SYN和ACK时设置初始序号,这两个初始序号没有任何逻辑上的关系。
【3】确认号为上一个数据包的序号加上个数据包的负载长度。
【4】SYN标志和FIN特殊,占用个序号,即确认号需在上个数据包的序号基础累加。
STM32NET项目中何时清零序号,何时初始化序号,何时修改确认号,何时修改确认号的确是一个非常麻烦的过程。通过反复的测试,发现即使确认号出现问题程序也能顺利运行。
3.TCP实现
从实用主义角度出发,原理不重要,重要的是如何实现实现功能。和UDP不同,TCP生成首部时的步骤较多。由于TCP首部中存在选项,所以需要通过更多的代码确定TCP负载的位置,在这里可以分为在TCP首部中查找负载长度字节,并通过首部中的TCP偏移字节确定长度
3.1 TCP数据发送
TCP发送时的参数很多,大致可以分为以下步骤。
【1】确定下一个确认号的具体值。使用时有两种情况,那么累加1,要么累加上一个报文的负载长度。
【2】初始化序号,可以在ABRNET作为客户端时使用 【3】清除序号,可以在TCP建立连接时使用 【4】设置标志,例如FIN SYN等
【5】设置其他参数,例如紧急指针,窗口大小 【6】填充以太网首部,IP首部 【7】加入校验和
[cpp] view plain copy void tcp_send_packet
( BYTE *rxtx_buffer, //发送缓冲区 WORD_BYTES dest_port, // 目标端口号 WORD_BYTES src_port, // 源端口号 BYTE flags, // TCP标志 FIN SYN ACK
BYTE max_segment_size, // 初始化序号 接收SYN时使用 BYTE clear_seqack, // 设置确认号为0 发送SYN时使用 WORD next_ack_num, // 在上一个数据包序号的基础上累加 WORD dlength, // 负载长度 BYTE *dest_mac, // 目标MAC地址 BYTE *dest_ip ) // 目标IP地址 { BYTE i, tseq; WORD_BYTES ck; // 生成以太网报文头 eth_generate_header ( rxtx_buffer,
(WORD_BYTES){ETH_TYPE_IP_V}, dest_mac ); // 计算数据包 确认号 next_ack_num为累加值 // 1.确认号因等于上一个数据包的序号加上数据包长度 // 2.序号等于上一个数据包的确认号 // 3.FIN和SYN各占一个序号 // 4.确认号和序号使用大端模式,即高地址存放低位数据 // 5.确认号修改发生在三种情况,接收到SYN,接收到FIN,接收到负载数据 if
( next_ack_num ) { for( i = 4 ; i > 0; i-- ) { // 取出上一个数据包的序号,累加next_ack_num next_ack_num = rxtx_buffer [ TCP_SEQ_P + i - 1] + next_ack_num; // 取出上一个数据包的确认号 tseq = rxtx_buffer [ TCP_SEQACK_P + i - 1]; // 复制本次数据包的确认号,即上个数据包的序号+next_ack_num rxtx_buffer [ TCP_SEQACK_P + i - 1] = 0xff & next_ack_num; // 复制上一个数据包的确认号于本数据包的序号
rxtx_buffer[ TCP_SEQ_P + i - 1 ] = tseq; next_ack_num >>= 8; } } // 初始化序号 // 设置最大分片 // 第一次发送或接收时使用 if ( max_segment_size ) { // 初始化序号
rxtx_buffer[ TCP_SEQ_P + 0 ] = 0; rxtx_buffer[ TCP_SEQ_P + 1 ] = 0; rxtx_buffer[ TCP_SEQ_P + 2 ] = seqnum;
rxtx_buffer[ TCP_SEQ_P + 3 ] = 0; seqnum += 2; // 初始化 报文段最大长度
rxtx_buffer[ TCP_OPTIONS_P + 0 ] = 2; // 最大报文长度 rxtx_buffer[ TCP_OPTIONS_P + 1 ] = 4; // TCP选项长度 TCP选项格式2 rxtx_buffer[ TCP_OPTIONS_P + 2 ] = HIGH(1408); //
rxtx_buffer[ TCP_OPTIONS_P + 3 ] = LOW(1408); // // 数据偏移,占用高4位,且计算长度为双字 rxtx_buffer[ TCP_HEADER_LEN_P ] = 0x60; dlength += 4; } else { // 没有TCP选项时长度为5个双字
rxtx_buffer[ TCP_HEADER_LEN_P ] = 0x50; } // generate ip header and checksum ip_generate_header ( rxtx_buffer, (WORD_BYTES){(IP_HEADER_LEN + TCP_HEADER_LEN + dlength)}, IP_PROTO_TCP_V,
dest_ip ); // 清除序号,一般使用于发送SYN时 if ( clear_seqack )
{ rxtx_buffer[ TCP_SEQACK_P + 0 ] = 0; rxtx_buffer[ TCP_SEQACK_P + 1 ] = 0;
rxtx_buffer[ TCP_SEQACK_P + 2 ] = 0; rxtx_buffer[ TCP_SEQACK_P + 3 ] = 0; }
// 设置TCP标志 rxtx_buffer [ TCP_FLAGS_P ] = flags; // 设置目标端口号 rxtx_buffer
[ TCP_DST_PORT_H_P ] = dest_port.byte.high;
rxtx_buffer [ TCP_DST_PORT_L_P ] = dest_port.byte.low; // 设置源端口号 rxtx_buffer [ TCP_SRC_PORT_H_P ] = src_port.byte.high; rxtx_buffer
[ TCP_SRC_PORT_L_P ] = src_port.byte.low; // 设置TCP窗口大小 rxtx_buffer [ TCP_WINDOWSIZE_H_P ] =
HIGH((MAX_RX_BUFFER-IP_HEADER_LEN-ETH_HEADER_LEN)); rxtx_buffer [ TCP_WINDOWSIZE_L_P ] = LOW((MAX_RX_BUFFER-IP_HEADER_LEN-ETH_HEADER_LEN)); // 紧急指针 rxtx_buffer[ TCP_URGENT_PTR_H_P ] = 0;
rxtx_buffer[ TCP_URGENT_PTR_L_P ] = 0; // 计算校验和 rxtx_buffer[ TCP_CHECKSUM_H_P ] = 0; rxtx_buffer[ TCP_CHECKSUM_L_P ] = 0; ck.word = software_checksum( &rxtx_buffer[IP_SRC_IP_P], TCP_HEADER_LEN+dlength+8, IP_PROTO_TCP_V + TCP_HEADER_LEN + dlength );