Posix API与网络协议栈

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: Posix API与网络协议栈

简介


TCP网络编程,通常是使用Posix API来操作socket来实现网络编程,而Posix API在window和linux下主要使用的接口只有以下几个:


  1. socket: 创建一个socket;
  2. bind: 绑定;
  3. listen: 监听;
  4. accept: 创建新的连接;
  5. recv: 接受数据;
  6. send: 发送数据;
  7. close: 关闭socket;
  8. connect: 连接;


另外setsockopt()/getsockopt()设置和获取属性的. 不管网络编程如何封装,如何设计,最终都是使用以上几个API实现; 但是这些API内部具体干了什么事呢?


socket


在Linux下的使用如下代码创建一个socket

int fd = socket() // fd = 3


如果在当前进程下第一次调用socket,那么fd肯定是等于3,0,1,2分别被stdin,stdout,stderr占用; 至于为什么占有,则需要从socket是什么来解释.


socket翻译为插座,本身也是有两部分构成。一部分为fd(文件描述符)和tcb(tcp control block)。fd为文件系统部分,而tcb为系统内核层面用户看不到的部分,主要为内核协议栈部分。操作fd实际操作的tcp协议栈部分, fd与tcb是一对一的。在进程启动的时候会创建三个fd,也就是stdin, stdout, stderr所以第一次创建socket的fd等于三,之后每次创建新的socket都是在之前的fd上加1,而fd其实就是一个内核中的记录标识而已。


bind


socket创建出来的tcb是一个closed状态(tcp 11个状态迁移),需要bind确定从什么地方接收和发送,俗称5元组, 包含remote ip, remote port, local ip, local port, protocol。绑定完后知道具体的连接后数据来源和去向。bind的端口,在接收或者发送数据,用来填充本机的ip和端口。当bind绑定0.0.0.0:8080的时候调用send时remote_ip是在优先选eth0。该函数大部分是在服务端使用,但是当客户端电脑有多个网卡或IP的时候,也可以使用该接口绑定具体的地址;


accept


从全连接队列中取出一个tcb, 然后创建一个fd,将tcb和fd建立关联,并返回fd, 之后对fd的操作都是针对对应的tcb操作。至于什么是全连接队列,稍后描述.


listen


服务器启动监听指定的端口.


connect


连接指定的地址.


send


send看名字是发送数据,但其实send只把数据拷贝到tcb sendbuffer中,然后交由内核协议栈来完成发送。而内核协议栈这个发送过程是非常复杂的。


recv


同理,由内核协议栈接收到tcb recvbuffer, 然后由recv api拷贝到用户空间。


close


关闭tcp连接,协议栈内部做四次挥手。


listen, accept和connect内部发生了什么


listen, accept和connect三个接口内部做的事情主要是tcp的三次握手。三次握手发生在服务器和客户端的内核协议栈中,对用户是隐藏的(用户态协议栈不在这范畴)。具体是如何运行:


三次握手是如何握手以及实现


三次握手是发生在内核协议栈之间的,对用户来说是无法感知到。具体流程如下:


  1. 客户端内核协议栈发送同步包,包含syn seqnum;
  2. 服务端内核协议栈收到syn包后,回应一个ack acknum和服务端的syn
    seqnum;
  3. 客户端收到了服务端的syn seqnum后再回复一个ack acknum, 至此完成三次握手;


三次握手是一个双向过程,客户端发送syn包后,服务器回复ack的同时附带syn同步请求包。acknum=1235表示1235包之前的包都收到了。三次握手要三次的原因是通信是双向的,客户端需要连接服务端需要请求和确认,服务端和客户端连接也需要请求和确认,中间一次确认和请求合并到一起。

具体发生在客户端connect,服务端发生在listen之后accept完成之前,服务端listen开启监听后。


  1. connect调用之后协议栈发送同步头,服务端被动等待到请求,创建一个5元组,构建一个节点叫tcb放入半连接队列(或syn队列)中, 因为需要响应多个客户端,因此采用队列存放异步处理。创建tcb后回复ack及syn包, connect如果是异步的,则当fd变为可写时表示连接成功。
  2. 服务器第三次握手请求后通过5元组去半连接队列中查找tcb然后放入全连接队列并且通过accept返回到用户空间, accept从全连接队列取出tcb创建一个fd返回,之后操作fd就是操作与之对应的tcb。
  3. 如果其中ack包没有接收到,则会重发,超时后会丢掉连接。
  4. 服务器就是通过端口复用,fd->tcb, tcb通过五元组区分,因此单台服务器能连接超过65535个socket。


