分析TCP/IP协议栈代码之TCP(STM32平台) .

1. TCP介绍
TCP与UDP都属于传输层,但是与UDP不同的是,TCP是面向连接的,可靠的传输协议。
ps:需要找几篇文章来看看两者的不同和各自的用武之地了,虽然对下面的代码分析之后对何为“ 面向连接”,何为“ 可靠”有一个具象的了解,但是不够全面和系统,比如何时采用TCP,何时采用UDP,效果如何,当然还得解释清楚其中的原因所在。

2.  TCP首部
TCP数据被封装在一个IP数据报中,如图17 - 1所示。

图17 - 2显示TCP首部的数据格式。如果不计任选字段,它通常是20个字节。

  •  每个TCP段都包含源端和目的端的端口号,用于寻找发端和收端应用进程。这两个值加上IP首部中的源端IP地址和目的端IP地址唯一确定一个TCP连接。有时,一个IP地址和一个端口号也称为一个插口或套接字(socket) 。这个术语出现在最早的TCP规范(RFC793)中,后来它也作为表示伯克利版的编程接口 。插口对或套接字对(socket pair)(包含客户IP地址、客户端口号、服务器 IP地址和服务器端口号的四元组 )可唯一确定互联网络中每个TCP连接的双方。
  • 序号用来标识从TCP发端向TCP收端发送的数据字节流,它表示在这个报文段中的的第一个数据字节。如果将字节流看作在两个应用程序间的单向流动,则 TCP用序号对每个字节进行计数。序号是32 bit的无符号数,序号到达2^32-1后又从0开始。

  • 在TCP首部中有6个标志比特(此处结合下面的状态变迁图是实现的关键之所在。它们中的多个可同时被设置为 1。我们在这儿简单介绍它们的用法,在随后的章节中有更详细的介绍。
    • URG 紧急指针(urgent pointer)有效(见2 0 . 8节) 。
    • ACK 确认序号有效。
    • PSH 接收方应该尽快将这个报文段交给应用层。
    • RST 重建连接。
    • SYN 同步序号用来发起一个连接。这个标志和下一个标志将在第 18章介绍。
    • FIN 发端完成发送任务。
  • TCP的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端正期望接收的字节。窗口大小是一个 16bit字段,因而窗口大小最大为65535字节。在24 . 4节我们将看到新的窗口刻度选项,它允许这个值按比例变化以提供更大的窗口。
  • 检验和覆盖了整个的TCP报文段:TCP首部和TCP数据。这是一个强制性的字段,一定是由发端计算和存储,并由收端进行验证。 TCP检验和的计算和UDP检验和的计算相似,使用如11 . 3节所述的一个伪首部。
  • 只有当URG标志置1时紧急指针才有效。紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。 TCP的紧急方式是发送端向另一端发送紧急数据的一种方式。我们将在20 . 8节介绍它。
  • 最常见的可选字段是最长报文大小,又称为 MSS (Maximum Segment Size)。每个连接方通常都在通信的第一个报文段(为建立连接而设置 SYN标志的那个段)中指明这个选项。它指明本端所能接收的最大长度的报文段。我们将在 18 . 4节更详细地介绍MSS选项,TCP的其他选项中的一些将在第24章中介绍。从图17 - 2中我们注意到TCP报文段中的数据部分是可选的。我们将在 18章中看到在一个连接建立和一个连接终止时,双方交换的报文段仅有 TCP首部。如果一方没有数据要发送,也使用没有任何数据的首部来确认收到的数据。在处理超时的许多情况中,也会发送不带任何数据的报文段。
        小结:
        TCP提供了一种可靠的面向连接的字节流运输层服务。我们简单地介绍了 TCP首部中的各个字段,并在随后的几章里详细讨论它们。
        TCP将用户数据打包构成报文段;它发送数据后启动一个定时器;另一端对收到的数据进行确认,对失序的数据重新排序,丢弃重复数据; TCP提供端到端的流量控制,并计算和验证一个强制性的端到端检验和。
        许多流行的应用程序如Telnet、Rlogin、FTP和SMTP都使用TCP。
3. TCP连接的建立与终止
3.1 建立连接协议
  1. 请求端(通常称为客户)发送一个 SYN段指明客户打算连接的服务器的端口,以及初 
    始序号(ISN,在这个例子中为1415531521) 。这个SYN段为报文段1。
  2. 服务器发回包含服务器的初始序号的SYN报文段(报文段2)作为应答。同时,将确认 
    序号设置为客户的ISN加1以对客户的SYN报文段进行确认。一个SYN将占用一个序号。
  3. 客户必须将确认序号设置为服务器的 ISN加1以对服务器的SYN报文段进行确认(报文 
    段3) 。
    这三个报文段完成连接的建立。这个过程也称为 三次握手( three-way handshake) 。
以下为WireShark的建立连接数据的抓包,其中HTTP为服务器端:
注意:这里PC端的默认MSS是1460(因为是以太网),而STM32端的MSS是1408,这个可以在程序里面修改。

3.2 连接终止协议
建立一个连接需要三次握手,而 终止一个连接要经过 4次握手。如下图所示,过程与建立连接的三次握手过程类似。

4.  TCP的状态变迁图
ps:跟着箭头走就ok了,当然不会所有的状态变迁都实现,看具体协议栈的实现,下面的代码就只实现了其中的一部分。

ESTABLISHED状态是连接双方能够进行双向数据传递的状态。
注意:并不是所有的状态变迁都需要实现的,这取决于协议栈的具体实现,但是必须要有至少一条状态回路来保证数据的传输。
----------------------------------以上内容整理于《TCP/IP协议详解:卷1》------------------------------
------------------------------------------以下内容产生于代码及分析--------------------------------------

5.  TCP宏定义实现
 与上文中的首部对着看,位置是一一对应的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// ******* TCP *******
//TCP首部长度
#define TCP_HEADER_LEN_PLAIN  20
//源端口位置
#define TCP_SRC_PORT_H_P 0x22
#define TCP_SRC_PORT_L_P 0x23
//目标端口位置
#define TCP_DST_PORT_H_P 0x24
#define TCP_DST_PORT_L_P 0x25
// the tcp seq number is 4 bytes 0x26-0x29
//32位序列号
#define TCP_SEQ_H_P          0x26
//32位确认序列号                    ox2a-0x2d
#define TCP_SEQACK_H_P       0x2a

//flags位置,最高两位保留
#define TCP_FLAGS_P             0x2f
// flags: SYN=2 6个标志位
#define TCP_FLAGS_FIN_V         0x01
#define TCP_FLAGS_SYN_V         0x02
#define TCP_FLAGS_PUSH_V        0x08
#define TCP_FLAGS_SYNACK_V      0x12
#define TCP_FLAGS_ACK_V         0x10
#define TCP_FLAGS_PSHACK_V  0x18
//  plain len without the options:
//4位首部长度
#define TCP_HEADER_LEN_P 0x2e
//校验和位置
#define TCP_CHECKSUM_H_P 0x32
#define TCP_CHECKSUM_L_P 0x33
//选项起始位置
#define TCP_OPTIONS_P    0x36
//

6.  TCP函数实现
 make_tcphead : TCP首部的填充,与IP和UDP等类似,但是TCP加入了 握手MSS可选项的设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// make a return tcp header from a received tcp packet
// rel_ack_num is how much we must step the seq number received from the
// other side. We do not send more than 255 bytes of text (=data) in the tcp packet.
// If mss=1 then mss is included in the options list
//
// After calling this function you can fill in the first data byte at TCP_OPTIONS_P+4
// If cp_seq=0 then an initial sequence number is used (should be use in synack)
// otherwise it is copied from the packet we received
void make_tcphead( unsigned  char * buf, unsigned   int rel_ack_num, unsigned  char  mss, unsigned  char  cp_seq)
{
     unsigned  char  i= 0;
     unsigned  char  tseq;
     while(i< 2)
    {
        buf[TCP_DST_PORT_H_P+i]=buf[TCP_SRC_PORT_H_P+i];
        buf[TCP_SRC_PORT_H_P+i]= 0// clear source port
        i++;
    }
     // set source port  (http):
    buf[TCP_SRC_PORT_L_P]=wwwport;
         //序列号和确认序列号的长度为32位
    i= 4;
     // sequence numbers: add the rel_ack_num to SEQACK
         //将序列号的值+rel_ack_num之后返回,来完成握手过程
     while(i> 0)
    {
        rel_ack_num=buf[TCP_SEQ_H_P+i- 1]+rel_ack_num;
        tseq=buf[TCP_SEQACK_H_P+i- 1];
        buf[TCP_SEQACK_H_P+i- 1]=0xff&rel_ack_num;
         if (cp_seq)
        {
             // copy the acknum sent to us into the sequence number
            buf[TCP_SEQ_H_P+i- 1]=tseq;
        }
         else
        {
            buf[TCP_SEQ_H_P+i- 1]=  0// some preset vallue
        }
        rel_ack_num=rel_ack_num>> 8;
        i--;
    }
     if (cp_seq== 0)
    {
         // put inital seq number
        buf[TCP_SEQ_H_P+ 0]=  0;
        buf[TCP_SEQ_H_P+ 1]=  0;
         // we step only the second byte, this allows us to send packts 
         // with 255 bytes or 512 (if we step the initial seqnum by 2)
        buf[TCP_SEQ_H_P+ 2]= seqnum; 
        buf[TCP_SEQ_H_P+ 3]=  0;
         // step the inititial seq num by something we will not use
         // during this tcp session:
        seqnum+= 2;
    }
     // zero the checksum
    buf[TCP_CHECKSUM_H_P]= 0;
    buf[TCP_CHECKSUM_L_P]= 0;
    
     // The tcp header length is only a 4 bit field (the upper 4 bits).
     // It is calculated in units of 4 bytes. 
     // E.g 24 bytes: 24/4=6 => 0x60=header len field
     //buf[TCP_HEADER_LEN_P]=(((TCP_HEADER_LEN_PLAIN+4)/4)) <<4; // 0x60
         //TCP可选项里面的MSS (Maximum Segment Size) 
     if (mss)
    {
         // the only option we set is MSS to 1460:
         // 1460 in hex is 0x5B4
        buf[TCP_OPTIONS_P]= 2;
        buf[TCP_OPTIONS_P+ 1]= 4;
        buf[TCP_OPTIONS_P+ 2]=0x05; 
        buf[TCP_OPTIONS_P+ 3]=0xb4;
         // 24 bytes:
        buf[TCP_HEADER_LEN_P]=0x60;
    }
     else
    {
         // no options:
         // 20 bytes:
        buf[TCP_HEADER_LEN_P]=0x50;
    }
}

 make_tcp_synack_from_syn : 与make_udp_reply_from_request过程类似
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void make_tcp_synack_from_syn( unsigned  char *buf)
{
     unsigned   int ck, i =  0;
    make_eth(buf);
     // total length field in the IP header must be set:
     // 20 bytes IP + 24 bytes (20tcp+4tcp options)
    buf[IP_TOTLEN_H_P] =  0;
    buf[IP_TOTLEN_L_P] = IP_HEADER_LEN + TCP_HEADER_LEN_PLAIN +  4;
    make_ip(buf);
    buf[TCP_FLAGS_P] = TCP_FLAGS_SYNACK_V;
    make_tcphead(buf,  110);
#ifdef TCP_DEBUG
    i =  0;
    printf( "TCP Server Test. \r\n");
    printf( "tcp客户端的IP地址及端口号 : \r\n");

     while(i <  sizeof(ipv4_addr))
    {
         //注意这里我们建立的是UDP Server,输出UDP Client的IP地址
        printf( "%d", buf[IP_DST_P + i]);

         if(i !=  sizeof(ipv4_addr) -  1)
        {
            printf( ".");
        }

        i++;
    }

    i =  0;
     //输出pc端的tcp port
    printf( ":%d \r\n", wwwport);
#endif
     // calculate the checksum, len=8 (start from ip.src) + TCP_HEADER_LEN_PLAIN + 4 (one option: mss)
    ck = checksum(&buf[IP_SRC_P],  8 + TCP_HEADER_LEN_PLAIN +  42);
    buf[TCP_CHECKSUM_H_P] = ck >>  8;
    buf[TCP_CHECKSUM_L_P] = ck & 0xff;
     // add 4 for option mss:
    enc28j60PacketSend(IP_HEADER_LEN + TCP_HEADER_LEN_PLAIN +  4 + ETH_HEADER_LEN, buf);
}

 Web_Server函数中的while(1)死循环中的TCP部分,这是个主过程,里面有很多子函数将在下面说明。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

/*-----------------tcp port www start, compare only the lower byte-----------------------------------*/
if(buf[IP_PROTO_P] == IP_PROTO_TCP_V && buf[TCP_DST_PORT_H_P] ==  0 && buf[TCP_DST_PORT_L_P] == mywwwport)
{
     /*
        以下为 TCP的状态变迁图 的部分实现。
    */

     //若为客户端的SYN请求,则返回SYN+ACK
     if(buf[TCP_FLAGS_P] & TCP_FLAGS_SYN_V)                               //第一次握手
    {
        make_tcp_synack_from_syn(buf);                                       //第二次握手
         // make_tcp_synack_from_syn does already send the syn,ack
         continue;
    }

     //若为客户端的ACK请求,即完成三次握手,可以传输数据了
     if(buf[TCP_FLAGS_P] & TCP_FLAGS_ACK_V)                               //第三次握手
    {
        init_len_info(buf);  // init some data structures
         // we can possibly have no data, just ack:
        dat_p =  get_tcp_data_pointer();

         //无数据,返回ack
         if(dat_p ==  0)
        {
             if(buf[TCP_FLAGS_P] & TCP_FLAGS_FIN_V)
            {
                 // finack, answer with ack
                 make_tcp_ack_from_any(buf);
            }

             // just an ack with no data, wait for next packet
             continue;
        }

         //有数据,好了,下面就是HTTP协议规定的数据了
         if(strncmp( "GET ", ( char *) & (buf[dat_p]),  4) !=  0)
        {
             // head, post and other methods:
             //
             // for possible status codes see:
             // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
            plen =  fill_tcp_data_p(buf,  0, PSTR( "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>200 OK</h1>"));
             goto SENDTCP;
        }

         //密码部分
         if(strncmp( "/ ", ( char *) & (buf[dat_p +  4]),  2) ==  0)
        {
            plen = fill_tcp_data_p(buf,  0, PSTR( "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"));
            plen = fill_tcp_data_p(buf, plen, PSTR( "<p>Usage: "));
            plen =  fill_tcp_data(buf, plen, baseurl);
            plen = fill_tcp_data_p(buf, plen, PSTR( "password</p>"));
             goto SENDTCP;
        }

        cmd = analyse_get_url(( char *) & (buf[dat_p +  5]));

         // for possible status codes see:
         // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
         if(cmd == - 1)
        {
            plen = fill_tcp_data_p(buf,  0, PSTR( "HTTP/1.0 401 Unauthorized\r\nContent-Type: text/html\r\n\r\n<h1>401 Unauthorized</h1>"));
             goto SENDTCP;
        }

         if(cmd ==  0)               // 用户程序
        {
            GPIO_SetBits(GPIOA, GPIO_Pin_8);
            i =  0;                            // 命令 = 1
        }

         if(cmd ==  1)                          // 用户程序
        {
            GPIO_ResetBits(GPIOA, GPIO_Pin_8);
            i =  1;                            // 命令 = 0
        }

         // if (cmd==-2) or any other value
         // just display the status:
        plen = print_webpage(buf, (i));
    SENDTCP:
         make_tcp_ack_from_any(buf);        // send ack for http get
         make_tcp_ack_with_data(buf, plen);  // send data
         continue;
    }
}

/*-------------------------------------- tcp port www end ---------------------------------------*/


 主过程中的各TCP相关的子函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// do some basic length calculations and store the result in static varibales
void init_len_info( unsigned  char *buf)
{
     //IP Packet长度
    info_data_len = (buf[IP_TOTLEN_H_P] <<  8) | (buf[IP_TOTLEN_L_P] & 0xff);
     //减去IP首部长度
    info_data_len -= IP_HEADER_LEN;
     //TCP首部长度,因为TCP协议规定了只有四位来表明长度,所需要如下处理,4*6=24
    info_hdr_len = (buf[TCP_HEADER_LEN_P] >>  4) *  4// generate len in bytes;
     //减去TCP首部长度
    info_data_len -= info_hdr_len;

     if(info_data_len <=  0)
    {
        info_data_len =  0;
    }
}

// get a pointer to the start of tcp data in buf
// Returns 0 if there is no data
// You must call init_len_info once before calling this function
unsigned   int get_tcp_data_pointer( void)
{
     if(info_data_len)
    {
         //在buf中数据开始的位置
         return(( unsigned   int)TCP_SRC_PORT_H_P + info_hdr_len);
    }

     else
    {
         return( 0);
    }
}

// fill in tcp data at position pos. pos=0 means start of
// tcp data. Returns the position at which the string after
// this string could be filled.
unsigned   int fill_tcp_data_p( unsigned  char *buf,  unsigned   int pos,  const  unsigned  char *progmem_s)
{
     char c;

     // fill in tcp data at position pos
     //
     // with no options the data starts after the checksum + 2 more bytes (urgent ptr)
     while((c = pgm_read_byte(progmem_s++)))
    {
        buf[TCP_CHECKSUM_L_P +  3 + pos] = c;
        pos++;
    }

     return(pos);
}

// fill in tcp data at position pos. pos=0 means start of
// tcp data. Returns the position at which the string after
// this string could be filled.
unsigned   int fill_tcp_data( unsigned  char *buf,  unsigned   int pos,  const  char *s)
{
     // fill in tcp data at position pos
     //
     // with no options the data starts after the checksum + 2 more bytes (urgent ptr)
     while(*s)
    {
        buf[TCP_CHECKSUM_L_P +  3 + pos] = *s;
        pos++;
        s++;
    }

     return(pos);
}

// Make just an ack packet with no tcp data inside
// This will modify the eth/ip/tcp header
void make_tcp_ack_from_any( unsigned  char *buf)
{
     unsigned   int j;
    make_eth(buf);
     // fill the header:
    buf[TCP_FLAGS_P] = TCP_FLAGS_ACK_V;

     if(info_data_len ==  0)
    {
         // if there is no data then we must still acknoledge one packet
        make_tcphead(buf,  101);  // no options
    }

     else
    {
        make_tcphead(buf, info_data_len,  01);  // no options
    }

     // total length field in the IP header must be set:
     // 20 bytes IP + 20 bytes tcp (when no options)
    j = IP_HEADER_LEN + TCP_HEADER_LEN_PLAIN;
    buf[IP_TOTLEN_H_P] = j >>  8;
    buf[IP_TOTLEN_L_P] = j & 0xff;
    make_ip(buf);
     // calculate the checksum, len=8 (start from ip.src) + TCP_HEADER_LEN_PLAIN + data len
    j = checksum(&buf[IP_SRC_P],  8 + TCP_HEADER_LEN_PLAIN,  2);
    buf[TCP_CHECKSUM_H_P] = j >>  8;
    buf[TCP_CHECKSUM_L_P] = j & 0xff;
    enc28j60PacketSend(IP_HEADER_LEN + TCP_HEADER_LEN_PLAIN + ETH_HEADER_LEN, buf);
}

// you must have called init_len_info at some time before calling this function
// dlen is the amount of tcp data (http data) we send in this packet
// You can use this function only immediately after make_tcp_ack_from_any
// This is because this function will NOT modify the eth/ip/tcp header except for
// length and checksum
void make_tcp_ack_with_data( unsigned  char *buf,  unsigned   int dlen)
{
     unsigned   int j;
     // fill the header:
     // This code requires that we send only one data packet
     // because we keep no state information. We must therefore set
     // the fin here:
    buf[TCP_FLAGS_P] = TCP_FLAGS_ACK_V | TCP_FLAGS_PUSH_V | TCP_FLAGS_FIN_V;
     // total length field in the IP header must be set:
     // 20 bytes IP + 20 bytes tcp (when no options) + len of data
    j = IP_HEADER_LEN + TCP_HEADER_LEN_PLAIN + dlen;
    buf[IP_TOTLEN_H_P] = j >>  8;
    buf[IP_TOTLEN_L_P] = j & 0xff;
    fill_ip_hdr_checksum(buf);
     // zero the checksum
    buf[TCP_CHECKSUM_H_P] =  0;
    buf[TCP_CHECKSUM_L_P] =  0;
     // calculate the checksum, len=8 (start from ip.src) + TCP_HEADER_LEN_PLAIN + data len
    j = checksum(&buf[IP_SRC_P],  8 + TCP_HEADER_LEN_PLAIN + dlen,  2);
    buf[TCP_CHECKSUM_H_P] = j >>  8;
    buf[TCP_CHECKSUM_L_P] = j & 0xff;
    enc28j60PacketSend(IP_HEADER_LEN + TCP_HEADER_LEN_PLAIN + dlen + ETH_HEADER_LEN, buf);
}

TCP比UDP要复杂的很多,这是由两者的特性所决定的,需要再找点文章来消化下两者的不同;还有一些HTTP相关的子函数,属于应用层的东东,加油,加油看了~~~

7.  TCP实验
串口现象:

浏览器现象:



  • 3
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值