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

来源:互联网 发布:二手数码淘宝店铺 编辑:程序博客网 时间:2024/05/17 06:16

    尽管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 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连接的建立与关闭

建立:

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状态变迁图


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         512STATIC 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;  } 













0 1
原创粉丝点击