拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Windows 内核网络编程实战:基于 Winsock Kernel(WSK)的 TCP Echo Server 驱动示例深入解析

示例工程【免费下载链接】Windows-driver-samplesThis repo contains driver samples prepared for use with Microsoft Visual Studio and the Windows Driver Kit (WDK). It contains both Universal Windows Driver and desktop-only driver samples.项目地址https://gitcode.com/gh_mirrors/wi/Windows-driver-samples点击查看免费下载导读本文以 network/wsk/echosrv 目录下的 WSK TCP Echo Server 示例为核心系统讲解如何使用 Windows 内核态套接字接口 Winsock KernelWSK实现一个同时监听 IPv4/IPv6、端口 40007、接收并原样回显数据的 TCP 回显服务器驱动。你将掌握 WSK 的注册流程、监听套接字建立、异步收发回显状态机、单工作线程同步模型以及基于 WPP 软件跟踪的日志采集与sc命令的驱动部署运行全流程。示例定位一个最小的内核态 TCP 回显服务该示例驱动是 Windows Driver KitWDK示例仓库中演示 WSK 编程接口的极简实现。它使用 WSK 接口实现了一个完整的内核态 TCP 服务器应用在端口 40007 上同时接受 IPv4 与 IPv6 的入站连接请求对每一条连接将收到的所有数据原样回显给对端直到对端关闭连接为止。从设计上该示例刻意保持最小化单一工作线程全部处理逻辑由一条工作线程完成。示例代码中明确说明在多处理器MP机器上可以通过增加工作线程数量来提升性能wsksmple.c 模块头注释 L15-L25。连接级线程亲和设计上保证同一条连接上的操作总是由同一条工作线程处理从而以最简单的形式实现同步——当一条连接上存在多个尚未完成、且以异步方式完成的操作时这种设计能确保套接字被正确、安全地关闭。不做资源限制示例不限制可接受的连接数量仅受系统可用内存的自然上限约束也不限制连接的存活时长。原文档特别提醒一个完整的生产服务器应用必须从安全角度考虑这些限制。原文档明确声明该示例不适用于生产环境其价值在于作为 WSK 编程接口的教学与验证样例。仓库文件构成与构建环境示例位于仓库的network/wsk/echosrv目录全部文件如下文件作用wsksmple.c唯一的核心实现源文件约 1363 行包含全部 WSK 逻辑wsksmple.rc资源文件声明驱动版本信息内部名wsksample.sysechosrv.ctlWPP 跟踪控制 GUID 文件内容为998bdf51-0349-4fbc-870c-d6130a955a5f WskSampleCtlGuidechosrv.vcxprojMSBuild 项目文件echosrv.vcxproj.Filters解决方案资源管理器文件分组echosrv.sln解决方案文件README.md本示例的官方说明文档从 echosrv.vcxproj 可以确认构建配置的关键事实驱动类型为WDMDriverTypeWDM平台工具集为WindowsKernelModeDriver10.0面向 Windows 10支持Debug / Release×x64 / ARM64四套配置最终输出统一命名为Echosrv.sysTargetNameechosrv链接netio.lib与uuid.lib前者提供 WSK 相关的内核网络接口后者提供 GUID 相关符号对wsksmple.c与wsksmple.rc均启用了WPP 预处理WppEnabledtrue、WppKernelModetrue这是 WPP 软件跟踪的编译期基础编译选项包含TreatWarningAsErrortrue与WarningLevelLevel4要求代码在最高警告级别下无告警编译通过。构建方式与仓库其他 WDK 示例一致在安装了 Windows 10 SDK 与 WDK 的 Visual Studio 环境中打开 echosrv.sln 直接生成或参考仓库根目录的 Building-Locally.md 使用脚本方式构建。构建产物Echosrv.sysDebug 配置下同时生成Echosrv.pdb供 WPP 跟踪格式化使用即部署所需的驱动文件。内核实现深度解析WSK 注册与客户端分发表WSK 编程的第一步是向内核 WSK 子系统注册客户端。示例在DriverEntry中完成wsksmple.c调用ExInitializeDriverRuntime(DrvRtPoolNxOptIn)启用非分页池的内存分配语义预分配监听套接字的上下文WskSampleListeningSocketContext失败则返回STATUS_INSUFFICIENT_RESOURCES构造WSK_CLIENT_NPI并调用WskRegister完成注册wsksmple.c启动全局工作队列与工作线程将首个操作WskSampleOpStartListen入队交给工作线程去建立监听套接字初始化 WPP 跟踪并输出TRCINFO级别的 LOADED 消息。客户端分发表WskSampleClientDispatchwsksmple.c通过MAKE_WSK_VERSION(1, 0)声明使用WSK 1.0 版本并将WskClientEvent回调置为NULL——1.0 版本不要求该回调。监听套接字的分发表WskSampleClientListenDispatchwsksmple.c仅注册了WskSampleAcceptEvent接受事件回调WskInspectEvent与WskAbortEvent均为NULL——注释明确说明这两个回调只在启用条件接受conditional-accept时才需要。工作队列与单线程模型示例的核心同步机制是全局工作队列WskSampleWorkQueuewsksmple.c其结构WSKSAMPLE_WORK_QUEUEwsksmple.c包含SLIST_HEADER Head无锁单项链表头用于存放待处理操作KEVENT Event唤醒事件队列为空时工作线程在此等待BOOLEAN Stop停止标志仅当队列为空且Stop为TRUE时工作线程才安全退出PETHREAD Thread工作线程对象。工作线程由WskSampleStartWorkQueue通过PsCreateSystemThread创建wsksmple.c随后循环执行WskSampleWorkerThread用InterlockedFlushSList一次性取走全部排队操作若队列为空检查Stop标志决定退出或KeWaitForSingleObject等待事件若取到操作反转链表以恢复 FIFO 顺序因为InterlockedPushEntrySList是头插法再逐个调用操作处理器OpHandler。操作的入队由WskSampleEnqueueOp完成wsksmple.c设置操作处理器后InterlockedPushEntrySList入队如果入队前队列为空则KeSetEvent唤醒工作线程。值得强调的是示例虽然只使用一个全局工作队列但结构设计上已为多队列扩展留好接口WSKSAMPLE_SOCKET_CONTEXT中保存了所属工作队列指针代码注释明确说明若改为多工作队列如每处理器一个队列同一条连接必须始终使用同一个工作队列wsksmple.c——这正是连接级线程亲和原则的实现基础。监听套接字建立双栈 IPv4/IPv6首个入队操作WskSampleOpStartListenwsksmple.c在工作线程内同步完成整条监听套接字的建立链期间不会有其他操作被处理。其核心流程在WskSampleSetupListeningSocketwsksmple.c中捕获 WSK Provider NPIWskCaptureProviderNPI以WSK_INFINITE_WAIT无限等待wsksmple.c。若驱动卸载发生在 WSK 子系统就绪之前此调用会失败示例会打印跟踪消息注册静态事件回调通过WskControlClient发送WSK_SET_STATIC_EVENT_CALLBACKSEventMask设为WSK_EVENT_ACCEPTwsksmple.c保证监听套接字创建后WskAcceptEvent自动生效创建监听套接字调用WskSocket参数为AF_INET6IPv6 地址族、SOCK_STREAM流式套接字、IPPROTO_TCP、WSK_FLAG_LISTEN_SOCKET监听标志并传入套接字上下文与监听分发表wsksmple.c关闭 V6ONLY 实现双栈通过WskControlSocket设置IPV6_V6ONLY0wsksmple.c使该 AF_INET6 套接字同时处理 AF_INET 流量——这正是 README 所述IPv4 和 IPv6 双协议监听的实现根源绑定通配地址WskBind绑定到全局IPv6ListeningAddresswsksmple.c该结构为SOCKADDR_IN6地址族AF_INET6端口字段0x479c——即十进制40007的网络字节序表示地址为IN6ADDR_ANY_INIT通配地址。绑定完成后 WSK 提供程序开始投递接受事件。所有 WSK 调用均通过 IRP 异步发起示例为每个同步等待都设置了WskSampleSyncIrpCompletionRoutinewsksmple.c完成例程中KeSetEvent置位事件并返回STATUS_MORE_PROCESSING_REQUIRED主线程随后KeWaitForSingleObject等待实现异步调用、同步等待的统一模式。任何一步失败时failexit路径会关闭已创建的监听套接字并释放资源确保错误路径不泄漏句柄。接受连接WskAcceptEvent 回调当有入站连接到达时WSK 提供程序调用WskSampleAcceptEventwsksmple.c回调包含两条路径AcceptSocket 为 NULL表示监听套接字已失效WSK 子系统保证该回调只会携带 NULL 触发一次。示例的处理是重新入队WskSampleOpStartListen——该操作会先关闭失效的旧监听套接字再重建新套接字并返回STATUS_REQUEST_NOT_ACCEPTED正常接受为被接受的套接字分配新的WSKSAMPLE_SOCKET_CONTEXT同时预分配WSKSAMPLE_DATA_BUFFER_LENGTH即 2048 字节的数据缓冲与 MDL把AcceptSocket存入上下文然后将WSKSAMPLE_OP_COUNT值为 2见 wsksmple.c个WskSampleOpReceive操作全部入队——两条并发的接收请求即构成了该连接上的最大未完成操作数。由于接受套接字不使用回调示例将AcceptSocketContext与AcceptSocketDispatch均置为 NULLwsksmple.c。回显状态机Receive → Send → Receive回显功能的本质是一条由 IRP 完成例程驱动的操作链。每个WSKSAMPLE_SOCKET_OP_CONTEXTwsksmple.c内嵌了操作处理器指针、套接字上下文、专用 IRP、数据缓冲与 MDL以及缓冲总长BufferLength和实际数据长DataLength。套接字上下文WSKSAMPLE_SOCKET_CONTEXTwsksmple.c则通过Closing、Disconnecting、StopListening三个布尔标志维护套接字生命周期状态。接收WskSampleOpReceivewsksmple.c构造WSK_BUF偏移 0、长度取BufferLength、MDL 指向预分配缓冲调用WskReceive异步发起接收。其完成例程WskSampleReceiveIrpCompletionRoutinewsksmple.c按完成状态分派失败 → 入队WskSampleOpClose关闭套接字成功且接收字节数为 0 → 说明对端已优雅关闭其半边连接入队WskSampleOpDisconnect关闭己方半边成功且有数据 → 将Irp-IoStatus.Information记录为DataLength入队WskSampleOpSend回显。发送WskSampleOpSendwsksmple.c以DataLength为长度调用WskSend。完成例程WskSampleSendIrpCompletionRoutinewsksmple.c中失败则关闭套接字成功则重新入队WskSampleOpReceive继续接收——收到什么就发回什么发完再继续收如此循环直至对端断开。断开WskSampleOpDisconnectwsksmple.c先置位Disconnecting再调用WskDisconnect完成后入队WskSampleOpClose。代码注释特别指出多个未完成的接收请求可能各自以成功且 0 字节完成导致断开操作被多次入队因此Disconnecting标志用于幂等去重。关闭WskSampleOpClosewsksmple.c同样以Closing标志保证只执行一次WskCloseSocket。其完成例程WskSampleCloseIrpCompletionRoutinewsksmple.c断言WskCloseSocket必然成功然后入队WskSampleOpFree。释放WskSampleOpFreewsksmple.c断言套接字已处于关闭或停止监听状态后调用WskSampleFreeSocketContextwsksmple.c释放 IRP、MDL、数据缓冲与上下文本体。由于WskCloseSocket的完成保证该套接字上不再有未决请求或回调但队列中可能还有未执行的操作因此释放操作必须排在队列尾部——注释明确说明关闭完成后不会再为该套接字入队新操作所以OpFree之后不会再有后续操作释放是安全的wsksmple.c。驱动卸载WskDeregister 的阻塞语义卸载例程WskSampleUnloadwsksmple.c揭示了 WSK 的一个重要行为WskDeregister会阻塞直到所有套接字关闭、所有对 WSK Provider NPI 的引用被释放。由于示例只在出错或对端断开时才关闭面向连接的套接字驱动卸载会一直阻塞到最后一个连接被对端断开为止。代码注释wsksmple.c明确指出若一个完整应用需要卸载或类似事件时主动关闭可能无限期存活的套接字则必须自行维护所有套接字的登记表以便枚举并逐个关闭同时必须保证套接字关闭发生在不会再对其发起任何调用之后。WPP 软件跟踪采集与分析驱动日志示例驱动使用 WPP 软件跟踪记录其行为。WPP 的控制 GUID 在源码中通过WPP_CONTROL_GUIDS宏定义wsksmple.cGUID 为998bdf51-0349-4fbc-870c-d6130a955a5f与 echosrv.ctl 文件内容一致。示例定义了两个事件类型标志位事件宏标志值含义TRCERROR0x1错误事件TRCINFO0x2信息事件原文档给出了使用 WDK\tools\tracing目录工具采集日志的完整流程命令如下假设已在工作目录放置好相关文件步骤 1准备文件。将Echosrv.ctl与Echosrv.pdb复制到一个目录并切换到该目录。Echosrv.pdb来自驱动构建输出包含 WPP 消息字符串的符号信息。步骤 2启动跟踪tracelog -start echosrvtrace -guid echosrv.ctl -f logfile.etl -flags 0x3-flags参数决定驱动记录哪些事件由于TRCERROR为 0x1、TRCINFO为 0x2标志值 0x3二者按位或即同时记录两类事件。若只想记录错误事件可将标志设为 0x1只记录信息事件则设为 0x2。步骤 3停止跟踪tracelog -stop echosrvtrace步骤 4格式化为可读文本tracefmt -o logfile.txt -f logfile.etl -r . -i \full-path\ echosrv.sys其中\full-path\是包含Echosrv.sys文件的目录路径。-r .指定在本地查找消息格式文件-i指定包含符号信息的目标镜像。步骤 5查看结果。打开生成的Logfile.txt查看跟踪日志。需要注意对示例驱动的跟踪可以随时启动——既可以在驱动启动之前也可以在驱动运行期间。从源码可以看到跟踪点的分布DriverEntry加载时输出LOADED、卸载输出UNLOAD START/UNLOAD END各操作处理器与完成例程均输出TRCINFO级消息如EnqueueOp、WorkerThread、AcceptEvent、OpReceive等错误路径则输出TRCERROR级消息如SetupListeningSocket: WSK_SET_STATIC_EVENT_CALLBACKS FAIL、SetupListeningSocket: WskSocket FAIL等。配合跟踪信息中打印的上下文指针地址可以完整还原每条连接上的操作时序。部署与运行sc 命令安装内核驱动原文档给出了在测试机上安装运行该驱动的完整步骤步骤 1复制驱动文件。将Echosrv.sys复制到测试机上的某个目录。步骤 2创建内核服务以管理员身份打开命令提示符sc create echosrv type kernel binpath \full-path\ echosrv.sys其中\full-path\是包含Echosrv.sys文件的目录。type kernel声明这是一个内核驱动程序服务注意sc的语法要求等号与值之间有空格。步骤 3启动驱动sc start echosrv步骤 4停止驱动sc stop echosrv驱动安装并启动后即开始在40007 端口上同时监听 IPv4 与 IPv6 的入站 TCP 连接直到驱动被停止。对每条连接驱动将所有收到的数据原样回显给对端直至对端关闭连接。你可以用任意 TCP 客户端例如 Windows 自带的 telnet 客户端连接测试机的 40007 端口验证回显行为发送的每一行文本都会被原样返回。设计要点与生产化改造建议综合原文档与源码这个示例为 WSK 编程提供了几个可复用的设计范式也暴露了生产化必须补足的问题连接级线程亲和同一连接的所有操作始终由同一工作线程处理天然消除了并发关闭与异步完成之间的竞态是多操作未决 异步完成场景下保证套接字正确关闭的最简同步方案。多处理器扩展时只需为每个处理器建立独立工作队列并确保同一条连接始终绑定同一队列。操作上下文预分配每个套接字上下文内嵌固定数量WSKSAMPLE_OP_COUNT2的操作上下文连同 IRP、数据缓冲、MDL 一次性分配避免运行期反复分配2048 字节的缓冲长度WSKSAMPLE_DATA_BUFFER_LENGTHwsksmple.c可按需调整。状态标志幂等化Closing/Disconnecting/StopListening标志配合先置位再发起 WSK 调用的次序保证关闭、断开、停止监听等操作在多条完成路径下只执行一次。卸载的阻塞语义WskDeregister等待全部套接字关闭的特性意味着被动关闭式设计会让驱动卸载阻塞到最后一个连接断开。生产应用应维护套接字登记表在卸载等事件中主动枚举并关闭所有套接字。安全限制缺失示例不限制连接数量与连接存活时长原文档明确建议完整服务器应用从安全角度补充这些限制——例如设置最大并发连接数、连接空闲超时等。小结WSK TCP Echo Server 以约 1300 行代码完整演示了 Winsock Kernel 的核心用法客户端注册WskRegister、Provider NPI 捕获WskCaptureProviderNPI、监听套接字创建与双栈绑定WskSocketIPV6_V6ONLY0WskBind、事件回调WskAcceptEvent、异步收发与断开关闭WskReceive/WskSend/WskDisconnect/WskCloseSocket以及基于 IRP 完成例程的状态机驱动方式。配合 README.md 中完整可复制的 WPP 跟踪与sc部署命令它既是学习 WSK 的入门范例也是理解内核态网络编程中异步操作 单线程串行化这一经典模式的参考实现。仓库中 network 目录下的其他网络驱动示例如 NDIS 系列可在此基础上进一步扩展学习。赞分享示例工程【免费下载链接】Windows-driver-samplesThis repo contains driver samples prepared for use with Microsoft Visual Studio and the Windows Driver Kit (WDK). It contains both Universal Windows Driver and desktop-only driver samples.项目地址https://gitcode.com/gh_mirrors/wi/Windows-driver-samples点击查看免费下载相关推荐Istio 服务网格中的 TCP Echo Server 示例从部署验证到 TCP 流量路由实战samples/tcp-echoIstio 服务网格中的 TCP Echo Server 示例从部署验证到 TCP 流量路由实战samples/tcp echo 在 Istio 中验证服务网格云原生微服务网络负载均衡可观测性Windows 驱动示例 Kcs基于内核模式性能库PCW实现内核计数器集的全流程解析Windows 驱动示例 Kcs基于内核模式性能库PCW实现内核计数器集的全流程解析 导读 KcsKernel Counter Sample是 Win示例工程Chrome Apps UDP 网络访问实战基于 chrome-extensions-samples 的 UDP Echo 客户端示例解析Chrome Apps UDP 网络访问实战基于 chrome extensions samples 的 UDP Echo 客户端示例解析 本指南以 chro示例工程上一篇在 Laravel 9 中集成 Shoelace Web Components基于 Vite 的完整实操指南下一篇告别版本混乱Python Launcher for Unix 让多环境管理像呼吸一样自然创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门