TCP介绍

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

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

TCP通过下列方式来提供可靠性:

  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报文格式

img

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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.

通过wireshark抓包:

img点击并拖拽以移动编辑

TCP连接的建立与关闭

连接建立过程:

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
      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后仍能发送数据。而这对利用半关闭的应用来说是可能的,尽管在实际应用中只有很少的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
  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是特意设计为了可以处理同时打开,对于同时打开它仅建立一条连接而不是两条连接

img

同时关闭

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

img

TCP状态变迁

TCP设计了一套非常复杂的状态机来保证协议的正常运转,如下TCP状态变迁图,状态的变迁是依赖收到的报文进行驱动的的。

img

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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/********************************************************************************* 
  *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;
}
 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
/********************************************************************************* 
  *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;  
}