1、浏览器访问网页的过程

1、回去DNS域名解析服务器,找到对应的ip地址 2、通过ip地址建立TCP连接 3、浏览器发送HTTP请求,服务器接受请求并处理,服务器返回,浏览器接受请求并渲染信息 4、

2、OSI七层模型

1.4 TCP/IP网络协议_三次握手

在这里插入图片描述

1.4 TCP/IP网络协议_服务器_02

在这里插入图片描述

1.4 TCP/IP网络协议_客户端_03

在这里插入图片描述

1.4 TCP/IP网络协议_服务器_04

在这里插入图片描述

1.4 TCP/IP网络协议_三次握手_05


在这里插入图片描述


TCP通信流程调用的函数:客户端

1、socket()创建socket 2、connect()服务器发送连接请求 3、连接成功之后就行read(),write()处理 4、close()关闭连接

#include <iostream>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>

int main()
{
// 1、创建socket
int nfd =socket(AF_INET,SOCK_STREAM,0);
// 准备服务端ip和端口地址
structsockaddr_in ser_addr;
    ser_addr.sin_family = AF_INET;
    ser_addr.sin_port =htons(8000);
inet_pton(AF_INET,"172.16.244.6",&(ser_addr.sin_addr.s_addr));

// 建立连接
connect(nfd,(struct sockaddr*)&ser_addr,sizeof(ser_addr));

// 连接成功之后先写数据
char buf[1024]="hello 服务器,我是客户端\n";
write(nfd,buf,sizeof(buf));

// 等待server回复
memset(buf,0,sizeof(buf));
read(nfd,buf,sizeof(buf));
printf("服务器发来信息:%s",buf);

// 断开连接
close(nfd);
return0;
}

服务端:

1、socket()创建socket 2、bing()ip地址和端口号 3、listen()设置同时与服务器建立连接上限数 4、accept()等待连接请求 5、有客户端连接之后,进行read(),write()处理 6、close()关闭连接

#include <iostream>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#define PORT 8000

int main()
{
// 1、创建socket
int nfd =socket(AF_INET,SOCK_STREAM,0);

// 2、绑定ip和端口
structsockaddr_in ser_addr,clit_addr;
    ser_addr.sin_family = AF_INET;
    ser_addr.sin_port =htons(PORT);
    ser_addr.sin_addr.s_addr =htonl(INADDR_ANY);
bind(nfd,(struct sockaddr*)&ser_addr,sizeof(ser_addr));

// 3、设置最大连接数
listen(nfd,128);

// 等待客户端连接
socklen_t clit_len =sizeof(clit_addr);
int ncfd;
char buf[1024]={0};
char ip_buf[16]={0};
    ncfd =accept(nfd,(struct sockaddr*)&clit_addr,&clit_len);
printf("%s:%d客户端连接成功!!!\n",
inet_ntop(AF_INET,&(clit_addr.sin_addr.s_addr),
            ip_buf,sizeof(ip_buf)),
ntohs(clit_addr.sin_port));

// 读信息
read(ncfd,buf,sizeof(buf));
printf("%s:%d客户端发来消息:%s",
inet_ntop(AF_INET,&(clit_addr.sin_addr.s_addr),
            ip_buf,sizeof(ip_buf)),
ntohs(clit_addr.sin_port),
            buf);
memset(buf,0,sizeof(buf));
strcpy(buf,"hello world\n");
// 写信息
write(ncfd,buf,sizeof(buf));

// 关闭socket
close(ncfd);
close(nfd);
return0;
}

3、TCP三次握手

TCP三次握手是指,客户端和服务器建立连接,一共发送3个数据包才能完成连接,客户端在调用connect函数时就会触发三次握手流程,

1.4 TCP/IP网络协议_三次握手_06

在这里插入图片描述

刚开始客户端处于 closed 的状态,服务端处于 listen 状态。然后 1、第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN(c)。此时客户端处于 SYN_Send 状态。2、第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN(s),同时会把客户端的 ISN + 1 作为 ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。3、第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 establised 状态。4、服务器收到 ACK 报文之后,也处于 establised 状态,此时,双方已建立起了链接。

说明:

(1)SYN=1 表示该报文不携带数据,但消耗一个序号 seq=x,seq=x是客户端的初始化序列号,因为tcp是面向字节流的 (2)SYN=1 表示该报文不携带数据,但消耗一个序号 seq=y,seq=y是服务器的初始化序列号,ACK=1是一个确认号 ack=x+1,表示服务器下次接收到的序号希望是x+1。然后服务器进入到SYN-RCVD等待的状态 (3)ACK=1是一个确认号,seq=x+1是上一次服务器回应的序号要求,ack=y+1表示客户下一次接收到的序号希望是y+1

