这篇文章小结一下使用socket发送和接收网络包的流程。
发送网络包
socket在内核中也是一个文件的存在,发送网络包调用的是write系统调用。对文件进行write的操作,最后会调用到对应struct fil e结构的file_operations里的钩子函数。对于socket而言,有对应的file_operations定义。具体的就是调用的sock_write_iter函数。这边就是一层层的函数调用。函数的调用逻辑就不再详细讲了,主要的逻辑流程说一下。
在linux源码,也是有分层这个概念的,发送网络包的函数调用是一层层的往下调。
- VFS层: write系统调用找到struct file, 然后调用file_operations的write_iter钩子函数。
- Socket层: 获取到struct socket,调用ops的sendmsg成员函数。
- Sock层: 获取到struct sock, 根据sk_prot值调用对应的发送函数, 比如tcp调用的是tcp_sendmsg.
- TCP层: 调用tcp_write_xmit函数, 具体内部实现了tcp的连接的逻辑。这里会封装tcp的头部。
- IP层: 调用ip_queue_xmit函数, 这里会封装ip的头部。需要选取路由,确定从哪个网卡发送出去。路由的选取采用的是最长前缀匹配法。最后调用ip_local_out发送。发送的时候,会根据iptables的规则作不同的数据包修改。
- MAC层: 获取到MAC地址,然后封装frame头,调用dev_queue_xmit发送二层包,这里是放入到一个队列中。
- 设备层: 网络包的发送触发一个软中断,调用流程结束。然后就是中断处理函数里面处理队列中的数据,最终将包发送到网络上。
这样一个包的发送就完成了。
接收网络包
网络包的接收跟发送是相反的,是从底层一层层向上传递的。
- 硬件网卡: 接收到网络包之后,把网络包放到环形缓冲区。然后触发中断。
- 网卡驱动: 调用中断处理函数,把网络包传入到内核协议栈。内核协议栈处理一些二层的逻辑后,传入IP层。网卡接收到第一个包,触发一个中断。然后再中断的处理过程中,还会poll网卡的数据,一直到没有数据了中断结束。等待下一次网络包的到来,进行另一轮中断的处理过程。这样减少中断的次数,免得在中断处理的过程中,又不断的触发中断。
- IP层:处理iptables的规则,解析ip头。继续到TCP层
- TCP层:根据socket的状态,把数据放到不同的队列中。
数据已经放到了TCP层的队列中,然后就是等待上层读取了,这边跟发送又是一样的。
- VFS层:调用read系统调用读取数据,找到struct file, 然后调用file_operations的read_iter钩子函数。
- Socket层: 获取到struct socket,调用ops的recvmsg成员函数。
- Sock层: 获取到struct sock, 根据sk_prot值调用对应的发送函数, 比如tcp调用的是tcp_recvmsg.
- TCP层: 依次从不同的队列读取网络包。这里包含了TCP的处理逻辑,不深入分析了。
更多
从网络包的发送和接收中,看到了分层系统的应用。所有复杂的逻辑,在软件系统中通过分层都可以来简化。不同协议、设备网卡等,通过分层的框架可以很快的进行适配。
行动,才不会被动!
欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。