ToB企服应用市场:ToB评测及商务社交产业平台
标题:
Linux网络编程二(TCP图解三次握手及四次挥手、TCP滑动窗口、MSS、TCP状态转
[打印本页]
作者:
美丽的神话
时间:
2024-7-28 05:19
标题:
Linux网络编程二(TCP图解三次握手及四次挥手、TCP滑动窗口、MSS、TCP状态转
1、TCP三次握手
TCP三次握手(TCP three-way handshake)是TCP协议建立可靠毗连的过程,确保客户端和服务器之间可以举行可靠的通讯。下面是TCP三次握手的
具体过程
:
假设客户端为A,服务器为B。
SYN---> ACK + SYN --->ACK
复制代码
(1) 第一次握手
第一次握手(SYN=1,seq=500)
A向B发送一个带有
SYN
标志位的数据包,表现A哀求建立毗连。SYN标志位为1表现这是一个毗连哀求数据包,500是A随机选择的初始序列号。
(2) 第二次握手
第二次握手(SYN=1,ACK=1,ack=500+1,seq=800):
B接收到A发送的毗连哀求后,会向A回复一个数据包。该数据包中,SYN和ACK标志位都被设置为1。
ACK=1
表现B确认收到了A的毗连哀求,ack字段的值为A的初始序列号加1,表明B盼望下一个收到的序列号是A初始序列号加1。seq字段800是B随机选择的初始序列号。
(3) 第三次握手
第三次握手(ACK=1,ack=800+1):
A收到B的回复后,查抄ACK标志位是否为1,以及ack字段的值是否为B的初始序列号加1。如果精确,A会向B发送一个确认数据包。在该数据包中,ACK标志位被设置为1,表现A确认收到了B的回复。ack字段的值是B的初始序列号加1,表明A盼望下一个收到的序列号是B初始序列号加1。
完成这三次握手后,TCP毗连就建立成功,A和B之间可以开始传输数据。毗连的状态变为已建立(ESTABLISHED)。
三次握手是操作体系内核(Kernel)的TCP协议栈负责处置惩罚。
用户层的表现:服务器端是accept(),客户端是connect(),其这两个函数成功执行并返回了。
2、TCP四次挥手
TCP四次挥手(TCP four-way handshake)是TCP毗连的
关闭过程
,用于在客户端和服务器之间终止一个已建立的毗连。与TCP三次握手不同,四次挥手须要举行四个步骤来关闭毗连,以确保数据传输的完整性和可靠性。
FIN--->ACK FIN--->ACk
复制代码
(1) 一次挥手
客户端向服务器发送毗连释放哀求(FIN)的数据包。
客户端希望关闭毗连,因此发送一个带有FIN标志位的数据包,FIN=1表现毗连释放哀求。设置序列号为seq=501。
(2) 二次挥手
服务器接收到客户端的毗连释放哀求后,回复确认毗连释放(ACK)的数据包。
服务器收到客户端的FIN后,发送一个带有ACK标志位的数据包,ACK=1,ack 502表现确认收到客户端的毗连释放哀求。
(3) 三次挥手
服务器向客户端发送毗连释放哀求(FIN)的数据包。
服务器希望关闭毗连,因此发送一个带有FIN标志位的数据包,FIN=1表现毗连释放哀求。设置序列号为seq=701。
(4) 四次挥手
客户端接收到服务器的毗连释放哀求后,回复确认毗连释放(ACK)的数据包。
客户端收到服务器的FIN后,发送一个带有ACK标志位(这个不是数据,是控制报文),ACK=1,ack 702表现确认收到服务器的毗连释放哀求。
在发送完ACK后,客户端
等候一段时间
,确保服务器收到了ACK,然后完全关闭毗连。
4、经典题目增补
为什么是三次握手,两次握手或者四次握手不可以吗?
答:
1、如果两次握手,客户端发送建立毗连哀求,由于网络阻塞,服务器端未回应。客户端再次发送哀求,服务端回应建立毗连。一段时间后,第一次发送的到达服务器端,服务器以为客户端重新哀求建立毗连(其实并没有),此时服务端会返反相应报文并不停处于待毗连状态,这就造成了资源浪费。
2、TCP建立的毗连时二次握手过程中,客户端发送一个SYN哀求帧,Server收到后发送了确认应答SYN+ACK帧。按照两次握手的协定,Server以为毗连已经成功地建立了,可以开始发送数据帧。这个过程中,如果确认应答SYN+ACK帧在传输中被丢失,Client没有收到,Client将不知道Server是否已准备好,也不知道Server的SN序列号,Client以为毗连还未建立成功,将忽略Server发来的任何数据分组,会不停等候Server的SYN+ACK确认应答帧。而Server在发出的数据帧后,不停没有收到对应的ACK确认后就会产生超时,重复发送同样的数据帧。这样就形成了死锁
为什么是四次挥手,三次挥手不可以吗?
答:不可以。当客户端发送断开毗连哀求后,停止发送数据(客户端还能接收数据),有可能此时服务端尚有数据须要发给客户端,以是它先回一个确认报文ACK,等发送完所有数据,再发送断开毗连的报文FIN,通知客户端可以断开毗连了。
四次挥手竣事后,为什么客户端没有立刻关闭呢?
客户端没有立刻关闭,而是进入TIME_WAIT状态,等候2个MSL的时长后,客户端才进入CLOSE状态,这是为了确保第四次挥手的确认消息到达服务端。
如果服务端在规定时间内未收到最后的确认消息,会重新举行第三次挥手哀求断开毗连,客户端重新发送确认消息。
3、TCP滑动窗口
TCP滑动窗口是TCP协议中的一个紧张概念,用于实现
流量控制
和
可靠性传输
。滑动窗口机制允许发送方和接收方在数据传输过程中动态调整可发送和可接收的数据量,从而顺应不同的网络条件和接收方的处置惩罚能力。每次通讯时,接收方利用win(4096)告知发送方缓冲区剩余巨细。
MSS(Maximum Segment Size)是指TCP数据包中的最大有用载荷巨细,它表现在TCP协议中一次性发送的最大数据量(即数据包中的有用数据部门,不包括TCP头部和IP头部)。
在TCP毗连建立时,通过TCP三次握手的过程中,双方会交换相互的MSS值,然后根据两端通讯的网络链路的MTU巨细举行协商,确定实际利用的MSS。
MSS = 1500 - 20 (TCP头部) - 20 (IP头部) = 1460 字节
复制代码
这意味着在该TCP毗连中,
一次可以发送的最大有用数据量为1460字节
,凌驾这个巨细的数据将被拆分成多个TCP数据包举行传输。
MSS的设置对于TCP性能和网络吞吐量很紧张。公道设置MSS可以克制网络分段和数据重组,进步数据传输效率,特别是在一些高延迟、低带宽的网络情况中。
4、TCP状态时序图
利用命令查察状态
netstat -aptn | grep 端口号 #查看tcp端口
复制代码
netstat -apn | grep 端口号 #查看tcp、udp端口
复制代码
1、主动发起毗连哀求端
CLOSE----发送 SYN—SYN_SEND—接收 ACK、SYN—发送 ACK-ESTABLISHED(数据通讯态)
2、主动关闭毗连哀求端
ESTABLISHED(数据通讯态)—发送 FIN—FIN_WAIT_1 --接收 ACK --FIN_WAIT_2(半关闭)—接收对端发送 FIN—FIN_WAIT_2(半关闭)—回发ACK–TIME_WAIT(只有主动关闭毗连方,会履历该状态)—等2MSL时长—CLOSE
3、被动接收毗连哀求端
CLOSE—LISTEN—接收 SYN—LISTEN—发送 ACK、SYN—SYN_RCVD—接收ACK—ESTABLISHED(数据通讯态)
4、被动关闭毗连哀求端
ESTABLISHED(数据通讯态)—接收 FIN —ESTABLISHED(数据通讯态)— 发送ACK — CLOSE_WAIT(说明对端【主动关闭毗连端】处于FIN_WAIT_2(半关闭)状态—发送FIN —LAST_ACK—接收ACK—CLOSE
重点:ESTABLISHED(数据通讯态)、FIN_WAIT_2、CLOSE_WAIT、TIME_WAIT(2MSL).
先启动服务器,只有LISTEN状态。
启动客户端,此时三次握手建立完成,进入ESTABLISHED(数据通讯态)。
尝试关闭一个客户端,此时该客户端进入TIME_WAIT状态。
服务器先主动关闭,服务器进入FIN_WAIT_2(半关闭)状态。客户端进入CLOSE_WAIT状态。
此时敏捷关闭客户端,客户端处于TIME_WAIT状态。
提示:#include "wrap.h"错误处置惩罚函数,已经封装
错误处置惩罚函数
5、多历程并发服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <pthread.h>
#include <signal.h>
#include <sys/wait.h>
#include "wrap.h" //错误处理函数,已经封装
//https://blog.csdn.net/qq_45009309/article/details/131813756?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171204506416800184170823%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=171204506416800184170823&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-131813756-null-null.nonecase&utm_term=%E9%94%99%E8%AF%AF&spm=1018.2226.3001.4450
#define SRV_PORT 9999
void catch_child(int signum) //回调函数 内核操作 产生信号后进来
{
while(waitpid(0, NULL, WNOHANG) > 0); //非阻塞回收子进程
//循环回收是因为可能产生多个子进程死亡
return ;
}
int main(int argc, char* argv[])
{
int lfd, cfd;
pid_t pid;
int ret;
char buf[BUFSIZ];
struct sockaddr_in srv_addr, clt_addr;
socklen_t clt_addr_len;
//memset(&srv_addr, 0, sizeof(srv_addr)); //地址结构清零
bzero(&srv_addr, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(SRV_PORT);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = Socket(AF_INET, SOCK_STREAM, 0); //创建套接字 返回用于监听的文件描述符
Bind(lfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)); //绑定服务器IP+地址
Listen(lfd, 128); //设置监听上线
clt_addr_len = sizeof(clt_addr);
while (1) {
cfd = Accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len); //返回用于双方通信的文件描述符
pid = fork(); //创建子进程
if (pid < 0) {
perr_exit("fork error");
}
else if (pid == 0) {
close(lfd);
break;
}
else { //父进程使用信号捕捉回收子进程
struct sigaction act;
act.sa_handler = catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
ret = sigaction(SIGCHLD, &act, NULL);
if (ret != 0) {
perr_exit("sigaction error");
}
close(cfd);
continue;
}
}
if (pid == 0) { //子进程实现读写功能
for (;;) {
ret = Read(cfd, buf, sizeof(buf));
for (int i = 0; i < ret; i++) {
buf[i] = toupper(buf[i]);
}
write(cfd, buf, ret);
write(STDOUT_FILENO, buf, ret); //实现大小写转换功能
if (ret == 0) {
close(cfd);
exit(1);
}
}
}
return 0;
}
复制代码
6、多线程并发服务器
//头文件同上
#define MAXLINE 8192
#define SERV_PORT 8000
struct s_info { //定义一个结构体,将地址结构跟cfd捆绑
struct sockaddr_in cliaddr;
int connfd;
};
void* do_work(void* arg) //子线程主调函数
{
int n, i;
struct s_info* ts = (struct s_info*)arg;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN]; //16
while (1) {
n = Read(ts->connfd, buf, MAXLINE);
if (n == 0) {
printf("the client %d closed...\n", ts->connfd);
break;
}
printf("received from %s at PORT %d\n", //打印客户端IP+端口
inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
ntohs((*ts).cliaddr.sin_port));
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
Write(STDOUT_FILENO, buf, n);
Write(ts->connfd, buf, n); //回写到客户端
}
Close(ts->connfd);
return (void*)0;
}
int main(int argc, char* argv[])
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
pthread_t tid;
struct s_info ts[256]; //创建结构体数组
int i = 0;
listenfd = Socket(AF_INET, SOCK_STREAM, 0); //创建一个socket,得到lfd
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT); //指定端口号
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //指定本地任意IP
Bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)); //绑定
Listen(listenfd, 128);
printf("Accepting client connect...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len); //阻塞监听客户端链接请求
ts[i].cliaddr = cliaddr;
ts[i].connfd = connfd;
pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
pthread_detach(tid); //子线程分离,防止僵尸线程产生
i++;
}
return 0;
}
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4