尽管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
URG: 紧急指针有效
ACK: 确认序号有效
PSH: 接收发应尽快将该报文交给应用层
RST: 重新连接
SYN: 同步序号用来发起一个连接
FIN: 发端完成发送任务
CheckSum: 检验和覆盖了整个的TCP报文段(TCP首部和TCP数据)。这是一个强制性的字段
---- ------ -------
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
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 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; }