数据从网卡到应用的过程

最近看的《网络是怎样连接的》非常有趣,真的是 “计算机网络概论” 图解趣味版

本文写写数据从网卡到应用的过程,内容与图片很多整理自《网络是怎样连接的》、《Tomcat内核设计与剖析》,有的图片因清晰度不够我进行了重绘。

总览

本文围绕这张图从下至上展开。假设一个HTTP请求的数据到达网卡,那数据是如何被层层处理并到达应用呢?

 

网卡

网卡(Network Adapter),也称网络适配器,是一个 硬件设备,有全球唯一的 MAC(Media Access Control)地址,MAC地址在网卡生产时就被烧制在ROM中,网卡初始化时恢复到计算机中。

网卡收到的数据是 光信号或电信号,然后将其还原成 数字信息(1和0组成)

下图是还原的数字信息结构。 

 

根据 FCS(帧校验序列,Frame Check Sequence) 校验数据,判断数据在传输过程是否因噪音等影响导致信号失真,从而导致数据错误,需要丢弃这种无效的数据包

 

然后 检查 数据包中MAC头部中的 接收方的MAC地址,若不是发给自己,则丢弃数据包;若数据包是发给自己,则将数字信息保存到网卡内部缓冲区。

以上过程网卡自行搞定,不需要CPU参与,CPU也不知道数据包的到达。

网卡驱动

硬件需要驱动程序来控制,就像电脑需要操作系统一样,而网卡驱动就是CPU控制和使用网卡的程序。

网卡处理完数字信号后,接下来的数据接收需要CPU参与,此时网卡通过中断将数据包达到的事件通知给CPU。接着,CPU暂停手头工作,开始用网卡驱动来干活。

  • 从网卡缓冲区读取接收到的数据
  • 根据MAC头部的以太类型字段判断协议种类并调用处理该协议的软件(即协议栈)

通常我们接触的以太类型是 IP协议,因此会调用TCP/IP协议栈来处理。

协议栈

因各层协议看上去像堆叠状态,也就取名”协议栈”。 像TCP、UDP、IP等协议都是规范,而协议栈则是实现各类协议的网络控制软件。例如:Windows、Linux各自对协议进行了实现,因此不同系统之间能够通讯,与JVM跨平台原理一致。

IP模块

当MAC头部以太类型为 IP 协议时,网卡驱动数据包交给TCP/IP协议栈来处理。

  • IP模块会检查IP头部以判断数据是不是发给自己
  • 判断数据包是否分片,如果分片则缓存起来等待分片全部到达再还原成数据包
  • 根据IP头部的协议号字段,将包转给TCP模块或UDP模块处理

下面是IP头部的部分字段。

TCP模块

下面是TCP报文首部结构。

 

TCP模块会根据 标志位 来进行不同处理,假设服务端收到该报文,会进行如下处理:

  • 如果 SYN=1,表示这是请求连接的包。
    • 首先检查接收方端口号,然后检查有没有与该端口号相同且处于等待连接状态的套接字
    • 如果没有,则返回错误通知的包;
    • 如果有,则为这个套接字复制一个新副本,将发送方IP、端口等必要信息写入套接字,同时分配用于发送缓冲区和接收缓冲区的内存空间。
    • 最后返回给数据客户端,客户端会再次确认,这属于TCP连接三次握手的一部分。
  • 如果是正常数据包,TCP模块需要检查该包对应的套接字。然后提取出数据,存放到缓冲区。此时,如果应用程序调用socket的read(),数据就可以转交给应用程序了。如果应用程序不来获取数据,数据则一直保存在缓冲区中。

在上述处理过程中,不同协议层会逐层处理,剥洋葱一样剔除协议头部,将数据转交到上层。而发送数据时,TCP、IP等也会一层层的为数据包加上头部。

最后,数据就到应用层,应用层通过socket来操作数据,下面说说socket。

Socket

socket 是什么

