标题：winsock kernel
WSK架构
WSK架构的核心是WSK子系统.WSK子系统是一个网络模块，它实现了WSK网络编程接口（NPI）的提供端（provider side）。WSK子系统接口在它的下层有传输接口，它支持多种类型的传输协议。
绑定在WSK子系统上的是WSK程序。WSK程序是内核模式的软件模块，为了展示了网络IO操作，它们实现了WSK NPI的客户端。（在此处，“客户”不应和客户-服务器系统中的“客户”相混淆）WSK子系统可以调用WSK客户NPI函数来通知WSK程序异步事件的到来。  [1]　　WSK程序通过一组WSK注册函数找到（discover）并绑定到WSK子系统。WSK程序可以通过使用这些函数在WSK子系统可用时动态检测到它，并交换提供者和WSK NPI客户端实现的派遣表。and to exchange dispatch tables that constitute the provider and client side implementations of the WSK NPI.
WSK程序可以使用网络模块注册（Network Module Registrar (NMR)）绑定到WSK子系统。更多信息，参考“使用NMR实现WSK的注册和注销”Using NMR for WSK Registration and Unregistration.
WSK对象
Winsock 内核NPI围绕两个主要的对象类型来设计：client 和 socket。客户对象（Client Object）一个client对象代表了一个WSK程序和WSK子系统间的依附或者绑定。一个Client对象 由一个WSK_CLIENT结构代表。一个指向客户对象的指针在WSK程序绑定到WSK子系统时获得。WSK程序传递这个指针给在这个客户对象层面上操作的所有WSK函数。套接字对象（Socket Object）一个socket对象代表了一个可以用于网络I/O的网络套接字。一个Socket对象由一个WSK_SOCKET结构代表。一个指向套接字对象的指针在WSK程序接收一个进来的（incoming）连接时获得。WSK程序传递这个指针给仅限于某个特定套接字的所有WSK函数。
WSK套接字类别
Winsock内核NPI定义了四种不同的套接字类别：基本套接字，监听套接字，数据包套接字，基于连接的套接字。每个WSK套接字类别有特有的功能并支持不同的套接字函数集合。WSK程序必须在它创建一个新套接字对象时指定类别。每个WSK套接字类别的用途如下：基本套接字（Basic Sockets）基本套接字只用于获取和设置传输堆栈的套接字选项，或者实现套接字IO控制操作。基本套接字不能绑定到本地的传输地址而且不能支持收发网络数据。监听套接字（Listening Sockets）监听套接字用于监听远端（remote）传输地址的进入（incoming）连接。监听套接字的功能包含所有基本套接字的功能。数据包套接字（Datagram Sockets）数据包套接字用于收发数据包（datagram）。数据包套接字的功能包含所有基本套接字的功能。基于连接的套接字（Connection-Oriented Sockets）　　基于连接的套接字用于在已建立的连接上收发网路数据。基于连接的套接字的功能包含所有基本套接字的功能。
使用WSK内核函数
WSK程序驱动套接字操作，也就是说当套接字造作发生时WSK程序开始控制。这会简化WSK程序所请求的的同步。WSK程序给套接字函数提供IRP。这些IRP被WSK子系统排队，直到套接字操作完成。更多关于WSK函数中使用IRP的信息，参考“WSK函数中使用IRP”（Using IRPs with Winsock Kernel Functions）。WSK程序可以实现阻塞套接字操作，通过等待IRP的每个操作都被WSK子系统完成。WSK程序可能需要进行多个在某些情况下排队的套接字操作，为了在基于连接的套接字中确保有好的数据传输表现，为了在数据包套接字中防止数据包被丢弃，或者为了在监听套接字中防止进入的连接被丢弃。WSK程序为数据传输操作提供数据缓冲区。这减少了数据被拷贝的次数。然而，如果一个WSK程序进行多个排队的数据传输操作，它必须为每个排队的数据传输操作提供数据缓冲区给WSK子系统。
使用事件回调函数
WSK子系统驱动套接字操作，意思是WSK子系统通过调用套接字的事件回调函数通知WSK程序套接字事件。WSK程序可能需要更复杂的同步来处理事件回调函数的异步特征。WSK程序在套接字操作中不适用IRPs。WSK程序不需要排队套接字操作。WSK子系统在套接字事件发生时，立刻调用WSK程序的事件回调函数。如果WSK程序可以与套接字事件回调函数被调用的进度持平，使用事件回调函数何以提供最好的表现和最小丢失数据包和进入的连接的机会。WSK子系统为数据传输操作提供数据缓冲区。WSK程序必须立刻或者在一个合理的时间内释放这些数据缓冲区给WSK子系统，这可以避免WSK子系统用完存储资源。因此，WSK程序可能需要把数据从WSK子系统拥有的数据缓冲区拷贝到它自己的数据缓冲区。
WSK“扩展接口”
WSK NPI支持扩展接口。WSK子系统可以通过使用“扩展接口”扩展WSK套接字的功能，是它不仅限于WSK NPI当前定义的套接字功能集和事件回调函数。每个NPI定义的扩展接口与WSK NPI无关。当前还没有没有定义扩展接口。
WSK程序可以通过使用SIO_WSK_REGISTER_EXTENSION套接字IOCTL操作注册一个被WSK子系统支持的设备扩展。
更多关于注册扩展接口的信息，参考“注册一个设备扩展” （Registering an Extension Interface）。
在Winsock内核函数中使用IRP
WSK NPI使用IRP实现网络I/O操作的异步完成。IRP的指针作为每个WSK函数的一个参数。WSK子系统在WSK函数完成操作后完成此IRP。
WSK程序用来传递到一个WSK函数中的IRP可以源于以下方法。
WSK程序调用IoAllocateIrp分配这个IRP。这种情况下，WSK程序必须给这个IRP分配至少一个I/O堆栈单元。WSK程序重新使用一个先前分配但已完成的IRP。WSK必须调用IoReuseIrp来重新初始化这个IRP。WSK程序使用从上层或I/O管理器传到它的IRP。这种情况，此IRP必须有至少一个IO堆栈单元供WSK子系统使用。
在WSK程序拥有一个IRP来调用WSK函数后，它可以为此IRP设置一个完成例程，当WSK子系统完成IRP时此例程被调用。WSK程序通过调用IoSetCompletionRoutine来为IRP设置完成例程。IoCompletion routine需要或可选，取决于这个IRP是如何产生的。
如果WSK程序分配此IRP，或者重用它先前分配的IRP，那么它必须在调用WSK函数前设置一个完成例程。这种情况下，必须把传递给IoCompletion routine的参数InvokeOnSuccess, InvokeOnError, and InvokeOnCancel都设为TRUE，以确保IoCompletion routine总是被调用。而且，此IRP的IoCompletion routine必须总是返回STATUS_MORE_PROCESSING_REQUIRED来结束此IRP的完成处理。If the WSK application is done using the IRP after the IoCompletion routine has been called, then it should call the IoFreeIrp function to free the IRP before returning from the IoCompletion routine.如果WSK程序。如果WSK程序没有释放这个IRP，那么它可以在调用其它的WSK函数时重用这个IRP。如果WSK程序使用一个从高层驱动或IO管理器传递下来的IRP，仅当它在WSK函数完成操作后必须被通知时，它需要在调用WSK函数前设置完成例程。如果WSK程序没有为此IRP设置完成例程，当此IRP被完成时，它会像每个正常IRP的完成处理那样被传递至上层驱动或IO管理器。如果WSK程序设置了一个完成例程，这个完成例程可以返回STATUS_SUCCESS或STATUS_MORE_PROCESSING_REQUIRED。如果完成例程返回了STATUS_SUCCESS，这个IRP的完成处理会正常地继续下去。如果完成例程返回了STATUS_MORE_PROCESSING_REQUIRED，WSK程序必须在它完成WSK函数处理的操作完成之后调用函数IoCompleteRequest来完成此IRP。WSK程序不能释放从高层驱动或IO管理器传给它的IRP。
注意：如果WSK程序为由上层驱动或IO管理器传递过来的且传至下层的IRP设置了一个完成例程，那么完成例程必须检查IRP的PendingReturned位，如果PendingReturned是TRUE的话，它必须调用IoMarkIrpPending。更多信息，参考“实现一个完成例程” Implementing an IoCompletion Routine。
WSK程序不初始化它传递至WSK函数的IRP，除了设置一个完成例程。当WSK程序传递一个IRP到WSK函数时，WSK子系统代表WSA程序设置下一堆栈单元。
下面的代码示例显示了WSK程序在一个套接字上进行接收操作时如何分配和使用IRP。
// 接受完成例程的原型
NTSTATUS
ReceiveComplete(
PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context
);
// 接收数据的函数
NTSTATUS
ReceiveData(
PWSK_SOCKET Socket,
PWSK_BUF DataBuffer
)
{
PWSK_PROVIDER_CONNECTION_DISPATCH Dispatch;
PIRP Irp;
NTSTATUS Status;
// 获取“供应者派遣结构指针”
Dispatch =
(PWSK_PROVIDER_CONNECTION_DISPATCH)(Socket->Dispatch);
// 分配一个IRP
Irp =
IoAllocateIrp(
1,
FALSE
);
// Check result
if (!Irp)
{
// 返回错误
return STATUS_INSUFFICIENT_RESOURCES;
}
// 设置此IRP的完成例程
IoSetCompletionRoutine(
Irp,
ReceiveComplete,
DataBuffer,  // 数据缓冲区作为上下文
TRUE,
TRUE,
TRUE
);
// 初始化这个套接字的接收操作
Status =
Dispatch->WskReceive(
Socket,
DataBuffer,
0,  // 没有指定标志
Irp
);
// 返回WskReceive的状态
return Status;
}
// 接收完成例程
NTSTATUS
ReceiveComplete(
PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context
)
{
UNREFERENCED_PARAMETER(DeviceObject);
PWSK_BUF DataBuffer;
ULONG ByteCount;
// 检查接收操作的结果
if (Irp->IoStatus.Status == STATUS_SUCCESS)
{
// 获取数据缓冲区的指针
DataBuffer = (PWSK_BUF)Context;
// 获取接收的字节数
ByteCount = (ULONG)(Irp->IoStatus.Information);
// 处理接收的数据
...
}
// 错误状态
else
{
// 处理错误
...
}
// 释放IRP
IoFreeIrp(Irp);
// 一直返回STATUS_MORE_PROCESSING_REQUIRED 来
// 结束IRP的完成处理
return STATUS_MORE_PROCESSING_REQUIRED;
}
在上面例子显示的模型中，WSK程序分配IRP并在完成例程里释放它。在余下的WSK文档中的例子使用了这个模型。
下面的代码示例显示了，在套接字上实现接受操作时，WSK程序可以使用上层驱动或IO管理器传递给它的IRP，当
// 接收完成例程的原型
NTSTATUS
ReceiveComplete(
PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context
);
//接收数据的函数
NTSTATUS
ReceiveData(
PWSK_SOCKET Socket,
PWSK_BUF DataBuffer,
PIRP Irp;  //从上层驱动或IO管理器传递下来的Irp
)
{
PWSK_PROVIDER_CONNECTION_DISPATCH Dispatch;
NTSTATUS Status;
// 获取“供应者派遣表”的指针
Dispatch =
(PWSK_PROVIDER_CONNECTION_DISPATCH)(Socket->Dispatch);
// 设置IRP的完成例程，仅当接收操作成功后被调用
IoSetCompletionRoutine(
Irp,
ReceiveComplete,
DataBuffer,  // 数据缓冲区作为上下文
TRUE,
FALSE,
FALSE
);
// 在套接字上初始化接收操作
Status =
Dispatch->WskReceive(
Socket,
DataBuffer,
0,  // 未指定标志
Irp
);
// 返回 WskReceive()的状态码
return Status;
}
// 接收完成例程
NTSTATUS
ReceiveComplete(
PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context
)
{
UNREFERENCED_PARAMETER(DeviceObject);
PWSK_BUF DataBuffer;
ULONG ByteCount;
// 因为完成例程只在操作完成时调用，所以这个永远是TRUE
ASSERT(Irp->IoStatus.Status == STATUS_SUCCESS);
// 检查这个IRP的pending状态
if (Irp->PendingReturned == TRUE)
{
// 标记这个IRP为pending
IoMarkIrpPending(Irp);
}
// 获取数据缓冲区的指针
DataBuffer = (PWSK_BUF)Context;
// 获取接收的字节数
ByteCount = (ULONG)(Irp->IoStatus.Information);
// 处理接收的数据
...
// 返回STATUS_SUCCESS继续此IRP的完成处理
return STATUS_SUCCESS;
}
更多关于使用IRP的信息，参考“处理IRP” Handling IRPs。