本文将介绍网络连接建立的过程、收发包流程,以及其中应用层、tcp层、ip层、设备层和驱动层各层发挥的作用。

应用层

对于使用socket进行网络连接的服务器端程序,我们会先调用socket函数创建一个套接字:

fd = socket(AF_INET, SOCK_STREAM, );

以上指定了连接协议,socket调用返回一个文件句柄,与socket文件对应的inode不在磁盘上,而是存在于内存。

之后我们指定监听的端口、允许与哪些ip建立连接,并调用bind完成端口绑定:

server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
bind(fd, (struct sockaddr_in *)&server_addr, sizeof(struct sockaddr_in));

端口作为进程的标识,客户端根据服务器ip和端口号就能找到相应进程。

接着我们调用listen函数,对端口进行监听:

listen(fd, backlog);

backlog值指定了监听队列的长度,以下内核参数限制了backlog可设定的最大值:

linux # sysctl -a | grep somaxconn
net.core.somaxconn =

监听端口在listen调用后变为LISTEN状态:

linux # netstat -antp | grep
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0.0.0.0: 0.0.0.0:* LISTEN /server

相应地,客户端调用connect进行连接,tcp三次握手在connect调用返回之前完成:

如果服务器端向客户端发送SYN+ACK后,客户端不返回ACK,则服务器保持半连接(SYN_RECV)状态:

linux # netstat -np | grep SYN_RECV
tcp 0.0.0.0: 127.0.0.0.: SYN_RECV -

若队列中的连接均处于半连接状态,服务器将不能处理正常的请求,syn泛洪攻击(syn flood)就是利用这个特点完成DoS(拒绝服务攻击)。

当连接数超过队列长度backlog时,超出的连接也保持为半连接状态,直到数量达到内核参数tcp_max_syn_backlog值,超出该值的连接请求将被丢弃:

linux # sysctl -a | grep tcp_max_syn
net.ipv4.tcp_max_syn_backlog =

accept调用用于处理新到来的连接:

new_fd = accept(fd, (struct sockaddr*)&client_addr, &sin_size);

其返回一个文件描述符,后续我们可以对该文件描述符调用write、read等操作函数,原监听端口仍处于LISTEN状态:

linux # netstat -antp | grep
tcp 0.0.0.0: 0.0.0.0:* LISTEN /server
tcp 127.0.0.1: 127.0.0.1: ESTABLISHED -

以上为网络连接建立过程中,应用层所做的工作,server端完成了socket创建、端口绑定、端口监听、连接和收发包任务,而client端相对简单,只需包含连接和收发包。

tcp层

内核代码中,tcp_sendmsg是tcp发包的主入口函数,该函数中struct sk_buff结构用于描述一个数据包。

对于超过MTU(maximum transmission unit, 最大传输单元)的数据包,tcp层会对数据包进行拆分,若开启了网口的tcp segmentation offload功能,则拆分工作由网卡完成:

linux # ethtool -k ether
Offload parameters for eth1:
rx-checksumming: on
tx-checksumming: on
scatter-gather: on
tcp segmentation offload: on 

以下内核参数是内核为tcp socket预留的用于发送数据包的缓冲区大小,单位为byte:

linux # sysctl -a | grep tcp_wmem
net.ipv4.tcp_wmem =

默认的用于包发送的缓冲区大小为16M。

除了用于缓冲收发数据包,对于每个socket,内核还要分配一些数据结构用于保持连接状态,内核对tcp层可使用的内存大小进行了限制:

linux # sysctl -a | grep tcp_mem
net.ipv4.tcp_mem =

以上值以页为单位,分别对应最小值、压力值和最大值,并在系统启动、tcp栈初始化时根据内存总量设定。通过proc提供的接口,我们可以查到tcp已用的内存页数:

linux # cat /proc/net/sockstat
sockets : used
TCP : inuse orphan tw alloc mem

ip层

内核代码中,ip_queue_xmit函数是ip层的主入口函数,注意ip层与tcp层操作的都是同一块内存(sk_buff结构),期间并没有发生数据包相关的内存拷贝。

