TCP/IP协议(一、自己动手实现udp)

article/2025/4/28 5:58:59

对TCP/IP协议都只是听过,没有仔细研究过,一些知识体系也比较零散,什么三次握手,四次挥手,滑动窗口,零拷贝技术等等,都是知识有这么个东西,而不知道具体是啥,这几天还是根king老师学习TCP/IP协议栈,受益匪浅,所以把这几天学习的TCP/IP协议的知识整理一下,形成一个自己的知识体系。

1.1 7层5层模型

每次说到tcp/ip都要上这一张图,这样才显得自己很专业(其实并不专业)。网络上介绍这5层模型的挺多的,我这里就引用一下
OSI七层模型与TCP/IP五层模型
在这里插入图片描述
顺便盗两张图:
第一张是描述不同的设备工作在那一层:
在这里插入图片描述
第二张是每一层的主要协议是啥:
在这里插入图片描述

从最后一张图就可以看到,之前实现的http协议就是在应用层的(博客还没总结,抽个时间写写,自己实现的http),然后我们在应用层就是直接调用api,比如send和recv,这个send和recv发送的数据到哪里去了呢?其实通过图就可以看出,我们应用层调用了send和recv之后,数据发送到传输层(也就是tcp/udp),这里就是我们今天要自己动手实现UDP协议,也就是造轮子,我一直以为要学习一个开源代码,还是自己动手实现一个比较靠谱,所以今天我们来实现一个简单的UDP协议。

1.2 动手实现udp

1.2.1 udp数据帧的包头

在这里插入图片描述
这是king老师自己画的,这里又盗图了。
右边是tcp/ip模型,我们从应用程序中调用sendto的时候,用户层会把用户数据传到传输层,传输层在用户数据的基础上添加传输层的数据包头(其实就是端口),传输层再往下发数据,网络层也会在传输层数据的基础上添加上自己的网络层的包头(其实就是IP),网络层继续往下走,数据链路层也会封装自己的包头(其实就是mac地址),这样经过层层包装,才送到物理层去发送,另一端接收到的数据,就是一层一层就解包,就跟收快递一样,这里就不细说了。

1.2.2 数据链路层包头

在这里插入图片描述
数据链路层的包头14个字节,包括目的mac地址,源目的mac地址,和类型

封装的代码如下:

#define ETH_LENGTH      6
struct ethhdr {unsigned char dest_mac[ETH_LENGTH];   //源mac地址unsigned char src_mac[ETH_LENGTH];	  //目的mac地址unsigned short proto;				  //网络层协议类型,通常是IP协议,0x0800
}

1.2.3 IP包头

在这里插入图片描述
版本:包含IP数据报的版本号:IPv4为14,IPv6为6.

首部长度:标明IP数据包头部长度,单位是字也就是4个字节,它是一个4位的字段,所以IPv4的头部为15*4=60个字节的数据,这个字段是正常的值是5(没当没有选项时)。IPv6不存在这个字段,其头部长度固定为40字节。

服务类型(Tos):区别不同服务

总长度:IPv4数据集包的总长度。由于它是一个16位的字段,所以IPv4数据报的最大长度(包括包头)为65535字节。

标识符:这个域的作用是当一个大的数据报被拆分时,拆分成的小的数据段的这个域都是一样的。

标记和段偏移以后再说

TTL:生存时间,设置一个数据报可经过的路由器数量的上限。发送方将它初始化为某一个值建议为64,每台路由器在转发数据报时将该值减1,表示在网络中经过了几个网关。(当这个字段值为0时,该数据被丢弃,防止出现不希望的路由环路而导致数据报在网络中永远循环)。

协议号:表示应用层使用的协议,比如UDP=17,TCP=6。

首部校验和:IP头部的校验和(为什么每层都加校验,是因为在以前双绞线的时候,数据容易出错,每一层加校验就可以知道那一层的数据出现了问题,容易定位问题)

源IP地址:数据报来源主机的IP地址

目的IP地址:数据报目的主机的IP地址

封装的代码如下:

struct iphdr {unsigned char version : 4;unsigned char hdrlen : 4;   //首部长度unsigned char tos;unsigned short totlen;unsigned short id;          //分片标识unsigned short flag : 3;unsigned short offset : 13;unsigned short ttl;  //每经过一个网关,交换机就减1,默认值是64,跨一次网络都减1unsigned char proto;    //应用层协议unsigned short check;unsigned int sip;       //源ip地址unsigned int dip;       //目标ip地址
};

1.2.4 udp包头

