简介项目将卷积神经网络与软件定义网络结合起来完成网络数据包识别、流量监控以及基于蚁群算法的最短路径计算适合作为计算机、网络、人工智能相关专业的毕业设计或课程设计参考。压缩包共21个文件大小约2.03MB里面既有2个CSV训练/测试数据集和Pcap抓包样本也有多个Python功能模块例如基于Keras的CNN流识别、Scapy函数示例、SDN启动与包处理脚本以及配置文件和README说明。目录结构清晰代码注释详细可以直观看到数据从预处理、模型训练到SDN联动调用的完整链路。通过该项目学习者能同时理解CNN在流量分类上的特征提取优势以及SDN集中控制下的监控与优化思路。已有52人学习下载对希望快速上手CNN和SDN融合应用的同学来说是一份内容紧凑、可直接落地实践的参考资源。1. 当SDN控制器遇上一维卷积先从一次流量识别说起接手这个项目时最先要回答的问题不是“CNN怎么跑”而是“SDN控制器凭什么需要CNN”。传统网络流量识别靠端口号或DPI深度包检测但加密流量和动态端口一多这两招都容易失效。而SDN控制器天然掌握全网流表如果它能直接从报文里学会“这是视频流还是恶意扫描”就能在不改硬件的前提下做精细化调度。这个毕设项目正是把两者缝合在一起用Scapy从pcap文件里提取报文特征用一维CNN做流级识别再把训练好的模型接到SDN控制路径上顺带用蚁群算法解决拓扑里的最短路径计算。整套代码用Python写成适合拿来当课程设计或毕业设计的骨架。本文后续所有命令和代码都基于项目包里的目录结构展开你拿到的压缩包解压后应能看到RenGongZhiNeng、data、PacketingPacket.pcap等文件。2. 从pcap到训练集Scapy的报文解析到底在解什么2.1 原始报文为什么不能直接喂给CNN网络报文本质上是字节串但CNN期望的输入是形状固定的张量。直接拿变长的以太网帧去训练第一个batch就会因为维度不一致报错。项目里的scapy(函数举例).py就是用来解决这个问题的它把每个报文转成固定长度的特征向量项目里用的是IP层的五元组信息加长度统计。这种做法的好处是特征维度可控坏处是丢掉了载荷内容——但对于流级识别场景四层头部的统计规律往往已经足够。2.2 用Scapy批量切割pcap的代码骨架下面这段代码模拟了项目里把PacketingPacket.pcap切分成流并提取特征的过程。实际工程里你大概率会直接复写它因为每个数据集的报文封装格式不一样。from scapy.all import rdpcap, IP, TCP, UDP from collections import defaultdict import numpy as np def extract_flow_features(pcap_path, max_len64): packets rdpcap(pcap_path) flows defaultdict(list) # 以五元组为key聚合报文 for pkt in packets: if IP not in pkt: # 只处理IP报文跳过ARP等二层帧 continue ip pkt[IP] proto TCP if TCP in pkt else (UDP if UDP in pkt else OTHER) flow_key (ip.src, ip.dst, ip.sport if protoTCP else 0, ip.dport if protoTCP else 0, proto) # 只取长度这一维做示范项目里会拼出多个统计量 flows[flow_key].append(len(pkt)) # 每个流取前max_len个报文长度不足补零 X np.zeros((len(flows), max_len), dtypenp.float32) for i, (key, lengths) in enumerate(flows.items()): X[i, :min(len(lengths), max_len)] lengths[:max_len] return X这段代码解决的是“把一组报文变成矩阵”的问题。max_len64意味着每个流最多看前64个报文超过的部分直接丢弃。defaultdict(list)用来按五元组聚合这样同一个TCP连接的所有报文才会落在同一行。补零操作对应CNN里的padding避免不同长度的流导致矩阵形状不齐。训练时你还需要给每一行打标签——比如哪条流是视频、哪条是P2P——项目里MyDataTrain1.csv的最后一列通常就是标签。2.3 特征与标签的组织方式data目录下的CSV文件是已经抽好的特征表而不是原始报文。每一行代表一个流前面若干列是流特征最后一列是类别。用pandas读进来后要做两件事数值型特征标准化标签转成one-hot编码。下面这段代码是项目里RenGongZhiNeng目录下常见的预处理段。import pandas as pd from sklearn.preprocessing import StandardScaler, LabelEncoder from tensorflow.keras.utils import to_categorical df pd.read_csv(data/MyDataTrain1.csv) X df.iloc[:, :-1].values y df.iloc[:, -1].values scaler StandardScaler() X scaler.fit_transform(X) encoder LabelEncoder() y_int encoder.fit_transform(y) y_onehot to_categorical(y_int)StandardScaler会把每个特征列的均值归零、方差归1这对CNN收敛速度影响很大尤其是报文长度这类数值跨度大的特征。LabelEncoder把字符串标签映射成整数to_categorical再转成独热矩阵这样softmax输出层的维度才能和类别数对上。注意fit_transform只用于训练集测试集必须用同一个scaler做transform否则数据分布就不一致了。3. 一维卷积识别网络流Keras模型的搭建与训练曲线解读3.1 为什么要用Conv1D而不是Conv2D图像识别里CNN处理的是H×W×C的二维矩阵但网络流特征是一维序列——比如64个报文长度依次排列。如果把一维序列强行reshape成二维图片卷积核会额外学习到不存在的空间关系反而干扰模型。所以项目里用的是Conv1D卷积核只在序列长度方向上滑动。这对应了项目文件名里的基于keras的cnn的网络流识别核心就一个把流当句子把报文长度当词向量用卷积提取局部模式。3.2 一个能跑通的CNN流识别模型下面的模型结构参考了项目里Cnn_Identify_Model的训练逻辑并做了适度的精简。输入维度设为64对应2.2节的max_len。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout def build_cnn_model(input_dim64, num_classes5): model Sequential([ Conv1D(filters64, kernel_size3, activationrelu, input_shape(input_dim, 1)), MaxPooling1D(pool_size2), Conv1D(filters128, kernel_size3, activationrelu), MaxPooling1D(pool_size2), Flatten(), Dense(128, activationrelu), Dropout(0.5), Dense(num_classes, activationsoftmax) ]) model.compile(optimizeradam, losscategorical_crossentropy, metrics[accuracy]) return modelinput_shape(64, 1)里的1表示每个时间步只输入一个特征。如果你想加入更多特征比如平均包长、TTL、标志位组合把最后一个维度改成特征数即可。两个卷积层分别用64和128个卷积核kernel_size3意味着每次看3个连续报文长度的模式。Dropout(0.5)是关键——网络流识别任务容易过拟合因为不同数据集的流特征分布差异很大丢掉一半神经元能明显提升泛化能力。3.3 训练时的验证策略和损失曲线训练时不能只有一个训练集项目里把MyDataTrain1.csv按7:3拆成训练和验证MyDataTest1.csv留到最后做最终评测。训练过程的回调里建议加EarlyStopping因为这类小数据集上跑50轮很容易过拟合验证集loss在第10轮附近就开始回升了。model.fit(X_train, y_train, validation_split0.2, epochs50, batch_size32, callbacks[tf.keras.callbacks.EarlyStopping(patience5)])validation_split0.2会在训练集末尾切出20%做验证所以数据要先随机打乱再传进去否则按时间顺序采集的流量会产生数据泄漏。patience5表示验证loss连续5轮不下降就提前停训实际跑下来一般会在15~25轮停下准确率能到85%~95%之间具体取决于你的CSV里类别是否均衡。如果训练完发现验证准确率远低于训练准确率说明过拟合优先调大Dropout其次是砍卷积核数量。4. 把模型塞进SDN控制器Ryu集成与蚁群算法求最短路径4.1 SDN控制器如何调用CNN的预测结果项目里的SDN部分是基于Ryu控制器实现的。Ryu用Python编写这正好和模型推理的生态打通。常见做法是写一个Ryu应用在_packet_in_handler里对每一条新流提取特征调用训练好的模型做预测然后根据预测类别下发不同的流表规则。from ryu.base import app_manager from ryu.controller.handler import set_ev_cls from ryu.controller import ofp_event from ryu.ofproto import ofproto_v1_3 import numpy as np class CnnController(app_manager.RyuApp): OFP_VERSIONS [ofproto_v1_3.OFP_VERSION] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.model load_cnn_model() # 加载训练好的h5文件 set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def packet_in_handler(self, ev): msg ev.msg features extract_features_from_packet(msg) # 实时提取特征 pred self.model.predict(np.array([features]))[0] label np.argmax(pred) # 根据label决定转发策略比如视频流走低延迟队列 self._install_flow(msg, prioritylabel 1)这里有一个工程上的坑self.model.predict()是同步阻塞调用而Ryu的packet_in事件频率很高模型推理的几十毫秒会直接阻塞事件循环。常见的缓解方案是给模型预测加缓存同一个五元组的预测结果在10秒内直接复用或者把预测逻辑丢进线程池只把结果异步写回流表。项目源码里没有明确做异步但你自己跑的时候必须考虑这个问题。4.2 蚁群算法在拓扑最短路径上的落地SDN控制器拿到全局拓扑后需要为数据流计算转发路径。项目里用蚁群算法替代了传统的Dijkstra——虽然在小拓扑上蚁群算法没有性能优势但它能输出多条次优路径这对负载均衡有实际意义。代码集中在蚁群算法实现网络拓扑最短路径目录里核心逻辑是迭代更新信息素矩阵。import numpy as np def ant_colony_shortest_path(graph, start, end, n_ants20, n_iter50, alpha1.0, beta2.0, rho0.5): n_nodes len(graph) pheromone np.ones((n_nodes, n_nodes)) # 信息素初始化为1 best_path, best_len None, float(inf) for it in range(n_iter): for _ in range(n_ants): path [start] visited set(path) while path[-1] ! end: cur path[-1] candidates [n for n in range(n_nodes) if graph[cur][n] 0 and n not in visited] if not candidates: break # 按信息素和启发信息(1/距离)的概率选择下一跳 probs [] for nxt in candidates: tau pheromone[cur][nxt] ** alpha eta (1.0 / graph[cur][nxt]) ** beta probs.append(tau * eta) probs np.array(probs) / sum(probs) nxt np.random.choice(candidates, pprobs) path.append(nxt) visited.add(nxt) if path[-1] end: path_len sum(graph[path[i]][path[i1]] for i in range(len(path)-1)) if path_len best_len: best_len, best_path path_len, path # 信息素蒸发 最优路径增强 pheromone * (1 - rho) if best_path is not None: for i in range(len(best_path)-1): pheromone[best_path[i]][best_path[i1]] 1.0 / best_len return best_path, best_lenalpha和beta分别控制信息素和距离的权重beta2.0意味着蚂蚁更倾向于走短边alpha1.0则保留一定的探索性。rho0.5是信息素蒸发率取值太大会导致算法过早收敛到局部最优太小则收敛慢。你可以在有环的拓扑上把n_iter调大到200验证路径是否收敛到与Dijkstra一致的结果。实际部署时Ryu的topology模块会维护交换机之间的链路信息你只需要把链路的port速率或时延作为graph的权重矩阵传进来即可。4.3 流量监控与异常报警的联动SDN部分还承担了流量监控职责。Ryu可以通过OFPPortStatsRequest周期性读取交换机每个端口的统计计数这个计数器会告诉你有多少个字节、多少个报文通过了该端口。把这个数值喂给CNN模型的实时评分就能做到粗粒度的异常流量报警。需要说明的是项目里这套联动是“半自动”的——模型预测出异常类别后Ryu只是打印日志并更新流表优先级并没有做封禁动作。如果你想做到自动封禁需要在_install_flow里把匹配到异常类别流量的instructions改成drop而不是output到某个物理端口。5. 从毕设到可用样本不均衡处理和实时推理的几个落地技巧5.1 数据集的类别分布先看一眼再训打开MyDataTrain1.csv统计一下每类的样本数这一步能省下你大量调参时间。网络识别数据集的标签天然不均衡——比如正常流量可能占了80%而某个攻击类型只有2%。如果直接训练模型会倾向于把所有样本都预测成多数类整体准确率虚高但少数类的召回率惨不忍睹。类权重是一种粗暴但有效的做法class_weight会在计算损失时按比例放大少数类的梯度让模型“被迫”重视它们。from sklearn.utils.class_weight import compute_class_weight weights compute_class_weight(balanced, classesnp.unique(y_int), yy_int) class_weight {i: w for i, w in enumerate(weights)} model.fit(X_train, y_train, class_weightclass_weight, epochs30)compute_class_weight会自动计算每个类的权重多数类权重小于1少数类权重大于1。这个技巧在项目场景下几乎必用因为网络流数据里背景流量永远是占大头的。如果你发现加权重之后整体准确率掉了几个点这不一定是坏事——F1分数更有参考价值把metrics改成[accuracy, tf.keras.metrics.Precision(), tf.keras.metrics.Recall()]再重新训练观察。5.2 把h5模型转成只走前向推理的形式训练好的模型默认保存为.h5里面包含了优化器状态、损失函数等训练专用信息。部署到SDN控制器时这些都用不上反而会让模型文件变大、加载变慢。更合适的做法是把权重固化成只推理的模式这样加载速度能提升30%左右。项目里如果直接复用训练脚本加载模型等价于每次都带上了训练状态属于典型的“能跑但不够利落”。model.load_weights(Cnn_Identify_Model.h5) # 只加载权重不加载优化器 # 在SDN控制器里用model.predict即可更进一步的优化是转成TensorFlow Lite或ONNX但那需要引入额外编译链对于毕设场景来说收益不高。只需记住两点部署时不需要compile也不需要保存整个模型对象只保留网络结构和权重文件即可。另外predict在批量小的时候有固定开销如果你连续预测多个流把特征堆叠成一个(N, 64, 1)的数组一次性传入比循环调用predict快5到10倍。5.3 复现时最容易踩的三个坑第一个坑是Scapy版本差异导致rdpcap读pcap失败。新版Scapy需要pip install scapy2.4.5才能兼容旧版pcap格式如果你的环境是Scapy 2.5以上可能需要用PcapReader流式读取而不是一次性rdpcap。第二个坑是CSV里包含非数值列预处理时用df.info()检查每个字段的类型把object类型的列做标签编码或直接丢弃。第三个坑是Ryu控制器的set_ev_cls装饰器里忘了加MAIN_DISPATCHER状态导致packet_in事件永远进不了处理函数——这个问题最常见的表现是控制器正常运行但任何流量都不触发打印日志。建议在packet_in_handler第一行加一条self.logger.info(got packet)先确认事件链路通畅再去做模型推理的联调。本文还有配套的精品资源点击获取