ip层主要完成查找路由的任务,其根据路由表配置,决定数据包发往哪个网口,另外,该层实现netfilter的功能。

网络设备层

dev_queue_xmit是网络设备层的主入口函数,该层为每个网口维护一条数据包队列,由ip层下发的数据包放入对应网口的队列中。在该层中,数据包不是直接交给网卡,而是先缓冲起来,再通过软中断(NET_TX_SOFTIRQ)调用qdisc_run函数,该函数将数据包进一步交由网卡处理。我们执行ifconfig时,txqueuelen指示了网络设备层中,网口队列的长度。

驱动层

使用不同驱动的网卡,相应的驱动层代码就不一样,这里以e1000网卡为例。e1000_xmit_frame是该层的主入口函数,该层利用环形队列进行数据包管理,由两个指针负责维护环形队列。执行ethtool命令,我们可以查询网口驱动层环形队列长度:

linux # ethtool -g eth1
Ring parameters for ether
Pre-set maximums:
RX :
RX Mini :
RX Jumbo :
TX :
Current hardware settings:
RX :
RX Mini :
RX Jumbo :
TX :

以上RX与TX分别指示收包队列与发包队列长度,单位为包个数。

网卡接收到数据包时将产生中断,以通知cpu数据包到来的消息,而网卡接收包又非常繁忙,如果每次收发包都向cpu发送硬中断,那cpu将忙于处理网卡中断。

相应的优化方案是NAPI(New API)模式,其关闭网卡硬中断,使网卡不发送中断,而非使cpu不接收网卡中断。在e1000驱动代码中,由e1000_clean函数实现NAPI模式。

不像写文件的过程,磁盘设备层完成内存数据到磁盘拷贝后,会将消息层层上报,这里的网卡驱动层发包后不会往上层发送通知消息。

收包过程

以上为网络发包所需经过的层次结构,以及各层的大体功能,下面我们简单看下收包过程。

网卡接收到数据包后,通知上层,该过程不会发生拷贝,数据包丢给ip层。

内核代码中,ip_rcv是ip层收包的主入口函数,该函数由软中断调用。存放数据包的sk_buff结构包含有目的地ip和端口信息,此时ip层进行检查,如果目的地ip不是本机,则将包丢弃,如果配置了netfilter,则按照配置规则对包进行转发。

tcp_v4_rcv是tcp层收包的接收入口,其调用__inet_lookup_skb函数查到数据包需要往哪个socket传送,之后将数据包放入tcp层收包队列中,如果应用层有read之类的函数调用,队列中的包将被取出。

tcp层收包使用的内存同样有限制:

linux # sysctl -a | grep rmem
net.ipv4.tcp_rmem =