在这里插入图片描述
udp的包头就比较简单了,没有ip包头那么多。
就不解释了,直接上代码:

struct udphdr
{unsigned short sport;unsigned short dport;unsigned short length;unsigned short crc;
};

1.2.5 udp帧

udp数据帧其实就是包前面的包头全部叠加,在加上用户数据,这里用户数据用柔性数组来定义:

//udp 包 = ethhdr + iphdr + udphdr + userdata
struct udppkt {struct ethhdr eh;          // 14struct iphdr ip;            // 20struct udphdr udp;          // 8unsigned char body[0];
};

1.2.6 用户层实现协议基础

怎么实现用户层程序来实现协议,在github上有一个开源代码netmap,是把网卡的数据直接映射到内存中,不经过内核,有点像DMA技术,所以我们需要用这种技术来完成网卡接收到数据直接放到内存中,代码github连接。

就是这个驱动编译搞了好几天,用了几个系统,最后没办法了,只能换回Ubuntu16的系统,因为这个驱动好像是基于Ubuntu16写的,所以只能乖乖的换回来,以后有空再分析一下不同系统安装的步骤。

1.2.7 简单实现udp协议

简述一个udp协议的步骤

1struct nm_desc *nmr = nm_open("netmap:eth0", NULL, 0, NULL); //使用netmap打开一个虚拟网卡设备。
2、pdf.fd = nmr->fd;pfd.events = POLLIN;int ret = poll(&pfd, 1, -1);//使用poll来管理nmr->fd的设备文件3、如果poll有数据过来了,就可以处理数据了,首先先转换成ethhdr包,判断一下协议是否是IP协议,IP协议的值是0x0800.if(ntohs(eh->h_proto) == PROTO_IP )4、如果网络层是使用ip协议,这时候就可以获取到ip包,并解析,得出传输层是使用什么协议的,如果是UDP就是17,icmp是1if(ntohs(eh->h_proto) == PROTO_IP )5.如果传输层是udp,这时候就可以取数据了,这里需要注意,我们是使用柔性数据接收的,所以需要拿到数据长度,这个数据长度是udp包已经有的,if(udp->ip.proto ==  PROTO_UDP)  {unsigned short udplen = ntohs(udp->udp.length);udp->body[udp->udp.length-8] = '\0';
}

其实比较简单的,做过单片机的,对这种协议都不陌生,当初手撕I2c的时候,只不过这个是分层处理了,用了之后,才越来越觉得分层的好处。

测试的的结果:
在这里插入图片描述

1.2.8 实现arp

上述程序实现的结果是只能发现一段时间,过了一会就发送了不了。

这是缺少实现arp协议了,(其实我之前也不知道arp是啥),现在要用到了,就要普及一下了。

arp协议详解,我看这这篇文章就讲的不错,ARP协议详解

讲的很详细,我这里就总结一下:
ARP是地址解析协议,在以太网中,一台主机和一台主机通信,是通过mac地址通信的,但是在一个局域网内,我们只知道ip地址,而不知道mac地址,这时候就需要ARP协议,IP地址和mac地址的一个映射表,通过IP地址查找到对应mac地址。

ARP映射主要是动态方式,如果其他主机要想知道ARP映射表,会轮询发送arp数据包,所以我们实现的udp协会也需要回复一个arp包。

1.2.8.1 arp包

在这里插入图片描述
这里借用了那篇博客的arp包的图,

代码封装如下:

//ip层协议
struct arphdr {unsigned short hw_type;unsigned short proto_type;unsigned char  hw_addr_len;unsigned char  proto_addr_len;unsigned short op;unsigned char  s_mac[ETH_LENGTH];      //mac地址unsigned int   sip;unsigned char  d_mac[ETH_LENGTH];unsigned int   dip;
};

arp包是网络层的协议,已经是最高层协议了,上面没有其他层了。

arp包也是基础数据链路层的,所以整个arp包:

//ip层协议
struct arphdr {unsigned short hw_type;unsigned short proto_type;unsigned char  hw_addr_len;unsigned char  proto_addr_len;unsigned short op;unsigned char  s_mac[ETH_LENGTH];      //mac地址unsigned int   sip;unsigned char  d_mac[ETH_LENGTH];unsigned int   dip;
};

1.2.8.2 arp包回复

程序思路:

