操作系统之网络系统

发布时间:2022-07-03 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了操作系统之网络系统脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

43 预习:Socket通信之网络协议基本原理 网络协议:一台机器将内容按照约定好的格式发送出去,另外一台机器收到后能按照约定格式解析,获取到信息。 两种网络协议, osi标准七层模型:物理层 数据链路层 网络层 传输层 会话层 表示层 应用层 TCP/IP模型:物理层 mac层 ip层 传输层  应用层(dns http)

具体如下图:

操作系统之网络系统

为什么要分层呢? 同一套网络协议栈通过切分成多个层次和组合,来满足不同服务器和设备的通信需求。

网络协议层次介绍 物理层:即物理设备,如连着电脑的网线、wifi 数据链路层:又称mac层,因为每个网卡都有唯一的硬件地址。在该层IP地址经过ARP协议得到对应的mac地址,在网络内服务器匹配上广播包对应的MAC地址就接收。 网络层:也就IP层,网络包从一个起始的IP地址,沿着路由协议指的道儿,经过多个网络,通过多次路由器转发,到达目标IP地址。 传输层:协议包括UDP/TCP,TCP是可靠协议,通过重传、编号等手段避免了丢包等问题。 应用层:如咱们在浏览器里面输入的 HTTP,Java 服务端写的 Servlet

应用层是用户态的,二层到四层都是在Linux内核里面处理的。内核对于网络包的处理不区分内核应用,应用层则进行区分,如通过端口,nginx监听80,tomcat监听8080.

用户态的应用层和内核2~4层的互通,就是通过socket系统调用。

数据包的传输过程 网络分完层后,对于数据包的发送和接收就是层层封装与解封装的过程。 在 Linux服务器B上部署的服务端Nginx, Linux 服务器 A 上的客户端,打开一个 Firefox 连接 Ngnix。如下图:

操作系统之网络系统

那么网络传输的具体流程: 1)在服务器A上,浏览器请求封装为HTTP协议,通过Socket发送到A的内核。传输层将http包加上TCP头发送给IP层,IP层加上IP头发送给mac层,mac层加上mac头,从硬件网卡发出去。 2)到达交换机,因只会处理到第二层所以被称为二层设备。会将网络包的MAC头拿下来,发现目标MAC是在自己右面的网口,于是就从这个网口发出去。 3)到达路由器,它左面的网卡会收到网络包,发现 MAC 地址匹配,就交给 IP 层,在 IP 层根据 IP 头中的信息,在路由表中查找,然后从合适的网口发出去。常把路由器称为三层设备,因为它只会处理到第三层 4)交换机,还是会经历一次二层的处理,转发到交换机右面的网口。 5)linux服务器B,它发现 MAC 地址匹配,就将 MAC 头取下来,交给上一层。IP 层发现 IP 地址匹配,将 IP 头取下来,交给上一层。TCP 层会获取TCP 头中的信息,内核会根据 TCP 头中的端口号,将网络包发给相应的应用。 6)应用会解析HTTP 层的头和正文,处理后获取结果,然后仍然封装为http的网络包,通过一系列过程返回给客户端。

43 socket通信 TCP/UDP的区别 1)TCP是面向连接的。UDP无连接 2)TCP通过编号、重试等是可靠传输,且提供流量控制和拥塞控制。UDP不可靠 3)TCP效率较低, 4)TCP是基于流的,UDP基于数据报 说明:所谓的连接,就是两端数据结构状态的协同,两边的状态能够对得上。可靠也是两端的数据结构做的事情,如不丢失其实是数据结构在“点名”,顺序到达其实是数据结构在“排序”,面向数据流其实是数据结构将零散的包,按照顺序捏成一个流发给应用层。