kernel笔记——网络收发包流程的更多相关文章

  1. kernel笔记:TCP参数

    http://blog.chinaunix.net/uid-27119491-id-3346430.html 本文将介绍网络连接建立的过程.收发包流程,以及其中应用层.tcp层.ip层.设备层和驱动层 ...

  2. Linux内核笔记--网络子系统初探

    内核版本:linux-2.6.11 本文对Linux网络子系统的收发包的流程进行一个大致梳理,以流水账的形式记录从应用层write一个socket开始到这些数据被应用层read出来的这个过程中linu ...

  3. 交换芯片收发包的 DMA 实现原理

    交换芯片支持:报文.计数.表项3种DMA类型,其中报文DMA包括系统从芯片到接收报文或发送报文到交换芯片,计数DMA用来从片上获取统计计数,表项DMA功能分为SLAM DMA(系统内存DMA到片上交换 ...

  4. Linux Kernel文件系统写I/O流程代码分析(一)

    Linux Kernel文件系统写I/O流程代码分析(一) 在Linux VFS机制简析(二)这篇博客上介绍了struct address_space_operations里底层文件系统需要实现的操作 ...

  5. Linux内核网络报文简单流程

    转:http://blog.csdn.net/adamska0104/article/details/45397177 Linux内核网络报文简单流程2014-08-12 10:05:09 分类: L ...

  6. UDP收发buffer尺寸对收发包流量的影响

    下午验证一个高流量发包问题时,发现了一个值得记录的问题:socket的收发buffer尺寸是会影响收发包的效率的,高流量通讯时,若socket的收发buffer尺寸过小会一定程度降低收发包效率. 自己 ...

  7. [development][dpdk][pktgen] 网卡收发包性能测试

    一: 多队列及中断信息收集 [root@T185 ~]# ethtool -g eth0 Ring parameters for eth0: Pre-set maximums: RX: RX Mini ...

  8. python 爬虫 ~ 查看收发包的情况

    DebugLog 可以用来查看收发包的情况,比较有意思,现特意记录下来: Sample: import urllib2 httpHandler = urllib2.HTTPHandler(debugl ...

  9. DPDK收发包全景分析

    前言:DPDK收发包是基础核心模块,从网卡收到包到驱动把包拷贝到系统内存中,再到系统对这块数据包的内存管理,由于在处理过程中实现了零拷贝,数据包从接收到发送始终只有一份,对这个报文的管理在前面的mem ...

随机推荐

  1. asp.net core 系列 7 Razor框架路由(上)

    一.概述 在上二篇中,主要是介绍了asp.net core mvc中路由的使用,这篇继续介绍路由在ASP.NET Core Razor中的使用.Razor Pages应该使用默认的传统路由,从应用程序 ...

  2. Linux命令收集

    文件处理命令:ls 功能描述:显示目录文件 命令英文原意:list 命令所在路径:/bin/ls 执行权限:所有用户 语法:  ls  选项[-ald]  [文件或目录] -a    显示所有文件,包 ...

  3. PE知识复习之PE合并节

    PE知识复习之PE合并节 一丶简介 根据上一讲.我们为PE新增了一个节. 并且属性了各个成员中的相互配合. 例如文件头记录节个数.我们新增节就要修改这个个数. 那么现在我们要合并一个节.以上一讲我们例 ...

  4. Perl IO:随机读写文件

    随机读写 如果一个文件句柄是指向一个实体文件的,那么就可以对它进行随机数据的访问(包括随机读.写),随机访问表示可以读取文件中的任何一部分数据或者向文件中的任何一个位置处写入数据.实现这种随机读写的功 ...

  5. [JavaScript] audio在浏览器中自动播放

    audio 在浏览器中自动播放 autoplay 属性 autoplay 属性规定一旦音频就绪马上开始播放. 如果设置了该属性,音频将自动播放. 使用 autoplay 属性进行播放 //使用auto ...

  6. AlwaysUp使用方法

    AlwaysUp是一款能将可执行文件.批处理文件及快捷方式作为Windows系统服务,并且进行管理和监视确保100%运行.当程序崩溃.挂起.弹出错误对话框时,AlwaysUp 能自动重启程序,并运行自 ...

  7. 【Nginx】使用nginx反向代理IIS实现80端口的解放

    下载版本: 官网地址:http://nginx.org/en/download.html 我使用了稳定版本. 下载完成之后进行目录解压,解压之后大概就是这个样子: 网上很多人写这些了想了解概念的百度一 ...

  8. Restful API设计规范及实战

    Restful API的概念在此就不费口舌了,博友们网上查哈定义文章很多,直入正题吧: 首先抛出一个问题:判断id为 用户下,名称为 使命召唤14(COD14) 的产品是否存在(话说我还是很喜欢玩类似 ...

  9. Android安全–Dex文件格式详解

    Dex文件是手机上类似Windows上的EXE文件,dex文件是可以直接在Dalvik虚拟机中加载运行的文件. 首先我们来生成一个Dex文件. 新建文件Hello.java内容如下: class He ...

  10. 解决基于IIS的.net core HttpWebRequest 连接特别慢

    用的是HttpWebRequest 连接特别慢,查找原因发现 : 由 HttpWebRequest. Proxy  代理的原因导致 . 其实请求1秒就完成了,那15秒是用来等待默认proxy超时的…… ...