1、因为arp协议是网络层的,所以需要判断数据链路层发过来的协议:
if(ntohs(eh->h_proto) == PROTO_ARP)2.收到ARP数据包的时候,需要判断是不是自己的IP地址
if(arp->arp.dip == inet_addr("192.168.121.155")3.如果匹配上的话,就回复一个arp包
void echo_arp_pkt(struct arppkt *arp, struct arppkt *arp_rt, char *hmac)
{memcpy(arp_rt, arp, sizeof(struct arppkt));memcpy(arp_rt->eh.h_dest, arp->eh.h_src, ETH_LENGTH);str2mac(arp_rt->eh.h_src, hmac);   //源mac  ffffffffarp_rt->eh.h_proto = arp->eh.h_proto;arp_rt->arp.hw_addr_len = 6;arp_rt->arp.proto_addr_len = 4;arp_rt->arp.op = htons(2);str2mac(arp_rt->arp.s_mac, hmac);arp_rt->arp.sip = arp->arp.dip;memcpy(arp_rt->arp.d_mac, arp->arp.s_mac, ETH_LENGTH);arp_rt->arp.dip = arp->arp.sip;
}
主要是把源mac和源ip换成目的mac和目的ip,然后再把自己的mac和ip填充到源mac和目的ip

在加上这个arp协议之后,我们实现的udp协议就比较稳定了。

1.2.9 实现ping

如果我们在电脑端ping这个虚拟机的ip地址,发现是ping不通,为什么呢?是因为我们没有实现ping协议。

ping其实是icmp协议中的,icmp协议是在网络层的协议,就是iP报文中的一部分,一直顶这ip报文这个大哥的大腿,做一些不可描述的事情。协议详解我目前不善长,所以还是引用了别人的协议详解,ICMP协议全解析,这个就讲的很清楚,可以好好看一看。

1.2.9.1 icmp协议包

在这里插入图片描述
看到这个协议包是不是很开心,比较简单,

struct icmppkt {unsigned char type;unsigned char code;unsigned short sum;  //前面才是icmp的数据包,这次封装的不好unsigned short id;   //这个是ping包的idunsigned short num;	  //是ping包的num
};

ping包的图也不好找,直接贴出代码:

struct pingpkt {struct ethhdr eh;       //14struct iphdr ip;        //20struct icmppkt icmp;    //8unsigned char data[0];  //柔性数组存储数据
};

1.2.9.2 ping请求包

所以就是我们接受到这个ping包之后,需要恢复一个ping包,通过wireshark抓包工具:
在这里插入图片描述
ICMP是在IP层的,并且协议是1,还有带上目的ip,所以首先我们做的是判断协议类型和ip地址:

 if(udp->ip.proto ==  PROTO_ICMP)  {if(udp->ip.dip == inet_addr("192.168.121.155"))

如果这两个满足就可以恢复ping包了,我们再看看icmp包的抓包图:
在这里插入图片描述
type:8就是请求的意思,可以看协议详解,就是不太明白为什么要带一串数据,不明白就先留着,以后可以看看。

1.2.9.2 ping回复包

代码简单思维:

1、先通过接受回来的ping包,计算出data数据的长度(就是莫名其妙的那个数据),通过这个长度申请一个ping包的内存,这里是柔性数据,记得申请data数据长度。2、把整个ping包数据拷贝到要发送的数据包中3、准备ethhdr包,把mac地址的源和目的交换4、准备ip的数据包,把源ip和目的ip交换,比较计算总长度,这个很重要,记得加上icmp的数据长度5、准备ping包,type=0是应答8的请求,并且计算校验和,这个校验和是抄king老师的6、调用发送接口,发送数据包
nm_inject(nmr, ping_rt, len);

完整代码如下,打印信息我还没去掉,你们可以去掉打印信息

struct pingpkt* echo_ping_pkt(struct pingpkt *ping, unsigned short *len)
{if(ping == NULL) {printf("ping is null\n");return NULL;}//第一步unsigned short icmp_data_len = ntohs(ping->ip.totlen)-sizeof(struct iphdr)-sizeof(struct icmppkt); //strlen(ping->data)printf("icm_data_len %d\n", icmp_data_len);struct pingpkt* ping_rt = NULL;ping_rt = malloc(sizeof(struct pingpkt)+icmp_data_len);if(ping_rt == NULL) {printf("echo_ping_pkt malloc error\n");return NULL;}//第二步memcpy(ping_rt, ping, sizeof(struct pingpkt)+icmp_data_len);printf("ping->data %s %d %d\n", ping->data, icmp_data_len, sizeof(ping_rt->data));//memcpy(ping_rt->data, ping->data, icmp_data_len);printf("ping_rt->data %s\n", ping_rt->data);//第三步memcpy(ping_rt->eh.h_dest, ping->eh.h_src, 6);memcpy(ping_rt->eh.h_src, ping->eh.h_dest, 6);ping_rt->eh.h_proto = ping->eh.h_proto;//第四步ping_rt->ip.sip = ping->ip.dip;ping_rt->ip.dip = ping->ip.sip;printf("ip tolen = %d %d\n", ntohs(ping->ip.totlen), icmp_data_len);unsigned short ippkt_len = sizeof(struct iphdr)+sizeof(struct icmppkt)+icmp_data_len;ping_rt->ip.totlen = htons(ippkt_len);//第五步ping_rt->icmp.type = 0;ping_rt->icmp.code = 0;printf("ping->id %d ping->seq %d\n", ping->icmp.id, ping->icmp.num);ping_rt->icmp.sum = 0;//ping_rt->icmp.sum = cimp_pkt_sum(&ping_rt->icmp, sizeof(struct icmppkt)+icmp_data_len);ping_rt->icmp.sum = in_cksum(&ping_rt->icmp, sizeof(struct icmppkt)+icmp_data_len);printf("dd\n");*len = sizeof(struct pingpkt)+icmp_data_len;return ping_rt;
}

校验和代码比较难算,这里就抄抄king老师的:

unsigned short in_cksum(unsigned short *addr, int len) {register int nleft = len;register unsigned short *w = addr;register int sum = 0;unsigned short answer = 0;while (nleft > 1)  {sum += *w++;nleft -= 2;}if (nleft == 1) {*(u_char *)(&answer) = *(u_char *)w ;sum += answer;}sum = (sum >> 16) + (sum & 0xffff);sum += (sum >> 16);answer = ~sum;return (answer);}

1.2.9.3 实现结果

在这里插入图片描述
左边是ping数据,右边是抓包出来的数据,一个简单实现的udp包就可以了。


http://chatgpt.dhexx.cn/article/C47fmOyw.shtml

相关文章

【Linux】传输层协议:UDP和TCP

但人不能永远浪漫下去,那会走向自我毁灭的。浪漫都是水字旁,会把人淹死的。人最终还是要进入一个规则体系,所谓,随心所欲不逾矩嘛 文章目录 一、UDP协议1.端口号2.理解UDP报头3.UDP的特点(面向数据报,全双…

Linux网络:UDP协议 | TCP协议

文章目录 前言端口号netstat -- pidof UDP协议TCP协议三次握手 四次挥手确认应答(ACK)机制超时重传机制连接管理机制理解TIME_WAIT状态理解 CLOSE_WAIT 状态 滑动窗口流量控制拥塞控制延迟应答捎带应答面向字节流粘包问题TCP异常情况TCP小结 基于TCP 和 UDP 的应用层协议TCP/UD…

C语言SOCKET编程指南

转载自:http://blog.sina.com.cn/s/blog_79b01f66010163q3.html 这篇文章完全可以作为c语言socket编程指南,无论在任何系统下。感谢作者fenglovel! C语言SOCKET编程指南 1、介绍 Socket 编程让你沮丧吗?从man pages中很难得到有…

【socket】从计算机网络基础到socket编程——Windows Linux C语言 + Python实现(TCP+UDP)

一、部分基础知识1.1 计算机网络的体系结构1.11 互联网简介1.12 计算机网络的分类1.13 协议与网络的分层体系结构▶ 协议▶ 网络的分层体系结构 1.14 OSI 七层模型(重要)▶ OSI 模型的结构▶ OSI 模型各层的功能 1.15 TCP/IP 的体系结构(重要…

C语言 C语言基础

C语言 C语言基础 一、简述 对于C语言基础相关方面的表面理解,简单介绍。 二、二进制 生活中常用的是十进制,基数0,1,2,3,4,5,6,7,8,9,。满10进1。 时钟60进制。基数0,1,2...57,58,59。满60进1。60秒为1分钟,60分钟为1小时。 计算机二进制&a…

炼一项专业技能c语言,C语言程序设计_安徽新华电脑专修学院

C语言程序设计,安徽新华电脑专修学院,2013年3月,在安徽省315国际消费者权益日纪念大会暨省消协四届三次理事会会议上,安徽新华电脑专修学院被省消费者协会授予“诚信单位”。 C语言程序设计, 并用c语言进行测试。还有b…

C++中使用UDP Socket发送字节数据

文章目录 参考代码结果展示 参考 这篇文章给的代码也能用 https://blog.csdn.net/qq_36437446/article/details/106446172 CRC代码来自https://blog.csdn.net/huijunma2010/article/details/124151471 在线计算CRC http://www.ip33.com/crc.html 代码 这里的byte是unsigned…

本机UDP收发性能测试

测试内容 测试单机版的UPD客户端和服务端之间的性能,UDP客户端发送数据到UDP服务端,并等待服务端返回,计算出UDP的性能 测试方法 客户端和服务端部署在同一台虚拟机器上,客户端启动多个线程,同时向服务端发送指定数量…

2020复旦大学计算机夏令营机试题

2020复旦大学计算机夏令营机试题 这个是开卷编程,不计入成绩,但是面试会问你做的情况。

复旦大学机试题题解(2017~2021)

前言 尝试把复旦大学历年的机试题做一遍,能做多少是多少了,因为没有数据集所以也不好确认是不是对的~(题目来自王道往届学长学姐回忆以及某位热心的同学整理,如果了解来源后,我一定备注来源) (…

复旦大学:专硕没住宿?我们帮你建!还给补助!

去年,小编发过一篇文章,里面爆出了复旦大学等学校的专硕可能不提供住宿了: 北京大学 上海交通大学 复旦大学 专硕可能不提供住宿! 后来,在复旦大学2019年招收攻读硕士学位研究生简章中,学校官方公布了不为专…

复旦大学机试题(2017-2021)

计院2021 1.完全二叉树 给定一颗二叉树,树的每个节点的值为一个正整数。如果从根节点到节点 N 的路径上不存在比节点 N 的值大的节点,那么节点 N 被认为是树上的关键节点。求树上所有的关键节点的个数。请写出程序,并解释解题思路。 例子&a…

三战上岸复旦工研院考研回忆(无干货)

干货链接: 复旦085400电子信息专硕初试经验分享 复旦085400电子信息专硕复试经验分享 复旦软院电子信息初试961真题回忆 说说一下我的考研经历,都是流水账,不涉及干货(干货参照其他博客),如果读者能有所借…

2020年复旦大学夏令营机试题及代码(VS2019 C++)

注:代码是本菜鸡自己个儿写的,没有找到参考答案,欢迎各位大佬批评指正. 题目如下图所示(图片来源网上): 第1题 该题主要考察拓扑排序.其实该算法有通用模板,但我写的时候没有意识到使用,代码不是很规范. #define _CRT_SECURE_NO_WARNINGS#include &…

复旦大学研究生机试(2019)

1. 计算机学院 今年的题目可以说是挺难的,第一题虽然像是送分题,实际上也不是很简单。第二题第三题是动态规划问题,而且复旦据说会卡大数,今年150人考生据说只有一个AC,大部分人只做出第一题,个别零分。 …

【20保研】2019年复旦大学工程与应用技术研究院全国优秀大学生夏令营通知

点击文末的阅读原文或者公众号界面左下角的保研夏令营或者公众号回复“夏令营”是计算机/软件等专业的所有保研夏令营信息集合,会一直更新的。 为了促进我国高校优秀大学生之间的交流、加强学生对复旦大学工研院的了解、特别是吸引优秀学生继续深造,探索…

复旦大学计算机专业硕士平均工资,在复旦大学当教授“月薪”是多少?这个工资条,让网友非常羡慕!...

文章原创,版权归本作者所有,欢迎个人转发分享 随着中国的高等教育发展的重视,很多的高校也是不负众望,不仅在国内知名度很高,在国外也享有盛誉的。 在中国知名度最高的大学就是清华、北大、复旦等高校,是受…

复旦计院、工研院2019机试真题及答案详解

计院 A 相隔天数 输入一个 yyyymmdd 格式的时间&#xff0c;如 20190318&#xff0c;计算与 20190205 相差的天数&#xff0c; 取绝对值&#xff0c;所有输入在 19000101 和 21000101 之间。 样例输入&#xff1a;20190208 输出&#xff1a;3 #include<iostream>us…

复旦计院、工研院2018机试真题及答案详解

计院 A 求众数 众数就是一个序列中出现次数最多的数字。 如果不唯一&#xff0c;则输出小的那个值。 给定第一个代表有几个数字。 1<n<10^5 每个数字在 int 范围内样例&#xff1a; 输入&#xff0c; &#xff08;第一个代表有几个数字&#xff09;8 10 3 8 8 …

2019年复旦大学机试题

机试&#xff1a; 一、计算机学院&#xff1a; 1、 算法笔记上有类似题&#xff0c;且time与time2都是输入。 而本体time是输入&#xff0c;time2是题目给的。 设置time与time2&#xff0c;为计算方便设定让time恒小于time2。 方法&#xff1a;从time&#xff08;较早的日期…