close断开连接发生的什么


TCP/IP的四次挥手


断开只有一个函数close, 但是状态迁移上有6个状态。四次挥手只有个主动方和被动方.


  1. 调用close后,协议栈会置fin位为1,然后发送fin包。
  2. 被动方接收到fin包,准备一个空包返回给被动方应用层api recv接口返回0, 然后回复ack包。
  3. 被动方recv返回0后调用close,再发送fin包到主动方,然后主动方再回复ack, 至此四次挥手完成,连接断开。

上面这是主动方调用close关闭连接,但存在另一种情况,就是双方同时调用close的情况.


主动方和被动方同时发送fin, 主动方再fin_wait_1的时候收到了fin包,进入了closing的状态.


断开过程中可能出现的问题


  1. 当主动方出现大量的fin_wait_2的状态,被动方出现大量的close_wait的状态就是服务器没有调用close接口。主要在于recv到close之间执行时间太长。
  2. 当出现last_ack, fin_wait_1状态,不用关心,会重传,只需等待即可。
  3. 客户端解决fin_wait_2不好解决,设置keeplive时间,等待超时终止掉。
  4. 当出现大量的time_wait,设置reused解决. time_wait是避免最后一个ack的丢失,time_wait默认120s,可以修改的。


被动方在调用close后fd被回收,tcb在last_ack后回收,而主动方在time_wait时间到了后回收。


send和recv中间发生了什么



调用send函数将数据拷贝到内核协议栈,由内核协议栈之间进行传输,当客户端内核协议栈的send返回-1表示sendbuffer已经满了,发送失败。服务端内核协议栈接收数据时会告诉客户端内核协议栈服务端的recvbuffer还有多少空余空间,保证在recvbuffer满的时候不再发送,致使客户端send失败。recvbuffer在接收数据时,有个push标识位,置1时会立即通知客户端。


粘包和分包问题处理


当多次send都拷贝到内核协议栈,内核协议栈会自行组包进行发送,从而产生了粘包和分包问题。


解决粘包和分包问题:


  1. 协议头中增加包长。
  2. 添加分隔符。
    但是这个前提是tcp包是顺序的,即先发先到,后发后到。那么如何保证顺序的呢? 采用延时ack来解决这问题。


延时ACK


延迟ack:


  1. 数据包发送有个mtu是确定的。
  2. 发送1号包,服务端启动定时器,延时回复ack.
  3. 当2号,3号,5号包到达重置定时器。
  4. 当超时触发,根据mtu发现4号包没到,则回复3号包的ack,从4号包开始重传。


MTU: Maximum Transmit Unit,最大传输单元,即物理接口(数据链路层)提供给其上层(通常是IP层)最大一次传输数据的大小;以普遍使用的以太网接口为例,缺省MTU=1500Byte,这是以太网接口对IP层的约束,如果IP层有<=1500 byte 需要发送,只需要一个IP包就可以完成发送任务;如果IP层有>1500 byte 数据需要发送,需要分片才能完成发送,这些分片有一个共同点,即IP Header ID相同。


通过延时ACK的方式保证了包的顺序,但当发生文件时又是如何操作的呢?


滑动窗口


客户端发送文件到服务器,发送条件:


  1. 发送1M的文件。
  2. Sendbuffer = 2k
  3. mss = 512
  4. mtu = 1500


MSS:Maximum Segment Size ,TCP提交给IP层最大分段大小,不包含TCP Header和 TCP Option,只包含TCP Payload ,MSS是TCP用来限制application层最大的发送字节数。如果底层物理接口MTU= 1500 byte,则 MSS = 1500- 20(IP Header) -20 (TCP Header) = 1460 byte,如果application 有2000 byte发送,需要两个segment才可以完成发送,第一个TCP segment = 1460,第二个TCP segment = 540。发送的方式应该是一个:

while(1) {
  poll(fd, ); // 判断是否可写
  send(fd, buffer, 1k, 0);  
}



客户端在进入发送状态后,发送到服务器后,回复了window空余空间,当等于0时,客户端不再发送。


那么服务器处理了数据后,客户端如何知道服务器已经可以接收数据了呢? 客户端接收到服务器window=0时,启动探测定时器,间隔发送探测包来访问服务器的window。


滑动窗口ack回复第7包的seqnum,表示前面的包都收到了,然后从8开始重发,依次移动两个指针。


发送过程这里还存在一个问题,就是发送频率问题,在tcp中叫做拥塞控制,tcp协议栈有个慢启动的机制。


慢启动


