Red Point

十年饮冰 , 热血难凉 ; 山高万仞 , 只登一步

  博客园 :: 首页 :: 博问 :: 闪存 :: :: :: :: 管理 ::

转载于: http://blog.csdn.net/fovwin/article/details/8887588

 
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宏定义实现
 与上文中的首部对着看,位置是一一对应的。
 
// ******* 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可选项的设置
 
// 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过程类似

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部分,这是个主过程,里面有很多子函数将在下面说明。

/*-----------------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相关的子函数

// 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实验

串口现象:
浏览器现象:

 

posted on 2017-01-11 13:25  Red_Point  阅读(943)  评论(0)    收藏  举报