前言:
TCP协议被广泛应用 其根本原因就是提供了详尽的可靠性保证 基于TCP的上层应用非常多 比如HTTP、HTTPS、FTP、SSH、MySQL等。TCP是一种面向连接的单播协议,在发送数据前,通信双方必须在彼此间建立一条连接。所谓“连接”,其实是客户端和服务器端内存里保持的一份关于对方的信息(IP地址、端口号)下面让我们学习一下什么是TCP协议吧
下图是一次TCP通讯的时序图。TCP连接建立断开。包含大家熟知的三次握手和四次握手。
三次握手建立连接、四次握手根据半关闭断开连接
1)客户端向主机端发送申请建立连接
2)主机端向客户端发送接收到申请请求并确定连接
3)客户端接收到主机端并确定是最近的一次确定连接申请后,确定连接
主动发起连接请求端,发送 SYN 标志位,请求建立连接。 携带序号号、数据字节数(0)、滑动窗口大小。
被动接受连接请求端,发送 ACK 标志位,同时携带 SYN 请求标志位。携带序号、确认序号、数据字节数(0)、滑动窗口大小。
主动发起连接请求端,发送 ACK 标志位,应答服务器连接请求。携带确认序号。
1)客户端向主机端发送申请断开连接
2)主机端向客户端发送信息,说明已经接收到客户端的请求
3)待主机端处理完线路上两者之间的数据后主动推送数据给客户端代表此时已经处理完所有数据
4)客户端确定后,最后确定并释放自己的连接
主动关闭连接请求端, 发送 FIN 标志位。
被动关闭连接请求端, 应答 ACK 标志位。 ----- 半关闭完成
被动关闭连接请求端, 发送 FIN 标志位。
主动关闭连接请求端, 应答 ACK 标志位。 ----- 连接全部关闭
注意:不能三次挥手的原因:第四次挥手用于确定主机的数据已经发送完毕的确认
介绍UDP时我们描述了这样的问题:如果发送端发送的速度较快,接收端接收到数据后处理的速度较慢,而接收缓冲区的大小是固定的,就会丢失数据。TCP协议通过“滑动窗口(Sliding Window)”机制解决这一问题。看下图的通讯过程:
上图在接收端用小方块表示1K数据,实心的小方块表示已接收到的数据,虚线框表示接收缓冲区,因此套在虚线框中的空心小方块表示窗口大小,从图中可以看出,随着应用程序提走数据,虚线框是向右滑动的,因此称为滑动窗口。
发送给连接对端,本端的缓冲区大小(实时),保证数据不会丢失
结合三次握手、四次挥手 理解记忆。
CLOSE --》 发送SYN --》 SEND_SENT --》 接收 ACK 、SYN --》 SEND_SENT --》 发送 ACK --》 ESTABLISHED(数据通信态)
#include
#include
#include
#include
#include
#include
#include
#include
#include "wrap.h"
#define MAXLINE 8192
#define SERV_PORT 8000
void do_sigchild(int num)
{
while (waitpid(0, NULL, WNOHANG) > 0)
;
}
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;
pid_t pid;
struct sigaction newact;
newact.sa_handler = do_sigchild;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGCHLD, &newact, NULL);
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
Listen(listenfd, 20);
printf("Accepting connections ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("-------------------------%d\n", connfd);
pid = fork();
if (pid == 0) {
Close(listenfd);
while (1) {
n = Read(connfd, buf, MAXLINE);
if (n == 0) {
printf("the other side has been closed.\n");
break;
}
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
Write(STDOUT_FILENO, buf, n);
Write(connfd, buf, n);
}
Close(connfd);
return 0;
} else if (pid > 0) {
Close(connfd);
} else
perr_exit("fork");
}
return 0;
}
/* client.c */
#include
#include
#include
#include
#include
#include "wrap.h"
#define MAXLINE 8192
#define SERV_PORT 8000
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (fgets(buf, MAXLINE, stdin) != NULL) {
Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if (n == 0) {
printf("the other side has been closed.\n");
break;
}
else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0;
}
ESTABLISHED(数据通信态) --》 发送 FIN --》 FIN_WAIT_1 --》 接收ACK --》 FIN_WAIT_2(半关闭)
接收对端发送 FIN --》 FIN_WAIT_2(半关闭) --》 回发ACK -- 》 TIME_WAIT(只有主动关闭连接方,会经历该状态) --》 等 2MSL时长 --》 CLOSE
CLOSE --》 LISTEN -- 》 接收 SYN --》 LISTEN -- 》 发送 ACK、SYN --》 SYN_RCVD --》 接收ACK --》 ESTABLISHED(数据通信态)
ESTABLISHED(数据通信态) --》 接收 FIN --》 ESTABLISHED(数据通信态) --》 发送ACK --》 CLOSE_WAIT (说明对端【主动关闭连接端】处于半关闭状态) --》 发送FIN --》 LAST_ACK --》 接收ACK -- 》 CLOSE
重点记忆: ESTABLISHED、FIN_WAIT_2 <--> CLOSE_WAIT、TIME_WAIT(2MSL)
netstat -apn | grep 端口号
一定出现在【主动关闭连接请求端】。 --- 对应 TIME_WAIT 状态。
保证,最后一个 ACK 能成功被对端接收。(等待期间,对端没收到我发的ACK,对端会再次发送FIN请求。)