慢启动的过程会先发送1个mss, 然后2mss, 4mss,成对数形式增长。发送次数到达16次(默认值)后线性增长,后面部分称为拥塞控制。


那么,如何判断数据包超出网络负载? 使用超时时间控制,一次发送到ACK返回的时间为RTT,RTT突然变大称为抖动。


rtt = 0.1 * rtt(最近的一次) + 0.9 * rtt(之前的)


上述公式就是一个消抖的过程. 一旦rtt超时后mss数量降半。



如上图,当发送一个mss时,回复ack没有超过rtt, 则下一次发送2个mss, 回复ack没有超过rtt, 则再下一次发送8个mss, 依次增加发送mss, 这个过程就是慢启动过程。


seqnum的含义?


seqnum记录的字节数量,初始值为随机值,当到了最大后从新从0开始。比如第一包seqnum = 1000, 第二包长度512,则第二包seqnum=10512;


为什么udp协议头有包长,而tcp协议头没有包长?


tcp有个mss(最大传输片),不管sendbuf多大,都会切割成mss长度发送。


每次发送都会拆成mss大小的一包数据发送到服务器,所以tcp包协议头不需要长度. Tcp的前后seqnum的差就是包的长度。

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
6月前
|
弹性计算 负载均衡 网络协议
这种情况可能是由于阿里云的API服务出现了短暂的故障或者网络波动导致的
【2月更文挑战第20天】这种情况可能是由于阿里云的API服务出现了短暂的故障或者网络波动导致的
135 1
|
6月前
|
分布式计算 API Linux
通义千问API:找出两篇文章的不同
本章我们将介绍如何利用大模型开发一个文档比对小工具,我们将用这个工具来给互联网上两篇内容相近但版本不同的文档找找茬,并且我们提供了一种批处理文档比对的方案
|
8天前
|
网络协议 安全 Go
Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
【10月更文挑战第28天】Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
34 13
|
2月前
|
网络协议 网络架构 数据格式
TCP/IP基础:工作原理、协议栈与网络层
TCP/IP(传输控制协议/互联网协议)是互联网通信的基础协议,支持数据传输和网络连接。本文详细阐述了其工作原理、协议栈构成及网络层功能。TCP/IP采用客户端/服务器模型,通过四个层次——应用层、传输层、网络层和数据链路层,确保数据可靠传输。网络层负责IP寻址、路由选择、分片重组及数据包传输,是TCP/IP的核心部分。理解TCP/IP有助于深入掌握互联网底层机制。
383 2
|
3月前
|
JavaScript 网络协议 API
【Azure API 管理】Azure APIM服务集成在内部虚拟网络后,在内部环境中打开APIM门户使用APIs中的TEST功能失败
【Azure API 管理】Azure APIM服务集成在内部虚拟网络后,在内部环境中打开APIM门户使用APIs中的TEST功能失败
|
3月前
|
机器学习/深度学习 API 算法框架/工具
【Tensorflow+keras】Keras API三种搭建神经网络的方式及以mnist举例实现
使用Keras API构建神经网络的三种方法:使用Sequential模型、使用函数式API以及通过继承Model类来自定义模型,并提供了基于MNIST数据集的示例代码。
54 12
|
3月前
|
缓存 网络协议 Linux
扩展Linux网络栈
扩展Linux网络栈
71 3
|
3月前
|
机器学习/深度学习 API 算法框架/工具
【Tensorflow+keras】Keras API两种训练GAN网络的方式
使用Keras API以两种不同方式训练条件生成对抗网络(CGAN)的示例代码:一种是使用train_on_batch方法,另一种是使用tf.GradientTape进行自定义训练循环。
38 5
|
3月前
|
Java API 网络安全
探索Java中的Stream API:从基础到高级应用云计算与网络安全:技术融合与挑战
【8月更文挑战第27天】在Java的海洋中,Stream API犹如一艘强大的船,让开发者能以声明式的方式处理集合数据。本文将启航,先带你了解Stream的基本概念和用法,再深入探讨其高级特性,如并行流、管道操作以及性能考量。我们将通过具体代码示例,展示如何高效利用Stream API简化数据处理流程,提升代码的可读性和性能。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇通往更优雅编程风格的大门。
|
3月前
|
SQL 网络协议 安全
【Azure API 管理】APIM集成内网虚拟网络后,启用自定义路由管理外出流量经过防火墙(Firewall),遇见APIs加载不出来问题
【Azure API 管理】APIM集成内网虚拟网络后,启用自定义路由管理外出流量经过防火墙(Firewall),遇见APIs加载不出来问题
下一篇
无影云桌面