tcp/ip协议学习笔记(8)TCP传输控制协议

时间:2021-07-16 10:29:57

    尽管TCP和UDP都使用相同的网络层(IP),TCP却向应用层提供与UDP完全不同的服务。TCP提供一种面向连接的、可靠的字节流服务

在一个TCP连接中,仅有两方进行彼此通信。广播和多播不能用于TCP。TCP协议在RFC793有明确的规范

T C P通过下列方式来提供可靠性:

1)应用数据被分割成TCP认为最适合发送的数据块。这和UDP完全不同,应用程序产生的数据报长度将保持不变。由TCP传递给IP的信息单位称为报文段或段( segment)。

2)当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。

3)当TCP收到发自TCP连接另一端的数据,它将发送一个确认。

4)TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段(希望发端超时并重发)。

5)既然TCP报文段作为IP数据报来传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。如果必要,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层。

6)既然IP数据报会发生重复, TCP的接收端必须丢弃重复的数据。

7)TCP还能提供流量控制。TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲区溢出。也即是常说的反压。

两个应用程序通过TCP连接交换8bit字节构成的字节流。TCP不在字节流中插入记录标识符。我们将这称为字节流服务。TCP对字节流的内容不作任何解释。TCP不知道传输的数据字节流是二进制数据,还是ASCII字符、EBCDIC字符或者其他类型数据。对字节流的解释由TCP连接双方的应用层解释。

TCP报文封装

tcp/ip协议学习笔记(8)TCP传输控制协议

TCP Header Format
                                    
    0                   1                   2                   3   
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          Source Port          |       Destination Port        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                        Sequence Number                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Acknowledgment Number                      |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Data |           |U|A|P|R|S|F|                               |
   | Offset| Reserved  |R|C|S|S|Y|I|            Window             |
   |       |           |G|K|H|T|N|N|                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           Checksum            |         Urgent Pointer        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                             data                              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

                            TCP Header Format

Source Port & Destination Port : 源目的端口(同UDP)
Sequence Number & Acknowledgment Number : 序列号和确认序列号,序号用来标识从TCP发端向TCP收端发送的数据字节流,它表示在这个报文段中的的第一个数据字节,每个传输的字节都被计数。举例来说:TCP报文被称为一个TCP SEGMENT,也就是一个段,比如这个段的长度是100,而初始序列号是0。当对端收到这个段后,会发送一个ACK报文,ACK的号就是101,当发送端收到这个ACK后,他就知道了,下一个报文段从第101个字节开始发送。也就是对已发送的字节进行计数。
DataOffset: 首部32bit word的数量,也即TCP首部,最多15(1111)* 4 = 60字节
Control Bits: 6 bits (from left to right):
URG: 紧急指针有效
ACK: 确认序号有效
PSH: 接收发应尽快将该报文交给应用层
RST: 重新连接
SYN: 同步序号用来发起一个连接
FIN: 发端完成发送任务
Window: TCP的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端正期望接收的字节
CheckSum: 检验和覆盖了整个的TCP报文段(TCP首部和TCP数据)。这是一个强制性的字段
Option:
Kind Length Meaning
---- ------ -------
0       -            End of option list.
1       -            No-Operation.
2       4           Maximum Segment Size.
抓包:

tcp/ip协议学习笔记(8)TCP传输控制协议

TCP连接的建立与关闭

建立:

1) 请求端(通常称为客户)发送一个SYN段指明客户打算连接的服务器的端口,以及初始序号(ISN,在这个例子中为100)。
2) 服务器发回包含服务器的初始序号的SYN报文段作为应答。同时,将确认序号设置为客户的ISN加1以对客户的SYN报文段进行确认。一个SYN将占用一个序号。
3) 客户必须将确认序号设置为服务器的ISN加1以对服务器的SYN报文段进行确认。

这个过程也称为三次握手(three-way handshake)

      TCP A                                                TCP B

  1.  CLOSED                                               LISTEN

  2.  SYN-SENT    --> <SEQ=100><CTL=SYN>               --> SYN-RECEIVED

  3.  ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK>  <-- SYN-RECEIVED

  4.  ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK>       --> ESTABLISHED

  5.  ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK><DATA> --> ESTABLISHED

          Basic 3-Way Handshake for Connection Synchronization