socket操作tcp或udp,都要调用socket函数,如下: int socket(int domain, int type, int protocol); 参数说明: domain:表示使用什么 IP 层协议。AF_INET 表示 IPv4,AF_INET6 表示 IPv6。 type:表示 socket 类型。SOCK_STREAM,顾名思义就是 TCP 面向流的,SOCK_DGRAM 就是 UDP 面向数据报的,SOCK_RAW 可以直接操作 IP 层,或者非 TCP 和 UDP 的协议。例如 ICMP。 protocol 表示的协议,包括 IPPROTO_TCP、IPPTOTO_UDP。 函数功能:在内核中,创建一个 socket 的文件描述符,后续的操作都会用到。

TCP下socket编程 整体流程如下图:

操作系统之网络系统

0)服务端和客户端分别调用socket函数,获取socket文件描述符 1)服务端调用bind函数,监听一个端口,给socket赋一个ip地址和端口。这样后面客户端可以通过ip和端口访问服务端,客户端则不需要bind 说明:服务端所在的服务器可能有多个网卡、多个地址,可以选择监听在一个地址,也可以监听 0.0.0.0 表示所有的地址都监听。服务端一般要监听在一个众所周知的端口上,例如,Nginx 一般是 80,Tomcat 一般是 8080。 2)服务端调用listen进入LISTEN状态,等待客户端连接。 3)服务端调用 accept,等待内核完成了至少一个连接的建立,才返回。如果有多个客户端发起连接,并且在内核里面完成了多个三次握手,建立了多个连接,这些连接会被放在一个队列里面,循环调用accpet进行处理。 4)客户端通过connect函数发起连接,指明要连接的 IP 地址和端口号,然后发起三次握手。握手成功后,服务端的 accept 就会返回另一个 socket。 监听的 socket 和真正用来传送数据的 socket,是两个 socket,一个叫作监听 socket,一个叫作已连接 socket。 5)双方开始通过 read 和 write 函数来读写数据

上面提到的三次握手如下图:

操作系统之网络系统

为什么是三次握手,不是2次或四次?tcp的报文序号,三次握手和四次挥手_进化的深山猿-CSDN博客

UDP下的socket编程 如下图:

操作系统之网络系统

UDP 是没有连接的,所以不需要三次握手,也就不需要调用 listen 和 connect,但是 UDP 的交互仍然需要 IP 地址和端口号,因而也需要 bind。 每次通信时,调用 sendto 和 recvfrom,都要传入 IP 地址和端口。

44 socket内核数据结构 socket函数解析 Socket系统调用会调用sock_create创建一个struct socket结构,然后通过sock_map_fd和文件描述符对应起来。 参数: 先分配struct socket 结构。 family,表示地址族。 net_families 数组中以该参数为下标,找到对应的 struct net_proto_family,net_proto_family中会指定create函数的指向。 type,也即 Socket 的类型。类型是比较少的,包括SOCK_STREAM、SOCK_DGRAM 和 SOCK_RAW。type作为inetsw数组下标,找到type对应的列表,根据protocol最终得到用户指定的 family->type->protocol 的 struct inet_protosw *answer 对象,其ops赋给struct socket *sock 的 ops 成员变量。 然后创建一个 struct sock *sk 对象。说明:socket 是用于负责对上给用户提供接口,并且和文件系统关联。而 sock,负责向下对接内核网络协议栈。 struct inet_protosw *answer 结构的 tcp_prot 赋值给了 struct sock *sk 的 sk_prot 成员。

protocol,是协议。协议数目是比较多的,也就是说,多个协议会属于同一种类型。会循环对应的链表,找到对应类型下对应协议的inetsw

解析bind函数 sockfd_lookup_light 会根据 fd 文件描述符,找到 struct socket 结构。然后将 sockaddr 从用户态拷贝到内核态,然后调用 struct socket 结构里面 ops 的 bind 函数。根据前面创建 socket 的时候的设定,调用的是 inet_stream_ops 的 bind 函数,即调用inet_bind,检查端口是否冲突,是否可以绑定。可以则进行绑定。