三次握手的作用:三次握手的作用也是有好多的,多记住几个,保证不亏。例如:1、确认双方的接受能力、发送能力是否正常。2、指定自己的初始化序列号,为后面的可靠传送做准备。3、如果是 https 协议的话,三次握手这个过程,还会进行数字证书的验证以及加密密钥的生成。

单单这样还不足以应付三次握手,面试官可能还会问一些其他的问题,例如:

1、(ISN)是固定的吗?

三次握手的一个重要功能是客户端和服务端交换ISN(Initial Sequence Number), 以便让对方知道接下来接收数据的时候如何按序列号组装数据。

  如果ISN是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。

2、什么是半连接队列

服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。

      这里在补充一点关于SYN-ACK 重传次数的问题: 服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传,如果重传次数超 过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s, 2s, 4s, 8s, …

三次握手过程中可以携带数据吗

很多人可能会认为三次握手都不能携带数据,其实第三次握手的时候,是可以携带数据的。也就是说,第一次、第二次握手不可以携带数据,而第三次握手是可以携带数据的。

为什么这样呢?大家可以想一个问题,假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据,因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。也就是说,第一次握手可以放数据的话,其中一个简单的原因就是会让服务器更加容易受到攻击了。而对于第三次的话,此时客户端已经处于 established 状态,也就是说,对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据页没啥毛病。

当进行第一次握手,网络不好可能会堵塞,所以连接的请求并没有到达服务器端;但是tcp连接有超时重传的机制,所以再一次发送请求,这时候服务器端接收到了你的请求,他也会返回一个请求给你,这是第二次握手;但是这时候网络环境突然又好了起来,那个堵塞的请求到达了服务器端,服务器端又给你回了一个请求,但是你又不想给服务器发送请求,这时候服务器的资源会进行占用等待你的请求,为了不使服务器的资源继续占用,你又必须发送一个请求给服务器;所以要进行3次握手

转载:https://www.cnblogs.com/zzjdbk/p/13028290.html

4、为什么是三次握手不是二次

(1)三次握手目的是确认双方的接收与发送能力是否正常,同步连接双方的初始化序列号 ISN,为后面的可靠性传输做准备。而两次握手只有服务端对客户端的起始序列号做了确认,但客户端却没有对服务端的初始序列号做确认,不能保证传输的可靠性。(2)三次握手可以防止已失效的连接请求报文段突然又传送到了服务端,导致服务器错误地建立连接,浪费服务端的连接资源。

如果客户端发出的第一个连接请求报文并没有丢失,而是在某个网络节点长时间的滞留了,以致延误到连接释放以后的某个时间才到达服务器侧。本来这是一个早已失效的报文,但服务器收到此失效的连接请求报文后:

(1)假设不采用“三次握手”,那么只要Server发出确认,新的连接就建立了。但由于现在Client并没有发出建立连接的请求,因此不会理睬Server的确认,也不会向Server发送数据。而Server却以为新的连接已经建立,并一直等待Client发来数据,这样,Server的很多资源就白白浪费掉了 (2)采用“三次握手”协议,只要Server收不到来自Client的确认,就知道Client并没有要求建立请求,就不会建立连接了。

5、四次挥手

数据通信结束后,通信的双方都可以释放连接,将连接关闭掉。连接关闭时将会走四次挥手的流程如下图所示:

1.4 TCP/IP网络协议_服务器_07

在这里插入图片描述

1、第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1状态。2、第二次握手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 + 1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态。3、第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。4、第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 + 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态 5、服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。

这里特别需要的就是TIME_WAIT这个状态了,这个是面试的高频考点,就是要理解,为什么客户端发送 ACK 之后不直接关闭,而是要等一阵子才关闭。这其中的原因就是,要确保服务器是否已经收到了我们的 ACK 报文,如果没有收到的话,服务器会重新发 FIN 报文给客户端,客户端再次收到 ACK 报文之后,就知道之前的 ACK 报文丢失了,然后再次发送 ACK 报文。至于 TIME_WAIT 持续的时间至少是一个报文的来回时间。一般会设置一个计时,如果过了这个计时没有再次收到 FIN 报文,则代表对方成功就是 ACK 报文,此时处于 CLOSED 状态。

6、为什么是四次挥手,不能是三次挥手吗

如果只是三次挥手,那就相当于服务器端发送完第三次挥手的报文后直接进入CLOSED(关闭)状态,假如此时网络出现问题,丢失了第三次挥手的报文,相当于客户端没有收到,那他依旧认为连接没有结束,在一段时间没有收到第二次ACK应答报文后,他会重新发送请求断开连接的报文,但是服务器端已经关闭,不会再接收报文,又形成了类似死锁的情况。

7、四次挥手中为什么要有CLOSE-WAIT状态和TIME-WAIT状态