socket 译为套接字,由加州大学伯克利分校的研究人员在20世纪80年代早期提出,因此也叫伯克利套接字。其研究人员使得套接字接口适用于任何底层协议,首个实现的协议就是针对TCP/IP。

从不同角度来看,其含义不同:

  • 对应用层来说,可通过 socket 与内核中的网络协议栈通信。应用不能直接使用协议栈,更不能控制网卡驱动。所以 socket 提供了网络编程的系统调用接口。
  • 从Linux文件系统来说,socket 是一个打开的文件。下面命令找到的就是一个套接字类型的文件。
  1. $ find / -type s
  2. /run/docker.sock

在普通 Java 应用中的文件描述符文件夹中也可以看到,下图都是一些指向socket的软链接。

  1. $ cd /proc/25693/fd
  2. $ ls -l | grep socket

 

  • 从Linux内核来说,socket 是一个通信的端点(Endpoint)。

常说 “连接”,通过C/S两端的socket真的是建立了一个物理通道吗?

连接实际上指的是通信双方的信息,套接字中记录了这些必要信息。下面是两个连接信息,包含了通信双方的IP、端口信息。

 

因此,套接字也可以认为是一个概念,它包含了通信双方的信息。

文件描述符

文件描述符(File Description)简称fd,是一个正整数,起到一个文件索引的作用,保存了一个指向文件的指针。

每创建或打开文件都会返回一个fd(一个数字),其实主流操作系统将TCP/UDP连接也当做fd管理,每新建一个连接就会返回一个fd。

创建 socket

应用程序申请创建套接字,具体实现由协议栈来搞定。协议栈首先会分配用于存放一个套接字所需要的内存空间,然后往其中写入控制信息(双方IP、port等信息)。套接字刚创建时,数据收发还没有开始,因此需要写入初始状态的控制信息。

接下来,需要将这个套接字的fd告诉应用程序。收到fd后,应用程序再向协议栈委托收发数据时,就要提供fd。

另,服务端在接收数据时,每来一个新连接,都会拷贝当前处于等待连接状态的socket,然后写入控制信息,而原先的socket则继续等待新的连接。

socket 编程 demo

一个最简单的网络编程例子。

服务端:

// 创建套接字并绑定到8080端口
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
    // 将套接字设置为等待连接状态,并阻塞线程
    Socket socket = serverSocket.accept();
    // 读数据时,其实要向协议栈提供套接字fd, 读取数据
    System.out.println(IOUtils.toString(socket.getInputStream(), "utf8"));
}

客户端:

// 创建客户端套接字(会有fd, 完成TCP握手)
Socket socket = new Socket("127.0.0.1", 8080);
// 使用套接字收发数据
socket.getOutputStream().write("Hello World".getBytes());

 

Socket 基于内核的回调机制

应用通过socket通信,socket保存了通信双方信息,相当于一个连接信息。当并发量大时,比如 C10K 即单机1W并发连接,1W连接也对应着1W个fd。那如何判断哪些连接有新数据呢?

  • 传统IO模型一个连接一个线程处理,然后遍历连接,CPU光遍历连接就已经满负荷。耗费大量线程,频繁切换线程环境,只适合低并发应用。
  • 进一步,可以一个线程管理多个socket(也就是多个fd),这也是NIO中的select机制。虽然降低了线程数,提高了并发能力,但遍历的瓶颈一直都在。
  • 问题最终由异步机制搞定。linux内核2.6版本提出了新的多路复用机制 epoll,套接字提供了回调函数,内核从网卡读取数据后就会回调该函数。

下面是《Tomcat内核设计与剖析》的一张图。

小结

应用层开发人员,比如Java工程师,更多的会好奇数据如何从Tomcat到Servlet,这个过程这些应用层框架是如何处理数据的。

信息技术迅猛发展的这些年,虽然应用层技术更新很快,但这些底层设施一直非常经久耐用,非常经典。所以多学习些Linux基础、计算机网络等基础知识,也大有裨益。

 

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值