Posix API与网络协议栈

简介: 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的差就是包的长度。

相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务&nbsp;ACK 容器服务&nbsp;Kubernetes&nbsp;版(简称&nbsp;ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情:&nbsp;https://www.aliyun.com/product/kubernetes
相关文章
|
1月前
|
弹性计算 负载均衡 网络协议
这种情况可能是由于阿里云的API服务出现了短暂的故障或者网络波动导致的
【2月更文挑战第20天】这种情况可能是由于阿里云的API服务出现了短暂的故障或者网络波动导致的
73 1
|
3月前
|
消息中间件 网络协议 Unix
Posix API 与 网络协议栈 详细介绍
Posix API 与 网络协议栈 详细介绍
62 0
|
19天前
|
网络协议 Linux SDN
虚拟网络设备与Linux网络协议栈
在现代计算环境中,虚拟网络设备在实现灵活的网络配置和隔离方面发挥了至关重要的作用🔧,特别是在容器化和虚拟化技术广泛应用的今天🌐。而Linux网络协议栈则是操作系统处理网络通信的核心💻,它支持广泛的协议和网络服务🌍,确保数据正确地在网络中传输。本文将深入分析虚拟网络设备与Linux网络协议栈的关联,揭示它们如何共同工作以支持复杂的网络需求。
|
2月前
|
存储 网络协议 安全
POSIX API与网络协议栈
POSIX API与网络协议栈
36 0
|
2月前
|
机器学习/深度学习 人工智能 API
人工智能应用工程师技能提升系列2、——TensorFlow2——keras高级API训练神经网络模型
人工智能应用工程师技能提升系列2、——TensorFlow2——keras高级API训练神经网络模型
34 0
|
18天前
|
缓存 前端开发 API
API接口封装系列
API(Application Programming Interface)接口封装是将系统内部的功能封装成可复用的程序接口并向外部提供,以便其他系统调用和使用这些功能,通过这种方式实现系统之间的通信和协作。下面将介绍API接口封装的一些关键步骤和注意事项。
|
25天前
|
监控 前端开发 JavaScript
实战篇:商品API接口在跨平台销售中的有效运用与案例解析
随着电子商务的蓬勃发展,企业为了扩大市场覆盖面,经常需要在多个在线平台上展示和销售产品。然而,手工管理多个平台的库存、价格、商品描述等信息既耗时又容易出错。商品API接口在这一背景下显得尤为重要,它能够帮助企业在不同的销售平台之间实现商品信息的高效同步和管理。本文将通过具体的淘宝API接口使用案例,展示如何在跨平台销售中有效利用商品API接口,以及如何通过代码实现数据的统一管理。
|
1月前
|
安全 算法 API
产品经理必备知识——API接口
前言 在古代,我们的传输信息的方式有很多,比如写信、飞鸽传书,以及在战争中使用的烽烟,才有了著名的烽火戏诸侯,但这些方式传输信息的效率终究还是无法满足高速发展的社会需要。如今万物互联的时代,我通过一部手机就可以实现衣食住行的方方面面,比如:在家购物、远程控制家电、自动驾驶等等,背后都离不开我们今天要聊的API接口。
|
1月前
|
数据采集 JSON API
如何实现高效率超简洁的实时数据采集?——Python实战电商数据采集API接口
你是否曾为获取重要数据而感到困扰?是否因为数据封锁而无法获取所需信息?是否因为数据格式混乱而头疼?现在,所有这些问题都可以迎刃而解。让我为大家介绍一款强大的数据采集API接口。
|
1天前
|
前端开发 Java 测试技术
IDEA 版 API 接口神器来了,一键生成文档,贼香!
IDEA 版 API 接口神器来了,一键生成文档,贼香!
7 0