解析listen函数 即调用 inet_listen。如果socket还不在TCP_LISTEN状态,会调用inet_csk_listen_start进入监听状态。 在内核中,为每个Socket维护两个队列: 1)一个是已经建立了连接的队列,这时候连接三次握手已经完毕,处于 established 状态;即icsk_accept_queue 2)一个是还没有完全建立连接的队列,这个时候三次握手还没完成,处于 syn_rcvd 的状态。

初始化完之后,将 TCP 的状态设置为 TCP_LISTEN,再次调用 get_port 判断端口是否冲突。

解析accept函数 即调用inet_accept 原来的 socket 是监听 socket,这里我们会找到原来的 struct socket,并基于它去创建一个新的 newsock。这才是连接socket。除此之外,我们还会创建一个新的 struct file 和 fd,并关联到 socket。

如果 icsk_accept_queue 为空,则调用 inet_csk_wait_for_connect 进行等待;等待的时候,调用 schedule_timeout,让出 CPU,并且将进程状态设置为 TASK_INTERRUPTIBLE。 如果再次 CPU 醒来,我们会接着判断 icsk_accept_queue 是否为空,同时也会调用 signal_pending 看有没有信号可以处理。一旦 icsk_accept_queue 不为空,就从 inet_csk_wait_for_connect 中返回,在队列中取出一个 struct sock 对象赋值给 newsk。

解析connect函数 三次握手结束后,icsk_accept_queue 才不为空。下面分析三次握手的过程。

客户端发送SYN: 三次握手由客户端调用connect函数发起,调用inet_stream_connect。 如果socket处于SS_UNCONNECTED 状态,调用tcp_v4_connect函数,主要工作如下: 1)ip_route_connect 其实是做一个路由的选择。SYN 包了,这就要凑齐源地址、源端口、目标地址、目标端口。 2)发送 SYN 之前,我们先将客户端 socket 的状态设置为 TCP_SYN_SENT。然后初始化 TCP 的 seq num 3)调用 tcp_connect 进行发送SYN,会有重试机制。

服务端接受SYN: 通过struct net_protocol 结构中的 handler 进行接收,调用的函数是 tcp_v4_rcv。接下来的调用链为 tcp_v4_rcv->tcp_v4_do_rcv->tcp_rcv_state_process。 服务端处于TCP_LISTEN状态,接收到SYN. 回复一个 SYN-ACK,调用 icsk->icsk_af_ops->conn_request 函数,其实调用的是 tcp_v4_conn_request-->tcp_conn_request 回复完毕后,服务端处于 TCP_SYN_RECV状态

客户端接收SYN-ACK网络包了: 都是 TCP 协议栈,所以过程和服务端没有太多区别,还是会走到 tcp_rcv_state_process 函数的,此时客户端目前处于 TCP_SYN_SENT 状态,所以tcp_rcv_synsent_state_process 会调用 tcp_send_ack,发送一个 ACK-ACK,发送后客户端处于 TCP_ESTABLISHED 状态。

服务端接收ACK-ACK网络包 又tcp_rcv_state_process 函数处理。由于服务端目前处于状态 TCP_SYN_RECV 状态,因而又走了另外的分支。当收到这个网络包的时候,服务端也处于 TCP_ESTABLISHED 状态,三次握手结束。

45-46 发送数据包 这个过程分成几个层次: VFS 层:write 系统调用找到 struct file,根据里面的 file_operations 的定义,调用 sock_write_iter 函数。sock_write_iter 函数调用 sock_sendmsg 函数。 Socket 层:从 struct file 里面的 private_data 得到 struct socket,根据里面 ops 的定义,调用 inet_sendmsg 函数。 Sock 层:从 struct socket 里面的 sk 得到 struct sock,根据里面 sk_prot 的定义,调用 tcp_sendmsg 函数。

TCP 层:tcp_sendmsg 函数会调用 tcp_write_xmit 函数,tcp_write_xmit 函数会调用 tcp_transmit_skb,在这里实现了 TCP 层面向连接的逻辑。