关闭:

建立一个连接需要三次握手,而终止一个连接要经过4次握手。这由TCP的半关闭(half-close)造成的。既然一个TCP连接是全双工(即数据在两个方向上能同时传递),因此每个方向必须单独地进行关闭。这原则就是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向连接。收到一个FIN只意味着在这一方向上没有数据流动。一个TCP连接在收到一个FIN后仍能发送数据。而这对利用半关闭的应用来说是可能的,尽管在实际应用中只有很少的。

  TCP A                                                    TCP B

  1.  ESTABLISHED                                          ESTABLISHED

  2.  (Close)
      FIN-WAIT-1  --> <SEQ=100><ACK=300><CTL=FIN,ACK>  --> CLOSE-WAIT

  3.  FIN-WAIT-2  <-- <SEQ=300><ACK=101><CTL=ACK>      <-- CLOSE-WAIT

  4.                                                       (Close)
      TIME-WAIT   <-- <SEQ=300><ACK=101><CTL=FIN,ACK>  <-- LAST-ACK

  5.  TIME-WAIT   --> <SEQ=101><ACK=301><CTL=ACK>      --> CLOSED

  6.  (2 MSL)
      CLOSED                                                      

                         Normal Close Sequence
2MSL:TIME-WAIT状态也称为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIMEWAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。
平静时间:TCP在重启动后的MSL秒内不能建立任何连接。这就称为平静时间(quiet time)。

同时打开

TCP是特意设计为了可以处理同时打开,对于同时打开它仅建立一条连接而不是两条连接

tcp/ip协议学习笔记(8)TCP传输控制协议

同时关闭

同时关闭与正常关闭使用的段交换数目相同

tcp/ip协议学习笔记(8)TCP传输控制协议

TCP状态变迁图

tcp/ip协议学习笔记(8)TCP传输控制协议

TCP服务器设计与实现

/********************************************************************************* 
  *Author     :  wph 
  *Version    :  1.0 
  *Date       :  2014/03/08   
  *Description:  tcp server  
  *Others     :  
  *History    :    
**********************************************************************************/ 
#include<stdio.h>  
#include<string.h>  
#include<unistd.h>  
#include<sys/types.h>  
#include<sys/socket.h>  
#include<stdlib.h>  
#include<netinet/in.h>  
#include<arpa/inet.h>

#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>

#include "errocode.h"
#include "basetype.h"

#define INVALID_FD      -1
#define PORT            1234  
#define MAXDATASIZE     512 
#define MAX_LINE        16384
#define BACKLOG         512

STATIC INT g_itcpFd   = INVALID_FD;

void readcb(struct bufferevent *bev, void *ctx)
{
    char buf[1024];
    int n;
    struct evbuffer *input = bufferevent_get_input(bev);
    struct evbuffer *output = bufferevent_get_output(bev);

    evbuffer_add_buffer(output, input);
}

void errorcb(struct bufferevent *bev, short events, void *ptr)
{
    if (events & BEV_EVENT_CONNECTED)
    {
         printf("Connect okay.\n");
    } else if (events & (BEV_EVENT_ERROR|BEV_EVENT_EOF)) {
         bufferevent_free(bev);
    }
}
VOID tcp_accept(evutil_socket_t listener, short what, void *arg)
{
    INT  iSockFd; 
    char buf[MAXDATASIZE]; 
    struct event_base *base = arg;
    struct sockaddr_in ss;
    socklen_t slen = sizeof(ss);

    iSockFd = accept(listener, (struct sockaddr*)&ss, &slen);
    if (iSockFd < 0)
    {
        perror("accept");
    } 
    else if (iSockFd > FD_SETSIZE) 
    {
        close(iSockFd);
    } 
    else 
    {
        struct bufferevent *bev;
        evutil_make_socket_nonblocking(iSockFd);
        bev = bufferevent_socket_new(base, iSockFd, BEV_OPT_CLOSE_ON_FREE);
        bufferevent_setcb(bev, readcb, NULL, errorcb, NULL);
        bufferevent_setwatermark(bev, EV_READ | EV_WRITE, 0, MAX_LINE);
        bufferevent_enable(bev, EV_READ | EV_WRITE);
        printf("You got a connection from client's ip is %s, port is %d\n",
               inet_ntoa(ss.sin_addr), htons(ss.sin_port));  
    }
}

