- 博客(75)
- 收藏
- 关注
原创 dpdk实现超时重传和快速重传-阶段四
arm上弦,布防= 使能中断待确认队列里面有两个部分,已上线段 和 延迟段。这次实现RTT估计,RTO定时器,超时重传,快重传。首先RTT估计,是根据发送seg的时间和接收到对应的ack来计算RTT往返时间。RTO定时器是发送seg后,进行定时,如果没有接收到ack,就进行超时处理,然后延长等待时间,超时次数超过阈值视为连接中断,释放资源。快速重传是指接收到三次重复ack,视为丢包,进行重传。
2026-11-09 16:32:29
134
原创 dpdk实现拥塞控制和慢启动-阶段三
现在是tcp_pkt_send直接调用tcp_send_load_datagram,tcp_send_load_datagram首先判断是否是控制包,是的话交给tcp_send_ctrl_datagram处理,不是的话将datagram挂载到snd_cur上。之前tcp_pkt_send的发送逻辑是ctrl和数据包混在一起在tcp_send_pump中发送,在tcp_send_pump中判断是否是数据包,是的话就马上发送,如果是应用数据的话就分片然后再发送。不是的话,就更新una。
2026-11-07 23:19:28
205
原创 dpdk实现未确认队列-阶段二
再切一片,再更新参数,再把该片发送出去,再加入未确认队列,通过外面的while(sent < TCP_SND_PUMP_MAX)控制切多片,而不是在tcp_seg_alloc中实现切多片,tcp_seg_alloc只切一片。如果有5个线程都在工作,此时来了新任务,进行single唤醒,但是没有线程被唤醒,sigle丢失。如果是应用数据,tcp_send_load_datagram只指明snd_cur,然后在tcp_send_pump中进行分片,最后在tcp_seg_transmit进行发送。
2026-11-05 13:30:33
223
原创 dpdk实现tcp滑动窗口-阶段1
仓库:https://gitee.com/fan-junchuang/1010-dpdkrwnd、cwnd、swnd、BIF这四个在一个tcp报文中的体现就是wind size、seq、ack这三个字段。window scale体现在tcp报文中option中。首先看我们实现了什么,实现了一个结构体里面的参数都是和窗口有关的,是实现滑动窗口、拥塞控制、慢启动等的基础。snd_una、snd_max、snd_nxt将发送队列分成了四个部分。示意图如下。
2026-11-04 00:26:45
208
原创 tcp三个窗口rwnd,cwnd,swnd以及BIF,拥塞点的理解
当接收窗口rwnd构成限制时,BIF=rwnd,表示在途的数据量等于接收方最大的接收量,所以这时候进入“TCP Window Full”状态,不再发送数据,直到新的ack到来。cwnd的增减不意味着传输速率的增减,cwnd的增加只能说明发送的数据量增加,速率还要考虑RTT时间,有可能cwnd增加,RTT也增加。cwnd:拥塞窗口,是通过拥塞算法计算出来的值,是一个虚拟的值。cwnd是发送方理论上能够发送的最大数据量,swnd是实际上能够发送的最大数据量,BIF是真实情况下发送的数据量。
2026-11-02 18:16:19
236
原创 2-15-dpdk实现epoll
首先网卡中有了新数据,会在main线程中加入到in ring中,pkt process线程对in ring中的数据进行解析,判断类型,然后构造datagram加入到entry recvbuf中,tcp server 阻塞在 epoll_wait,由协议栈回调唤醒后再读 recvbuf。整集的数据结构就是红黑树,将节点添加到就绪队列中时,不是从整集中去除该元素再添加到就绪队列,而是直接在原本的基础上加入到就绪队列,这个元素可以既在整集中又在就绪队列中。如果内存不够再分配内存的话,是呈几何级增长的。
2026-10-31 18:58:41
202
原创 读写锁、互斥锁、自旋锁、信号量、rcu的用法以及区别
free(old);这是因为线程之间的竞争关系,三个线程都没有加锁,reader先读取x再读取y,当reader读取x后,update更新了全局变量gp的值,reader再读取gp的y时就不符合y=x+1的规则了。mutex从睡眠到唤醒是微秒级的开销,所以先自旋一小段时间,如果能够拿到锁,比唤醒的开销要小。当其他线程访问时,若已加锁,就一直轮询等待,不进入休眠,所以性能比mutex互斥锁高。互斥锁,读写互斥,且只允许一个线程。读写锁允许多个线程同时读,只允许一个线程写,锁加的比较繁琐,所以性能最低。
2026-10-28 16:16:10
213
原创 dpdk添加ddos攻击检测
常规做法是把连接使用hash记录下来,key是ip和port,value就是连接的请求数量,统计连接请求的频率,如果超过阈值就视为ddos攻击。如果是水平触发,一旦有攻击就会一直触发警告,我们需要的是从正常到攻击这个状态转变才会触发警告。还可以通过TCP标记位的计数来判断,如果syn的比例远大于ack的比例,则视为攻击。ddos是一种攻击,不断给服务器发送不必要的命令,增加服务器的负担。先统计一个包的二进制表达式里面有多少个1,再统计整个包的bit数量。统计每个包的复杂度,把所有包的复杂度加在一起。
2026-10-27 11:27:33
212
原创 dpdk实现dns
前面我们已经用dpdk实现了udp server,现在基于udp server实现dns 服务器。DNS是udp上层的协议,所以可以直接调用我们实现的nrecvfrom与nsendto来实现。DNS有三种资源记录类型,分别是A记录(ipv4地址记录)、AAAA记录(ipv6地址记录),TXT记录(文本记录)。A记录(ipv4地址记录)就是将域名指向一个ipv4的地址。AAAA记录(ipv6地址记录)是将域名指向ipv6的地址。TXT记录(文本记录)
2026-10-25 19:01:20
136
原创 dpdk实现kni
tcp process中处理了很多 222.128的数据,所以把tcp process的打印关掉了,用客户端连接222.134,没有反应,怀疑tcp process一直在处理222.128,处理不过来222.134的数据。我们可以只用dpdk解析特定的协议,然后把其他的协议写回内核中,交由内核处理,实现把数据写回内核的就是kni,也可以通过kni从内核中读取到数据。有时候都不能正常收发。dpdk会虚拟化一个eth,称之为veth,这个veth在内核中也有一个相应的结构体net_device。
2026-10-22 23:48:03
210
原创 dpdk实现tcpserver
组织好了后加入entry的recvbuf,那么谁来读取entry的recvbuf呢?通过fun_tcp_entry_create创建synClientEntry,并且把synClientEntry加入entry table,synClientEntry指明了sip,dip,seq等,客户端有数据包到来,可以根据四元组查找到cliententry,再根据cliententry状态为NG_TCP_STATUS_ESTABLISHED,执行fun_tcp_handle_established_rcvd。
2026-10-21 19:11:59
266
原创 dpdk实现tcp回显
判断接收的数据包合不合法时,就可以通过判断源ntohl(tcphdr->sent_seq)与entrytcp->ack_next是不是相等来判断,相等则合法。组织的发送数据的包的ack与seq和ack包的这两个参数相等。tcp_socket_entry是面向连接的,一个连接一个tcp_socket_entry,里面有两个参数。组织ack包的时候,需要更新entrytcp的ack_next与seq_next。,第一个包是接收客户端的数据,第二个包是构造ack,第三个包是构造发送的数据包。
2026-10-19 00:37:09
183
1
原创 dpdk实现tcp三次握手
A4是服务端向客户端发送了五个字节的数据,每个数据占用一个序号,起始序号是2001,一共占用了2001,2002,2003,2004,2005这五个序号,那么服务端下一次再发送的起始序号就是2006,所以A5的ack=2006。在tcp send中,遍历所有的连接,如果sendbuf不为空,就取出datagram,然后根据datagram中的内容组织数据报,并发组织好的数据报加入到ring out中。A4服务端发送来的ack=1001,表示客户端A5的起始序号就是1001,所以seq=1001。
2026-10-18 17:58:12
154
原创 dpdk实现UDPserver
已经开辟了两个线程,main loop与pkt process。main loop负责dpdk 原始数据的收发。pkt process负责对dpdk原始数据的解析 以及 打包。现在需要实现udp server。udp server需要管理多个udp连接,所以再开辟一个线程,用来处理udp server。为每一个连接提供一个item,item里面存放sockid,src ip,src port,rbuf ,wbuf等。对这些item进行管理,初步使用双向链表管理,后续会优化为红黑树管理。
2026-10-17 18:02:14
206
原创 dpdk实现arp协议,icmp协议-----架构优化
一个dpdk的线程对应一个 lcore ,logical core,DPDK的逻辑核。之前的代码都是写在main中的while里面,接收数据,解析协议帧,打包协议帧,发送数据,定时器发送arp请求,都是在main loop中。再开辟一个线程用来pkt_process专门处理协议帧的解析和打包,打包好的协议帧放入out ring中。对于in ring和out ring的入队出队操作都只有一个线程,所以使用单生产者入队,单消费者出队即可。对于我们的架构,有两个线程,mian线程和,pkt process线程。
2026-10-14 23:08:45
237
原创 dpdk实现icmp
ICMP(Internet Control Message Protocol,网际控制报文协议):用来传递控制信息,不携带用户数据。这里还有两个字段identifier和sequence number。dpdk没有提供icmp的计算校验和的函数,需要自己实现。icmp类型:0 Reply 回显应答。标识符identifier:原样回显。我们这里只实现了8,请求回显。,这样双方才能匹配。
2026-10-14 17:34:16
90
原创 dpdk实现arp协议
DPDK的mempoll底层用的是环形队列管理的,队列的大小刚好是2的幂,为了区分环形队列的满和空需要牺牲一个元素,所以在设置mempool的时候通常设置为2的幂减1。arp请求需要给局域网内的每一个ip都发送一遍,在发送的时候如果arp表里面没有它的mac地址就按照上面的方式组织,如果有mac地址,就按照正确的mac地址组织协议帧。多队列网卡,一个队列对应一个中断,中断可以绑定到一个cpu上,也可以将处理该队列数据的进程绑定到这个cpu上,就实现了一个队列一个进程,一一对应的关系。
2026-10-14 17:11:07
176
原创 DPDK实现epoll
双方都需要维护两个window,一个是自身的window,表示自身还可以接收多少的数据,另一个是对方的window,表示这次最多可以发送多少数据,也就是对方最多可以接受多少数据。整集的数据结构就是红黑树,将节点添加到就绪队列中时,不是从整集中去除该元素再添加到就绪队列,而是直接在原本的基础上加入到就绪队列,这个元素可以既在整集中又在就绪队列中。查找后怎么设置的epitem的event,没找到相关代码啊,只有如果该epitem在就绪队列中就合并事件,没有单独处理epitem的event。
2026-10-07 18:41:09
216
原创 DPDK原理以及解析tcp/udp协议包
一个客户端对应一个skbuffer,如果有大量的客户端,就会有大网卡—>网卡驱动—>tcp/ip协议栈---->通过recv,send拷贝到用户空间量的skbuffer,那么通过什么方式来组织这些skbuffer?dpdk主要处理的是把数据包收发的路径,而不是处理的过程,中间的协议解析和机制都是存在的。CRC-32 多项式除法,其中一个步骤是把二进制串的每一位看作多项式的系数,那么顺序不一样,计算出来的多项式就不一样,所以会改变。每个 CPU 有自己的内存,访问自己内存快,访问别人的内存慢。
2026-10-06 11:47:22
214
原创 用DNS域名解析理解同步,异步,阻塞,非阻塞
while里面recvfrom,while (1) recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&addr, (socklen_t*)&addr_len);while里面recvfrom,while (1) recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&addr, (socklen_t*)&addr_len);//nonblock。
2026-09-23 21:15:27
247
原创 rector搭配epoll的LT/ET分别实现webserver
客户端发送2000字节的数据,接收缓冲区是1024字节,ET情况下epollwait触发了一次EPOLLIN,进行循环读取,第一次recv时读了1024字节的数据,还剩976字节数据未读,但在第二次recv之前,客户端断开了连接,接收缓冲区为空,那么第二次recv时发现接收缓冲区为空,就进入阻塞的状态,将线程挂起,这时候由于客户端已经断开了连接,整个线程就会一直在这里挂起,卡死在了这里。在LT的情况下,recv中没有数据,线程也会挂起,有了数据就会触发EPOLLIN事件,在ET下这样不也是可以的吗?
2026-09-21 18:10:32
212
原创 epoll实现百万并发
服务端server:192.168.222.1298888-8987 100个端口客户端client1:192.168.222.128客户端client2:192.168.222.130客户端client3:192.138.222.131客户端虚拟机可以通过VMware的链接克隆创建,这样占用的内存会少很多。但是克隆出来的虚拟机和被克隆的机器是同一个ip地址,需要进行修改。使用epoll面向事件reactor模式测试百万并发。
2026-09-20 14:15:46
111
原创 epoll实现reactor模式
可以类比一下学生集体体检,如果是面向io的话,就相当于这个学生要逐个科室的去跑,这个科室是不是抽血的、这个是不是视力检查、那个是不是测心率等等。但如果是面向事件的,就是抽血的都来这里,视力检测的都来这里,测心率的都来那里,按照事件区分,统一管理。而现在的写法是对io的处理,并不是对事件的处理。reactor的好处,不同的事件处理不同的回调函数,相互分开、互相独立。而其他fd的回调函数recv_callback指向的是recv_cb,而我们再业务中要负责的是对io事件的处理,业务的处理,不是io本身。
2026-09-17 22:44:37
186
原创 线程池详解
什么是线程池?提前创建好一些线程,把线程放在一起,也就是放在一个池子里面,进行统一的管理。线程池中的线程回去处理任务队列中的任务,当有任务时就去执行,执行完后线程不销毁,而是回到池子里面等待下一次的任务。
2026-09-17 15:32:09
195
原创 IO多路复用-select/poll/epoll详解
对于每个sockfd的操作,需要设置对应的参数,哪一个sockfd对应的是ev.data.fd,什么事件对应的是ev.enents(IN 还是 OUT),然后使用epoll_ctrl使设置好的参数生效,也需要指明是添加修改还是删除。当socket有事件触发时,协议栈会通知epoll,然后epoll会把触发事件的epitem加入rdlist,再唤醒挂起在wq上的线程,然后该线程把rdlist上的epitem拷贝到用户态。而epoll是每次加一个,每次加一个,逐渐累积的。
2026-09-17 15:19:43
143
原创 从源代码到可执行文件(.exe)的完整过程
makefile在一些简单的工程完全可以人工手写,但是当工程非常大的时候,手写makefile也是非常麻烦的,如果换了个平台makefile又要重新修改。报错信息晦涩,缺乏“人性化”的报错。处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置。文件),就能自动生成一个project的makefile文件或者ninja文件,然后就可以通过ninja或者make进行启动编译了。处理所有条件预编译指令,比如“#if”,“#ifdef”,“#elif”,“#else”,“#endif”。
2026-07-28 11:09:05
180
原创 最小堆定时器
处理到期的任务,使用循环,如果到期就执行回调函数,然后移除该任务,并释放该任务;删除任务结点,先将队尾元素移动到需要删除的结点位置,然后再调整最小堆的结构体以满足最小堆的性质。对于任务结点,需要的元素有 任务索引,任务定时时间,回调函数,任务上下文。添加定时器任务,申请任务结点内存,然后设置回调函数以及定时时间,压入堆中。对于最小堆结点,需要一个任务指针数组,最小堆的容量,最小堆中的元素个数。最小堆可以使用数组实现,因为最小堆可以满足,在数组中索引为k的结点,初始化最小堆,将最小堆结构体清空。
2026-04-30 11:34:57
47
原创 三层时间轮的实现
比如一个70s的任务在插入时会插入到1分钟对应的链表下,到1分钟后,那这个70s的任务取出来,再进行添加,那岂不是还是原来的位置?其实就是在59s进入timer_update函数执行60s的任务(59s的任务已经在58s进入timer_update函数执行完了),在60s进入timer_update函数执行61s的任务。做一次测试,添加58s,59s,60s,61s任务,使用两次执行,运行程序。这里的两次执行是在59秒进入timer_update函数,执行59秒的任务,然后推进1s,执行60秒的任务。
2026-04-29 21:00:28
415
原创 网络缓冲区
的核心是,以事件为连接点,当有IO事件准备就绪时,以事件的形式通知相关线程进行数据读写,进而业务线程可以直接处理这些数据,这一过程的后续操作方,都是被动接收通知,看起来有点像回调操作;调用read,然后立即返回一个结果,不管内核是否已经完成数据的拷贝,如果没有完成就返回一个错误,然后不断地询问,每次询问都会立即返回,直到拷贝完成,返回正确结果,就是同步非阻塞。用户进行read,内核等待网卡的数据,然后将数据拷贝到用户空间,如果用户线程一直在等待,等待时“挂起”,不做任何事情。Reactor读数据但不处理。
2026-04-22 22:23:00
415
原创 同步异步阻塞非阻塞
调用read,然后立即返回一个结果,不管内核是否已经完成数据的拷贝,如果没有完成就返回一个错误,然后不断地询问,每次询问都会立即返回,直到拷贝完成,返回正确结果,就是同步非阻塞。用户进行read,内核等待网卡的数据,然后将数据拷贝到用户空间,如果用户线程一直在等待,等待时“挂起”,不做任何事情。在内核将数据拷贝到用户空间时,用户线程不等待,拷贝完成后,内核通知用户已经拷贝完成。调用read,然后等待内核将数据拷贝到用户空间,期间用户一直等待,不做其他事情,就是同步阻塞。阻塞非阻塞是调用后是否立即返回。
2026-04-22 22:21:26
241
原创 无锁队列简介
无锁算法是非阻塞同步策略的一种,它确保至少有一个线程能在有限的步骤中完成其操作,从而在全局上避免了死锁。无等待算法是一种特殊类型的非阻塞同步,它保证所有线程都能在有限的步骤中完成其操作,从而为每个线程提供了最强的进度保障。多线程编程中,有lock-free(无锁),wait-free(无等待),blocking(阻塞),Non-blocking(非阻塞)。当一个线程占用共享资源,其他线程再去访问这个共享资源时,会发现有冲突,就去处理其他事情了(通常是进行重试)。放入队列中的是指向数据对象的指针。
2026-04-12 21:57:19
206
原创 内存池以及实现
当你malloc了16字节的内存,然后释放掉,再malloc了8字节的内存;那么还有8字节内存未被使用,如果再申请4字节内存,那还剩下4字节内存。可以一直划分,直到所剩的内存空间太小而分配不出内存,这样就会有小部分内存空间无法利用,便叫做内存碎片。char ** ptr 表示 m->free_ptr前8个字节的地址。每一小块的前8个字节(一个指针8字节)存放下一个空闲小块的地址。每一个节点具有不同大小的内存,然后再用一个结构体管理所有的节点。内存池一般分为固定大小的内存池,和大小不固定的内存池。
2026-03-27 21:46:54
182
原创 线程池的理解使用以及代码详解
池化结构是一种设计模式,用来维护有限数量的资源池。当需要使用资源时,从池中获取;使用完毕后,归还给池而不是直接销毁。自旋锁是一种忙等待(busy-waiting)的锁。线程尝试获取锁时,如果锁已被占用,它不会进入睡眠状态,而是不断循环检查锁是否释放,直到成功获取为止。适用于 临界区代码执行时间短(只有几条指令),系统调用次数频繁但竞争不激烈的情况。互斥锁是一种阻塞式锁。线程尝试获取锁时,如果锁已被占用,它会进入睡眠状态,让出 CPU,直到锁可用时被唤醒。适用于 临界区代码执行时间长;
2026-03-24 21:05:49
399
原创 KV存储 网络epoll,ntyco,io_uring 数据结构:数组,红黑树,哈希,跳表,B树
KV存储就是K(key)关键字 和 V(value)数值 一对一的关系,将二者存储起来。首先要实现 网络框架 ,基于网络框架 来解析信息,在对立相应的数据,数据存储的引擎也就是数据存储的数据结构要重点设计实现。网络框架 —> 解析数据 -----> 存储引擎引擎层也就是存储所用的数据结构,使用了五种数据结构数组,红黑树,哈希,跳表,B树网络层使用了三种方式接口层的5+2中2表示: 初始化 和 销毁5表示:通信的五条命令 set get mod del count。
2026-03-12 14:49:27
356
原创 跳表Skip List以及实现代码C语言
跳表(Skip List)是一种随机化的数据结构,由William Pugh在1989年提出。它通过在有序链表的基础上增加多层索引,实现了近似二分查找的效率,同时保持了链表的简单性。在普通链表中,查找一个元素需要O(n)的时间。虽然平衡树(如红黑树)可以实现O(log n)的查找,但实现复杂。完整代码见最下方。跳表通过建立索引,来提高查找元素的效率,就是典型的“空间换时间”的思想,所以在空间上做了一些牺牲,。。
2026-03-10 21:58:04
415
原创 btree B树实现key-value存储
B树属于多叉树,在存储大量数据的时候可以降低层高,内节点和叶子节点都可以存储数据.B+树的内节点不存储数据,用来做索引.
2026-03-09 22:23:11
449
原创 Hash哈希表以及代码
在每个数组位置(桶)上维护一个链表(或树结构)。当发生冲突时,将新元素添加到对应索引的链表中。如果冲突链表过长,这个时候可以将这个链表转换为红黑树、最小堆。它通过一个哈希函数(Hash Function)将任意的键(如字符串、数字等)映射到一个固定范围的索引(通常是一个数组下标),进而实现数据的快速插入、查找、删除和修改。测试 main() 函数:涵盖初始化 → 插入 → 查询 → 统计 → 删除 → 修改 的完整流程。分别实现了初始化函数,申请节点函数,销毁函数,增删改函数。
2026-03-08 22:51:19
60
原创 Hash,布隆过滤器,hyperloglog
它通过一个哈希函数(Hash Function)将给定的键映射到数组(哈希桶)中的某个位置(称为索引或哈希值),从而实现对数据的增、删、改、查操作。因为桶只有16384个,所以一定会有冲突,有冲突的话,索引对应的数值里就存放 长度更长的低位最长连续零 的数值。如果全部为 1,则可能存在。对于一个输入的字符串,首先得到64位的hash值,用前14位来定位桶的位置(共有 ,即16384个桶)。:由于哈希函数的输出空间是有限的,而输入可能是无限的,因此不同的键可能会被映射到同一个索引,这种情况称为“哈希冲突”。
2026-03-07 22:00:23
410
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人
RSS订阅