IP 层:扩展 struct sock,得到 struct inet_connection_sock,根据里面 icsk_af_ops 的定义,调用 ip_queue_xmit 函数。 IP 层:ip_route_output_ports 函数里面会调用 fib_lookup 查找路由表。FIB 全称是 Forwarding Information Base,转发信息表,也就是路由表。 在 IP 层里面要做的另一个事情是填写 IP 层的头。 在 IP 层还要做的一件事情就是通过 iptables 规则。

MAC 层:IP 层调用 ip_finish_output 进行 MAC 层。 MAC 层需要 ARP 获得 MAC 地址,因而要调用 ___neigh_lookup_noref 查找属于同一个网段的邻居,他会调用 neigh_probe 发送 ARP。 有了 MAC 地址,就可以调用 dev_queue_xmit 发送二层网络包了,它会调用 __dev_xmit_skb 会将请求放入队列。

设备层:网络包的发送会触发一个软中断 NET_TX_SOFTIRQ 来处理队列中的数据。这个软中断的处理函数是 net_tx_action。 在软中断处理函数中,会将网络包从队列上拿下来,调用网络设备的传输函数 ixgb_xmit_frame,将网络包发到设备的队列上去。

47-48 接收数据包 整个过程可以分成以下几个层次: 硬件网卡接收到网络包之后,通过 DMA 技术,将网络包放入 Ring Buffer; 硬件网卡通过中断通知 CPU 新的网络包的到来;

网卡驱动程序会注册中断处理函数 ixgb_intr; 中断处理函数处理完需要暂时屏蔽中断的核心流程之后,通过软中断 NET_RX_SOFTIRQ 触发接下来的处理过程; NET_RX_SOFTIRQ 软中断处理函数 net_rx_action,net_rx_action 会调用 napi_poll,进而调用 ixgb_clean_rx_irq,从 Ring Buffer 中读取数据到内核 struct sk_buff; 调用 netif_receive_skb 进入内核网络协议栈,进行一些关于 VLAN 的二层逻辑处理后,调用 ip_rcv 进入三层 IP 层;

在 IP 层,会处理 iptables 规则,然后调用 ip_local_deliver 交给更上层 TCP 层;

在 TCP 层调用 tcp_v4_rcv,这里面有三个队列需要处理,如果当前的 Socket 不是正在被读取,则放入 backlog 队列,如果正在被读取,不需要很实时的话,则放入 prequeue 队列,其他情况调用 tcp_v4_do_rcv; 在 tcp_v4_do_rcv 中,如果是处于 TCP_ESTABLISHED 状态,调用 tcp_rcv_established,其他的状态,调用 tcp_rcv_state_process; 在 tcp_rcv_established 中,调用 tcp_data_queue,如果序列号能够接的上,则放入 sk_receive_queue 队列;如果序列号接不上,则暂时放入 out_of_order_queue 队列,等序列号能够接上的时候,再放入 sk_receive_queue 队列。

至此内核接收网络包的过程到此结束,接下来就是用户态读取网络包的过程,这个过程分成几个层次。 VFS 层:read 系统调用找到 struct file,根据里面的 file_operations 的定义,调用 sock_read_iter 函数。sock_read_iter 函数调用 sock_recvmsg 函数。 Socket 层:从 struct file 里面的 private_data 得到 struct socket,根据里面 ops 的定义,调用 inet_recvmsg 函数。 Sock 层:从 struct socket 里面的 sk 得到 struct sock,根据里面 sk_prot 的定义,调用 tcp_recvmsg 函数。 TCP 层:tcp_recvmsg 函数会依次读取 receive_queue 队列、prequeue 队列和 backlog 队列。

脚本宝典总结

以上是脚本宝典为你收集整理的操作系统之网络系统全部内容,希望文章能够帮你解决操作系统之网络系统所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。
标签: