有哪些底层api接口
服务器端
- socket
- bind
- listen
- accept
- recv
- send
- close
客户端
epoll
- epoll_create
- epoll_ctl
- epoll_wait
TCP的三个过程
建立连接
tcp和udp的connect函数的作用
tcp的connect 只有在服务器的状态是listen时,才能发送
udp的connect只是验证一下这条路径是否通畅,不会建立连接
三次握手发送在哪个函数
客户端:accept函数
服务器:listen和accept函数之间
半连接队列和全连接队列
当服务器绑定、监听了指定端口后,内核通常会为每一个LISTEN状态的socket维护两个队列:
- SYN队列(半连接队列):由/proc/sys/net/ipv4/tcp_max_syn_backlog指定,表示处于 SYN_RECV 状态的队列
- ACCEPT队列(全连接队列):由listen()函数的第二个参数 backlog 指定,内核硬限制由 net.core.somaxconn 限制,即实际的值由min(backlog,somaxconn) 来决定。表示已完成连接的队列,等待被 accept系统调用取走。
调用listen函数,会发生什么
服务器端将fd设置为listen状态,此时才能进行三次握手,第二个参数是syn半连接队列的长度,现在指的是三次握手完成后的队列,也就是全连接队列。
三次握手细节
客户端调用connect函数,就把IP地址端口号,copy到协议栈中,协议栈自身会准备一个包syn包,发给对端
服务器收到以后,将节点数据保存在syn半连接队列中,返回客户端一个ack syn包
客户端发送ack后,服务器将半连接队列中的节点移动到accept全连接队列中,该节点被称为TCB控制块。
第一次握手时,服务端需要在TCB中保存客户端的连接
第三次握手时,判断客户端数据是否存在半连接队列中,若存在,移动到全连接队列中。
服务器调用accept函数,做了什么
- 从全连接队列中取出一个连接节点
- 把节点分配一个fd ,返回fd
服务器调用accept时,全连接队列中没有TCB控制块会怎么样
如果全连接队列中没有连接节点,就会进入阻塞等待
如果listenfd设置为非阻塞,判断全连接队列中是否有连接节点,没有则返回
SYN队列满会发生什么情况
如果SYN队列满,则会直接丢弃连接请求。 比如syn floods 攻击就是针对半连接队列的,攻击方不停地建连接,但是建连接的时候只做第一步,第二步中攻击方收到server的syn+ack后故意扔掉什么也不做,server需要一个超时时间把这个连接断开,否则大量这样的连接导致server上这个队列满其它正常请求无法进来。
ACCEPT队列满会发生什么情况
如果ACCEPT队列满了,server 通过 /proc/sys/net/ipv4/tcp_abort_on_overflow 来决定如何返回:
- tcp_abort_on_overflow 为 0,不会把连接从SYN队列中移除,server过一段时间再次发送syn+ack给client(也就是重新走握手的第二步),这样来回重发几次,次数由 /proc/sys/net/ipv4/tcp_synack_retries(centos默认为 5 ) 指定,如果三次握手第三步的时候 ACCEPT 队列一直是满,那么server扔掉client 发过来的 ACK(在server端认为连接还没建立起来);
- tcp_abort_on_overflow 为 1 ,表示第三步的时候如果 ACCEPT 队列满了,server发送一个RST包给client,表示废掉这个握手过程和这个连接(本来在server端这个连接就还没建立起来),客户端会出现 connection reset by peer 的异常。
如何找到半连接队列中的TCB控制块
通过五元组,源ip 目的ip 源端口 目的端口 协议 五个因素决定。
TCB控制块中需要存储哪些信息
tcp状态机的11个状态、发送缓冲区和接收缓冲区
传输过程
tcp是如何保证单个数据包的顺序的?这样做有什么缺点
每接收到一个包,协议栈就会启动一个200ms的定时器,每接收到一个包,就会重置这个定时器。如果哪个包超时,则会重发以后的包。
缺点:确认时间长,ack的意思是此号包之前的数据全部收到,后面的包没有收到,后面的包即使收到了,前面的包也会重发。
多个数据包的收发,慢启动,拥塞控制
- 第一阶段(慢启动):第一次发1个包,第二次发2个包,第三次8个包,以2的指数方式增长发送数据包的个数,当达到门限值,进入第二阶段。
- 第二阶段(拥塞控制):在第一阶段进入第二阶段后,发包数量按照固定的斜率增长,当发送数据包到达一定数据,发送数据包个数变为原来的一半,继续增长发送。主要是为了控制流量。
数据传输过程中调用send函数会发生什么
调用copy_from_user将数据从用户空间拷贝到协议栈的sendbuffer中,数据发送的过程是在协议栈中解决的
断开连接
四次挥手细节
在右端看来,对端调用close,读关闭
接收到了fin包,epoll中就能触发EPOLLRDHUP
自己也发送fin包,收到ack后,fd会触发epollhub
出现大量的time_wait?
主动方(主动先调用close的一端)才会产生time_wait 1. 这时查看自己的逻辑;2. 通过setsockopt()设置为reuse(重用),使tcb不被释放而重用,一定程度上减少time_wait
出现大量的close_wait?
接收到主动发送的fin包以后,recv返回0,调用close的过程延迟,因为此时需要处理业务,发生未发送的数据。
解决方法,收到返回的0以后立即调用close,资源的释放等行为交给线程处理
出现大量fin_wait_1或者2?
客户端没有接收到ack包,导致长期处于fin_wait_1的状态。解决方法:无解只能kill
UDP
UDP应用场景
- 大量数据传输,下载,会影响别人用网
- 游戏,实时性
- dns协议udp用在请求,回应的时候,采用tcp
UDP的并发怎么做
接收不同客户端的数据,只通过一个buffer接收,很难区分清楚是哪个客户端发的数据,可能产生脏数据。
用udp模拟tcp的方式
在客户端和服务器第一次发送数据的时候,服务器接收到数据以后为其分配一个新的fd以及一个对应的端口号,再从对应的端口号send出来。一个fd对应一个客户端。
- recvfrom(&addr)接受一个客户端的信息,拿出IP地址的端口
- 新建一个fd,调用sendto(fd,)发给对方的ip地址和端口号
|