尽管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
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 -->3. ESTABLISHED <-- --> SYN-RECEIVED 4. ESTABLISHED --> <-- SYN-RECEIVED 5. ESTABLISHED --> --> ESTABLISHED --> 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 -->--> CLOSE-WAIT 3. FIN-WAIT-2 <-- <-- CLOSE-WAIT 4. (Close) TIME-WAIT <-- <-- LAST-ACK 5. TIME-WAIT --> --> 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#include
#include
#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 \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;
}