🌈hello,你好鸭,我是Ethan,西安电子科技大学大三在读,很高兴你能来阅读。
✔️目前博客主要更新Java系列、项目案例、计算机必学四件套等。
🏃人生之义,在于追求,不在成败,勤通大道。加油呀!
🔥个人主页:Ethan Yankang
🔥推荐:史上最强八股文||一分钟看完我的几百篇博客
🔥温馨提示:划到文末发现专栏彩蛋 点击这里直接传送
🔥本篇概览:详细讲解了手写TCP/IP的第3节第3讲——以太网输入输出的完整处理,。🌈⭕🔥
【计算机领域一切迷惑的源头都是基本概念的模糊,算法除外】
🔥 手写底层系列
🔥 手写TCP/IP系列
【OSI与课程讲解】
🌈章节引出:
前一篇章:手写TCP/IP——第3节以太网数据包收发实现2——以太网驱动封装-CSDN博客
🌈章节速览:
本节课我们处理的就是最底层的包在两台机器上以太网层的的发送与接收过程:
具体本课时实现3种功能:
定义数据包格式、数据包的发送处理、数据包的接收处理
1.以太网包头的的格式
【以以太网数据包格式(RFC894)为准】
这里的目的地址是指服务器上的MAC网卡地址,源地址是指客户机上的MAC网卡地址。
为了简便起见,本课程 不涉及前导码与CRC字段,仅仅涉及中间的四块,具体见下面:
【本课程只涉及这四块,完全足够了】
1.1抓一个真实的包:
这里就是以太网的包头的组成,可以看到,源MAC地址为0a:00:27:00:00:2b、目的MAC地址为08:00:27:ae:36:6b、上层协议为0x0800
我们打开虚拟机看看其MAC地址,果真如此,为08:00:27:ae:36:6b:
再来看看物理机上的MAC地址,也果真如此,为0a:00:27:00:00:2b:
这两者就对上了@@
1.2定义以太网包头
#define XNET_MAC_ADDR_SIZE 6 //MAC 地址的长度48bit,6字节
typedef struct _xether_hdr_t {
uint8_t dest[XNET_MAC_ADDR_SIZE]; // 目标mac地址
uint8_t src[XNET_MAC_ADDR_SIZE]; // 源mac地址
uint16_t protocol; // 协议/长度
}xether_hdr_t;
我们在定义包头的内存指向的时候,要求内存是精确的 ,但是由于编译器的内存填充优化存在,会解析失败,具体详见:
1.3编译器结构体的内存对齐问题:
我们想要自己的的内存地址完全指向正确(即使用左边的结构体对齐模型),所以就要禁用编译器的对其填充。
可以使用宏包在包头的上下:
这样就实现了禁用编译器的对其填充,得到我们想要的内存执行!!
#pragma pack(1)
#define XNET_MAC_ADDR_SIZE 6 // MAC地址长度
/**
* 以太网数据帧格式:RFC894
*/
typedef struct _xether_hdr_t {
uint8_t dest[XNET_MAC_ADDR_SIZE]; // 目标mac地址
uint8_t src[XNET_MAC_ADDR_SIZE]; // 源mac地址
uint16_t protocol; // 协议/长度
}xether_hdr_t;
#pragma pack()
2.以太网初始化:
static uint8_t netif_mac[XNET_MAC_ADDR_SIZE]; //这个MAC地址后面实现ARP协议的时候会用得到
/**
* 以太网初始化
* @return 初始化结果
*/
static xnet_err_t ethernet_init(void) {
xnet_err_t err = xnet_driver_open(netif_mac);//用于后面实现ARP,驱动会自动将MAC地址填入netif_mac里面
if (err < 0) return err;
return XNET_ERR_OK;
}
3. 发送函数
目的:将IP包或者ARP包通过以太网帧发到上层协议(IP或ARP)
流程:添加包头,调用驱动发送
/**
* 发送一个以太网数据帧
* @param protocol 上层数据协议,IP或ARP
* @param mac_addr 目标网卡的mac地址
* @param packet 待发送的数据包
* @return 发送结果
*/
static xnet_err_t ethernet_out_to(xnet_protocol_t protocol, const uint8_t *mac_addr, xnet_packet_t * packet) {
xether_hdr_t* ether_hdr;
// 添加头部
add_header(packet, sizeof(xether_hdr_t));
ether_hdr = (xether_hdr_t*)packet->data;
memcpy(ether_hdr->dest, mac_addr, XNET_MAC_ADDR_SIZE);
memcpy(ether_hdr->src, netif_mac, XNET_MAC_ADDR_SIZE);
ether_hdr->protocol = swap_order16(protocol);
// 数据发送
return xnet_driver_send(packet);
}
4.以太网查询包函数
以太网查询网络接口,看看是否有数据包,有则进行处理
/**
* 以太网查询网络接口,看看是否有数据包,有则进行处理
*/
static void ethernet_poll(void) {
xnet_packet_t* packet;
if (xnet_driver_read(&packet) == XNET_ERR_OK) {
// 正常情况下,在此打个断点,全速运行
// 然后在对方端ping 192.168.254.2,会停在这里
ethernet_in(packet);//这里后面就是直接传入到ARP或者IP层协议进行处理了
}
}
5.以太网输出到ARP/IP层的函数
/**
* 以太网数据帧输入输出
* @param packet 待处理的包
*/
static void ethernet_in (xnet_packet_t * packet) {
// 至少要比头部数据大
if (packet->size <= sizeof(xether_hdr_t)) {
return;
}
// 往上分解到各个协议处理
xether_hdr_t* hdr = (xether_hdr_t*)packet->data;
switch (swap_order16(hdr->protocol)) {
case XNET_PROTOCOL_ARP:
break;
case XNET_PROTOCOL_IP: {
break;
}
}
}
6.上层协议的大小端存储方式
网络上的数据大小端处理不一样,一般是大端存储,但是x86/ARM上大多以小端存储。所以需要大小互换!!
两字节大小端互换代码:
#define swap_order16(v) ((((v) & 0xFF) << 8) | (((v) >> 8) & 0xFF)) //大小端问题,这里两字节的协议前后字节互换
7.以太网的差错检测
本节以太网的功能就是仅仅将包丢在网络上去,然后也不管对方有没有收到,最多只是在上层提供一个CRC校验,及对方如果接受到,就会检测CRC,一般还是更底层的硬件电路去检测。如果发现有错,那么会把包丢掉,上面的层是不会收到的。
以太网只能提供这一种差错检测,没有提供流量控制,数据检测等功能。
本节完整代码地址:
💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖
热门专栏推荐
🌈🌈计算机科学入门系列 关注走一波💕💕
🌈🌈CSAPP深入理解计算机原理 关注走一波💕💕
🌈🌈微服务项目之黑马头条 关注走一波💕💕
🌈🌈redis深度项目之黑马点评 关注走一波💕💕
🌈🌈JAVA面试八股文系列专栏 关注走一波💕💕
🌈🌈JAVA基础试题集精讲 关注走一波💕💕
🌈🌈代码随想录精讲200题 关注走一波💕💕
总栏
🌈🌈JAVA基础要夯牢 关注走一波💕💕
🌈🌈JAVA后端技术栈 关注走一波💕💕
🌈🌈JAVA面试八股文 关注走一波💕💕
🌈🌈JAVA项目(含源码深度剖析) 关注走一波💕💕
🌈🌈计算机四件套 关注走一波💕💕
🌈🌈数据结构与算法 关注走一波💕💕
🌈🌈必知必会工具集 关注走一波💕💕
🌈🌈书籍网课笔记汇总 关注走一波💕💕
📣非常感谢你阅读到这里,如果这篇文章对你有帮助,希望能留下你的点赞👍 关注❤收藏✅ 评论💬,大佬三连必回哦!thanks!!!
📚愿大家都能学有所得,功不唐捐!