注:最后有面试挑战,看看自己掌握了吗
🍃博主昵称:
一拳必胜客
🌸博主寄语:欢迎点赞收藏关注哦,一起成为朋友一起成长;
特别鸣谢:木芯工作室 、Ivan from Russia
前言
- 功能+应用
链路层功能
- 封装成帧—链路层
- 传比特-----物理层
- 加头加尾封装----------链路层
- 节点—主机、路由器,
- 链路–物理通道
- 数据链路----逻辑通道
- 帧----封装网络数据包—链路层
- 可靠的传输到相邻节点
- 给网络层提供无差错的服务
- 加强物理层传输原始比特流功能
功能
- 为网络层提供服务—无确认无连接服务、有确认无连接服务、有确认有链接服务
- 链路管理:连接的建立维持释放------有连接服务
- 组帧
- 流量控制-----限制发送方
- 差错控制—帧错/位错
封装成帧和透明传输
- 把网络层IP数据报加头加尾形成帧
- 帧首部、尾部
- 这个作用叫:帧定界服务
- 帧同步----接收方可以区分出 头、尾
- 最大传送单元MTU
组帧的四种方法
- 字符集书法—用帧首部的第一个字节来标明帧内字符数-----痛点:鸡蛋装在一个篮子–一错全错,失去同步
- 字符填充法— SOH---------EOT -------1当传送文本字符文件非常好用----透明传输2采用字符填充法,加上转义字符ESC不去管数据当中的数据信息
- 零比特填充法------方法:5"1"1"0"-------连续的5个1添0
- 违规编码法--------曼彻斯特编码,用高-高,低-低来界定帧的起始终止------局域网IEEE就用这种方法
透明传输
- 链路层好像看不到这样的信息
- 单纯传送数据到目标
差错控制
检错编码
- 插错都是由噪声引起的
- 全局性:线路本身电气特性所产生的随机噪声,是信道固有的、随即存在。---------------解决办法:提高信噪比或者避免干扰
- 局部性-----外部短暂原因造成的冲击噪声--------是产生差错的主要原因-----------利用编码技术拉解决
差错
- 位错—比特位错误
- 帧错—帧丢失、帧重复、帧失序
- 当通信质量好有线传输链路----------无确认无连接--------传输层来保证可靠传输
- 通信质量差的无线传输-----------有确认有链接
- ------------------因材施教
链路层的差错控制
检错编码
- 奇偶校验码—1的个数的奇偶------检错能力50%
- 循环冗余码CRC------数据发送之前,按照某种关系附加一定冗余位
- 要传的数据 / 生成多项式= *** ----------PSC帧检测序列/冗余码
- 接收端:接收到的数据 / 生成多项式 = *** -----------0
- 最终发送的数据: 要发送的数据+帧检验序列FCS
- 计算冗余码:1.加0 2.模二除法(异或)
- TIP 多项式N位,阶位N-1位
- 硬件实现-----迅速
- “凡是接收端数据链路层接收的帧均无差错”
- 这仍然是不可靠传输 ----有丢弃的帧,没有进行处理
纠错编码
- 海明码
- 码距----海明距离-------一个编码的系统里面任意两个合法编码之间的最小距离
- 看码距-----就是直接眼看--------另种方法就是异或
- 码距:n--------检错能力:n-1
- 所以,码距是3检错能力2
- 海明码
- 数据m位,校验码r位
- 校验码取值有2的r次方种取值
- 2的r次方>=m+r+1----------------海明不等式----------确定校验码位数-------直接带入尝试r--------加的1是正确情况
- 校验码放在2的n次方位置上------------一个个算1 2 4 8
- 通配形式 1 ** ---------负责所有1开头的检测 ------如101
- 求出校验码的值:采用偶校验,1号校验码负责1,3,5,7的校验
- 2号位负责2 3 6 7
- 4号校验码负责:4 5 6 7
- 我们把出错的找出来,然后把对的排除出来--------找出那个错的
- 纠错方法2:把那些校验码都写出来 x4 x2 x1-------101------第五位出错
链路层代码实现
//Ethernet.h //这里只为上次的Ethernet.h做一补充 int is_accept_ethernet_packet(u_int8_t *packet_content, int len); void ethernet_protocol_packet_callback(u_char *argument, const struct pcap_pkthdr *packet_header, const u_char *packet_content);
//Ethernet.cpp #include "Network_IPV4_recv.h" #include "Network_ARP_recv.h " //这里需要调用上层提供的接收函数接口交付数据包 int is_accept_ethernet_packet(u_int8_t *packet_content, int len) { struct ethernet_header *ethernet_hdr = (struct ethernet_header *)packet_content; int i; int flag = 0; //检查目的MAC地址是否是广播MAC地址 for (i = 0; i < 6; i++) { if (ethernet_hdr->destination_mac[i] != 0xff)break; } if (i == 6) { flag = 1; printf("It's broadcast packet.\n"); } //检查目的MAC地址是否是本地MAC地址 for (i = 0; i < 6; i++) { if (ethernet_hdr->destination_mac[i] != local_mac[i])break; } if (i == 6) { flag = 1; printf("It's sended to my pc.\n"); } //若上边检查均未通过,返回0表示本数据包不需要接收 if (!flag) return 0; //否则检查CRC校验码 u_int32_t crc = calculate_crc((u_int8_t *)packet_content , len - 4 ); if (crc != *((u_int32_t *)(packet_content + len - 4))) { printf("The data has changed.\n"); return 0; } //CRC无误则本数据包可以接收,返回1 return 1; } //接收数据帧的回环函数 void ethernet_protocol_packet_callback(u_char *argument, const struct pcap_pkthdr *packet_header, const u_char *packet_content) { int len = packet_header->len; if (!is_accept_ethernet_packet((u_int8_t *)packet_content, len)) { return; } struct ethernet_header *ethernet_hdr = (struct ethernet_header *)packet_content; u_int16_t ethernet_type = ntohs(ethernet_hdr->ethernet_type); //去掉MAC帧首部,将数据部分根据上层协议交付给相应的上层接收函数 u_int8_t *upper_buffer = (u_int8_t *)(packet_content + sizeof(ethernet_header)); switch (ethernet_type) { case 0x0800: printf("Upper layer protocol: IPV4\n"); network_ipv4_recv(upper_buffer); break; case 0x0806: printf("Upper layer protocol: ARP\n"); dest_mac=network_arp_recv(upper_buffer); break; case 0x8035: printf("Upper layer protocol: RARP\n"); //network_rarp_recv(); break; case 0x814c: printf("Upper layer protocol: SNMP\n"); //network_snmp_recv(); break; case 0x8137: printf("Upper layer protocol: IPX(Internet Packet Exchange)\n"); //network_ipx_recv(); break; case 0x86DD: printf("Upper layer protocol: IPV6\n"); //network_ipv6_recv(); break; case 0x880B: printf("Upper layer protocol: PPP\n"); //network_ppp_recv(); break; default:break; } //以上注释掉的协议均未实现,有兴趣的伙伴可以在看完我的协议栈设计的基础上在进行追加 }
到这里我们就算介绍完了数据链路层以太网的数据包发送和接收的过程及实现,我们先在此简单总结一下:
我们的数据发送,向上层提供的接口函数是:
int ethernet_send_packet(u_int8_t *upper_buffer,u_int8_t *destination_mac,u_int16_t ethernet_type)
上层调用此函数时需要提供的参数有:
1、上层的数据包,即链路层数据帧的数据部分
2、数据包长度,这里我们用全局变量ethernet_upper_len来获取 2、目的MAC地址
3、调用此函数的上层协议
数据接收时,根据上层协议不同提交时上层提供给我们的接口有:
network_arp_recv(upper_buffer); network_ipv4_recv(upper_buffer); //这两个我们后边会慢慢给大家展示出来