Board logo

标题: 分析TCP/IP协议栈代码之TCP(STM32平台) . [打印本页]

作者: 我是MT    时间: 2015-6-29 17:18     标题: 分析TCP/IP协议栈代码之TCP(STM32平台) .

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

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


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



        小结:
        TCP提供了一种可靠的面向连接的字节流运输层服务。我们简单地介绍了 TCP首部中的各个字段,并在随后的几章里详细讨论它们。
        TCP将用户数据打包构成报文段;它发送数据后启动一个定时器;另一端对收到的数据进行确认,对失序的数据重新排序,丢弃重复数据; TCP提供端到端的流量控制,并计算和验证一个强制性的端到端检验和。
        许多流行的应用程序如Telnet、Rlogin、FTP和SMTP都使用TCP。
3. TCP连接的建立与终止
3.1 建立连接协议

    这三个报文段完成连接的建立。这个过程也称为三次握手( 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, 1, 1, 0);
#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 + 4, 2);
    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, 1, 0, 1); // no options
    }

    else
    {
        make_tcphead(buf, info_data_len, 0, 1); // 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实验
串口现象:





浏览器现象:







欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0