1. TCP三次握手
TCP是一种面向连接的安全的流式传输协议,TCP报文的格式如下
TCP在建立连接的时候需要进行三次握手(TCP握手时一定有SYN标志,不带SYN标志的为建立连接后的正常数据传输)
至此,三次握手成功,双向连接均已建立,可以开始数据传输了。示意图如下:
2. TCP四次挥手
TCP断开连接时需要进行四次挥手:
四次挥手的过程如下:
3. TCP连接与数据传输过程
首先看一个TCP连接与数据传输的示意图
在图中:
- 1-3:三次握手阶段(握手阶段一定有SYN标志)
- 4-6:数据传输阶段
- 7-10:四次挥手阶段(挥手阶段一定有FIN标志)
图中的<mss 1460>表示最大数据长度,即告知对端给我发送数据的时候不要超过这个最大长度。
详细分析上述过程的完整图示如下
4. TCP滑动窗口机制
首先看滑动窗口的示意图
在图中,发送端速度快,接收端速度慢,一般来说谁先发送SYN谁就是客户端,因为客户端总是主动连接服务端,而服务端则被动等待客户端的连接。
在TCP中,滑动窗口实际上就是一块缓冲区(缓存)。在上图中,客户端与服务端进行数据传输的时候总是带有一个win 4096或win 6144等标志,这个win就代表滑动窗口的意思,而后面的数字则代表滑动窗口所表示的缓存区的大小。比如客户端发起的第一条握手请求,即fast sender所代表的1处,SYN, 0(0), win 4096, <mss 1460>
通过server端发送的 win 6144, <mss 1024> 可知,服务端滑动窗口大小为6KB,一次可以接受1KB数据。client端发送数据的速度是要快于server端接收数据的速度的,所以client端会发送多条数据,其中每条数据为1KB(由server端的mss指定最大接收长度),总共发送的数据不能超过6KB(server端的滑动窗口大小)。所以上图中client端总共发送了6条数据,每条数据长度为1KB(可见图中序号2-9数据传输),正好大小总共为6KB,等于server端窗口大小(实际上是server端缓冲区的大小)。
此时,server端的缓冲区已经满了,不能再接收数据了,只有当server端把数据读出的时候,缓冲区才能空出位置接受新数据。当server端读取了2K数据,那么server端的缓冲区将空余出2K字节,请见上图中的序号10处,server端向client端回复 ACK,6145, win 2048,表示server端收到了6144字节(6KB)数据,6145是由6144+1来的,win 2048表示server端现在缓冲区空余2K空间。当server端再次读出2K数据的时候,server再次向client发送一条数据 ACK,6145, win 4096,这里ACK和确认序号不变,win所代表的窗口大小发生了变化,变成了当前空余的缓冲区大小4KB,可见上图中序号11所代表的数据传输,此时在server发送数据10和11之间,client端并没有发送数据,但是server端要向client端告知自己的空闲缓冲区大小。
最后,12-18的过程实际上就是四次挥手的过程,client发送FIN断开连接,此时server还没有处理完缓冲区中的数据,所以每处理一批数据都会向client发送一条信息并告知缓冲区剩余大小,直到缓冲区中的数据全部处理完毕,server向client发送FIN断开连接。实际上滑动窗口就是缓冲区的大小,并且在发送数据过程中,并不是client发一条server就必须收一条,也可以发多条收多条,这是因为TCP是流式传输,一端发送的数据虽然没有立即被处理,但是已经存起来了,就存在了滑动窗口所表示的缓冲区中,当缓冲区满了,发送端就会临时阻塞,等待接收端缓冲区出现剩余空间。
上面所描述的过程都是所有数据传输都成功的前提下进行的,实际上,每条数据的发送都可能会失败,当发生传输失败的情况,TCP会进行重传,重传的示意图如下:
5. server服务端与client客户端编程实现
server.c
/************************************************************
>File Name : server.c
>Author : Mindtechnist
>Company : Mindtechnist
>Create Time: 2022年08月14日 星期日 19时53分24秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>
int main(int argc, char* argv[])
{
//创建用于监听的套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd == -1)
{
perror("socket err");
exit(1);
}
//bind
struct sockaddr_in server_addr;
//init
memset(&server_addr, 0, sizeof(server_addr));
//bzero(&server_addr, sizeof(serve_addr));
server_addr.sin_family = AF_INET; //IPv4
server_addr.sin_port = htons(8765);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //使用本机任意IP
int ret = bind(lfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if(ret == -1)
{
perror("bind err");
exit(1);
}
//设置监听
ret = listen(lfd, 128);
if(ret == -1)
{
perror("listen err");
exit(1);
}
//等待并接受连接请求
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int cfd = accept(lfd, (struct sockaddr*)&client_addr, &client_len);
if(cfd == -1)
{
perror("accept err");
exit(1);
}
char ipbuf[64];
printf("client ip: %s, port: %d\n",
inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
ntohs(client_addr.sin_port));
//通信
while(1)
{
//接收数据
char buf[1024] = {0};
int len = read(cfd, buf, sizeof(buf));
if(len < 0)
{
perror("read err");
break;
}
else if(len == 0)
{
printf("client disconnect ...\n");
break;
}
else
{
//读到数据
printf("read buf : %s\n", buf);
for(int i = 0; i < len; i++)
{
buf[i] = toupper(buf[i]);
}
printf("toupper read buf: %s\n", buf);
//发送数据
write(cfd, buf, strlen(buf) + 1);
}
}
close(lfd);
close(cfd);
return 0;
}
client.c
/************************************************************
>File Name : client.c
>Author : Mindtechnist
>Company : Mindtechnist
>Create Time: 2022年08月15日 星期一 11时13分29秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <fcntl.h>
int main(int argc, char* argv[])
{
if(argc < 2)
{
printf("err:./exe port\n");
return -1;
}
int port = atoi(argv[1]);
//创建套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1)
{
perror("socket err");
exit(1);
}
//连接服务器
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr.s_addr);
int ret = connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if(ret == -1)
{
perror("connect err");
exit(1);
}
//通信
while(1)
{
//接收键盘输入
char buf[512];
printf("please input string: \n");
fgets(buf, sizeof(buf), stdin);
//发送给服务器
write(fd, buf, strlen(buf));
//接收服务端数据
int len = read(fd, buf, sizeof(buf));
if(len < 0)
{
perror("read err");
exit(1);
}
else if(len == 0)
{
printf("server close connect ...\n");
break;
}
else
{
printf("read buf: %s, buflen: %d\n", buf, len);
}
}
close(fd);
return 0;
}