ULONG tcp_init(VOID)
{
    INT sockfd = INVALID_FD;  
    struct sockaddr_in server;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(INVALID_FD == sockfd)   
    {  
        perror("Creatingsocket failed.");  
        return EROOR_FAILD;  
    } 
    
    bzero(&server, sizeof(server));  
    server.sin_family = AF_INET;  
    server.sin_port= htons(PORT);  
    server.sin_addr.s_addr= htonl(INADDR_ANY);  
    if(-1 == bind(sockfd, (struct sockaddr *)&server, sizeof(server)))  
    {  
        perror("Bind()error.");  
        close(sockfd);
        return EROOR_FAILD;
    }

    if(-1 == listen(sockfd, BACKLOG))
    {   
       perror("listen()error\n");  
       exit(1);  
    } 

    g_itcpFd   = sockfd;

    return EROOR_SUCCESS;
}

VOID tcp_fini(VOID)
{
    INT sockfd = g_itcpFd;  

    if (INVALID_FD != sockfd)
    {
        close(sockfd);
    }
}
VOID main_loop(VOID)
{
    INT ifd = g_itcpFd;
    struct event *ev1;
    struct event_base *base = event_base_new();

    ev1 = event_new(base, ifd, EV_TIMEOUT|EV_READ|EV_PERSIST, tcp_accept, (VOID *)base);

    event_add(ev1, NULL);
    event_base_dispatch(base);

    return ;
}
INT main()
{
    if(EROOR_SUCCESS != tcp_init())
    {
        return -1;
    }

    main_loop();

    tcp_fini();

    return 0;
}

/********************************************************************************* 
  *Copyright(C),2010-2011, 
  *Author     :  wph 
  *Version    :  1.0 
  *Date       :  2014/03/08   
  *Description:  tcp client  
  *Others     :  
  *History    :    
**********************************************************************************/ 
#include<stdio.h>  
#include<stdlib.h>  
#include<unistd.h>  
#include<string.h>  
#include<sys/types.h>  
#include<sys/socket.h>  
#include<netinet/in.h>  
#include<netdb.h>  

#include "basetype.h"

#define  PORT 1234  
#define  MAXDATASIZE 100  

INT main(INT argc, CHAR *argv[])  
{  
    INT   sockfd = -1;
    UINT  num = 0;  
    CHAR  buf[MAXDATASIZE];  
    struct hostent *he;  
    struct sockaddr_in server;  
    
    if (argc!=3)
    {  
        printf("Usage:%s <IP Address>\n",argv[0]);  
        exit(1);  
    }  
    
    if(NULL == (he=gethostbyname(argv[1])))
    {  
        printf("gethostbyname()error\n");  
        exit(1);  
    } 
    
    if(-1 == (sockfd=socket(AF_INET, SOCK_STREAM, 0))){  
        printf("socket()error\n");  
        exit(1);  
    }  
    
    bzero(&server,sizeof(server));  
    server.sin_family= AF_INET;  
    server.sin_port = htons(PORT);  
    server.sin_addr =*((struct in_addr *)he->h_addr);  
    if(connect(sockfd,(struct sockaddr *)&server,sizeof(server))==-1){  
        printf("connect()error\n");  
        exit(1);  
    } 

    send(sockfd, argv[2], strlen(argv[2])+1, 0);

    if(-1 == (num=recv(sockfd, buf, MAXDATASIZE,0)))
    {  
        printf("recv() error\n");  
        exit(1);  
    } 
    
    buf[num-1]='\0';  
    printf("Server Message: %s\n",buf);  
    close(sockfd); 
    
    return 0;  
}