对于CLOSE-WAIT状态,因为服务器端收到断开TCP连接请求时,有可能还有数据没有向客户端发送完毕,需要一段时间来把所有信息传输完毕。

对于TIME-WAIT状态,假设客户端发送完第四次挥手的报文后,直接进入CLOSED(关闭)状态。那么假设此时网络出现问题,报文丢失,那么因为服务器端收不到第四次挥手的ACK报文段,所以认为此时TCP连接还没有断开。然后重发ACK+FIN报文段,但此时客户端已经关闭与其的TCP连接,肯定不会再接收该报文,这样会浪费大量资源。

8、什么情况下会出现大量的TIME-WAIT状态?该怎么解决

在高并发且短连接的通信情况下,服务器会出现大量TIME-WAIT状态,这占用了大量的socket,会影响服务器的正常通信服务。解决办法有:

(1)降低time_wait的时限;(2)设置中允许重用time_wait的socket;(3)设置快速回收time_wait的socket。

9、telnet工具使用

telnet ip 端口号

部分内容转载:https://blog.csdn.net/chenlycly/article/details/127067195

10、TCP状态转换图

1.4 TCP/IP网络协议_服务器_08

在这里插入图片描述

我们用两个客户端连接到服务器,服务器给每个客户端发一条信息之后,close掉客户端,我们通过netstat观察状态,虽然close掉这两个客户端,但是产生了两个TIME_WAIT状态信息,一旦有了这个信息,重新启动就会失败bind函数会失败,四次挥手主动方就会产生这个状态。(1-4分钟,2个MSL 最长数据包生命周期)

RST标志:(一般是异常关闭的时候)

正常关闭:对于每一个TCP连接,操作系统都会开辟一个发缓存区和一个收缓存区,当我们close一个TCP连接,如果我们发缓存区有数据,他就会很优雅的将数据发送出去,然后发fin包表示关闭。异常关闭:一般都会导致丢失一些包,此时发送缓存区会丢弃。

11、setsockopt函数

用在服务端,socket创建之后,bind创建之前,解决TIME_WAIT

SO_REUSEADDR能力:
1、即便TIME_WAIT存在,服务器bind也能成功
2、允许通过端口上启动多个实例,只要ip地址不一样就行
3、SO_REUSEADDR允许单个进程捆绑同一个端口到多个套接字,只要每次绑定不同ip
4、SO_REUSEADDR允许完全重复绑定
同样的ip地址和端口还可以绑定到另一个套接字上,一般UDP。
int reuseaddr = 1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(const void*)&reuseaddr,sizeof(int));

12、listen函数,监听套接字队列

1.4 TCP/IP网络协议_客户端_09

在这里插入图片描述

对于一个调用listen()进行监听的套接字,操作系统会给套接字维护两个队列 未完成的连接的队列:【保存连接用】 当客户端发送tcp三次握手的第一次【syn包】给服务器,服务器会在未完成队列中创建一个跟这个syn对应一项,其实我们可以把这项看成一个半连接,这个半连接将LISTEN状态设置为SYN_RCVD状态,同时给客户端返回第二次握手包【syn+ack包】,服务器等待完成第三次握手。已完成连接队列:【保存连接用】 当三次握手完成,这个连接就完成ESTABLISH状态,每个完成三次握手的都放在这个队列中

曾经listen第二个参数,已完成和未完成队列和不能操作第二个参数数 后来修改成:已完成队列中最多多少。

RTT是

13、accept函数

就是从已完成队列,队头位置取出一项,每一项都是一个已完成三次握手的TCP连接,返回给进程,如果已完成连接队列是空的,accept会一直卡顿等待休眠,一直到已完成队列中有一项才会唤醒

1、监听套接字,一直都在监听这个端口 2、客户端连接进来,进行三次握手的时候就会创建一个套接字,没完成之前放在未完成队列,完成三次握手之后放在完成队列中,accept返回的就是这个套接字

14、connect函数什么时候返回

客户端收到第二次握手包时就已经返回了(服务器发来的syn+ack)

15、思考题

1、如果两个队列之和达到listen第二参数,也就是队列满了,此时,再有一个客户端连接发送syn包时,服务器反应?服务器会忽略这个syn包,客户端发现syn没有回应,会再次发送,发送几次就会connect失败 2、三次握手完成,从已完成队列中取走的时候,此时要是客户端发来了消息,这个数据就会被保存在这个已连接的套接字的收缓存区里,缓存区多大就能存多少数据。

16、syn攻击

虚拟出来一些ip和port给服务器发送syn包,当服务器想给你syn+ack包时,因为你这个ip是不存在的所以就无法完成连接,就会导致大量的未完成连接填满你的未完成队列。