现在还真是有点犹豫,有太多的牵绊,毕竟现在这个年纪,不能就这样随随便便,不是说,我不够自信,只是,目前的条件是这样,这里真的是没有太多的机会。创业没有足够的人脉,和行业,客户,目前,天时,地利,人和,都没有,暂时也就不考虑了。就算是有企业,真的就放弃这些去吗?那不是又回到老路上去了。所以,目前,应该积极的扩展自己的人脉。还有,就是最好有一些现实的东西,自己可以做出来。用之前的经验。

Registering a Winsock Kernel Application

WSK Client Object Registration

WSK应用必须通过调用WskRegister函数注册作为一个WSK客户端。WskRegister需要WSK应用初始化并传递一个指向WSK客户的NPI(WSK_CLIENT_NPI结构)一个WSK注册对象(WSK_REGISTRATION结构)被WskRegister初始化并返回。

下面的代码演示了WSK应用镇压个去注册一个WSK客户端。
 
 
  // Include the WSK header file
  #include "wsk.h"

// WSK Client Dispatch table that denotes the WSK version
  // that the WSK application wants to use and optionally a pointer
  // to the WskClientEvent callback function
  const WSK_CLIENT_DISPATCH WskAppDispatch = {
   MAKE_WSK_VERSION(1,0), // Use WSK version 1.0
   0,    // Reserved
   NULL  // WskClientEvent callback not required for WSK version 1.0
  };

// WSK Registration object
  WSK_REGISTRATION WskRegistration;

// DriverEntry function
  NTSTATUS
  DriverEntry(
   PDRIVER_OBJECT DriverObject,
   PUNICODE_STRING RegistryPath
  )
  {
   NTSTATUS Status;
   WSK_CLIENT_NPI wskClientNpi;

.
   .
   .

// Register the WSK application
   wskClientNpi.ClientContext = NULL;
   wskClientNpi.Dispatch = &WskAppDispatch;
   Status = WskRegister(&wskClientNpi, &WskRegistration);

if(!NT_SUCCESS(Status)) {
   .
   .
   .
    return Status;
   }

.
   .
   .
  }
 
              WSK应用不需要在其DriverEntry函数中,调用WskRegister。举例来说,如果WSK应用只是一个复杂驱动的一个子部件,注册应用的操作可能发生在WSK应用子部件激活的时候。

WSK应用必须保证传递给WskRegister的参数WSK_CLIENT_DISPATCH结构体的有效和一直驻留在内存中,直到WskDeregister被调用,注册不再有效。WSK_REGISTRATION结构体也必须保持其有效和一直驻留在内存中直到WSK应用停止调用其他的WSK注册函数。预前的代码将这两个结构体放在驱动的全局变量中,从而保证结构体一直驻留在内存中直到驱动被卸载。

WSK Provider NPI Capture

在WSK应用使用WskRegister已经注册为WSK客户端后,它为了使用WSK接口必须调WskCaptureProvideNPI函数去捕获WSK从WSK子系统提供的NPI.
 
             因为WSK子系统在WSK应用试图去捕获WSK提供的NPI的时候可能还没有准备好。WskCaptureProvideNPI函数允许WSK应用投票或等待WSK子系统准备好。
 
            如果WaitTimeout参数是WSK_NO_WAIT,这个函数不等待总是直接返回。
  
            如果WaitTimeout是WSK_INFINITE_WAIT,函数将等待直到WSK子系统准备好。
   
            如果WaitTimeout是其他值,这个函数将在WSK变成准备好以后返回,或在等待了时间到了之后返回。
   
            为了避免影响到其他的驱动的服务的启动,WSK 应用在DriverEntry函数中调用WskCaptureProviderNPI的时候,不应该设置WaitTimeout参数为WSK_INFINITE,或一个有限的等待时间。如果,WSK应用在系统的启动很早的阶段开启,它应该在不同于DriverEntry的工作线程中等待WSK子系统准备好。

如果调用WskCaptureProviderNPI 失败返回STATUS_NOINTERFACE,WSK应用应该使用WskQueryProviderCharacteristics函数去发现WSK子系统支持的WSK NPI的范围。WSK应用可以调用WskDeregister去卸载当前注册过的实例,然后使用不同的WSK NPI版本支持的WSK_CLIENT_DISPATCH实例重新注册。

当WskCaptureProviderNPI返回成功,它的WskProviderNpi参数指向WSK提供的NPI(WSK_PROVIDER_NPI)准备好给WSK应用使用的接口。WSK_RPOVIDER_NPI结构包含一个指向WSK客户对象的指针(WSK_CLIENT)和WSK_PROVIDER_DISPATCH派遣函数指针列表的指针,可以在WSK客户对象上使用这些函数创建WSK的套接字并执行一些操作。WSK应用在结束使用WSK_PROVIDER_DISPATCH函数以后,它必须调用WskReleaseProviderNPI去释放WSK提供的NPI.

如下的例子展示了WSK应用怎样去捕获WSK提供的NPI,使用它去创建套接字,然后将其释放。
 
  // WSK application routine that waits for WSK subsystem
  // to become ready and captures the WSK Provider NPI
  NTSTATUS
  WskAppWorkerRoutine(
  )
  {
   NTSTATUS Status;
   WSK_PROVIDER_NPI wskProviderNpi;
 
   // Capture the WSK Provider NPI. If WSK subsystem is not ready yet,
   // wait until it becomes ready.
   Status = WskCaptureProviderNPI(
     &WskRegistration, // must have been initialized with WskRegister
     WSK_INFINITE_WAIT,
     &wskProviderNpi
   );

if(!NT_SUCCESS(Status))
   {
    // The WSK Provider NPI could not be captured.
    if( Status == STATUS_NOINTERFACE ) {
    // WSK application's requested version is not supported
   }
   else if( status == STATUS_DEVICE_NOT_READY ) {
    // WskDeregister was invoked in another thread thereby causing
    // WskCaptureProviderNPI to be canceled.
   }
   else {
    // Some other unexpected failure has occurred
   }

return Status;
  }

// The WSK Provider NPI has been captured.
   // Create and set up a listening socket that accepts
   // incoming connections.
   Status = CreateListeningSocket(&wskProviderNpi, ...);

// The WSK Provider NPI will not be used any more.
   // So, release it here immediately.
   WskReleaseProviderNPI(&WskRegistration);

// Return result of socket creation routine
   return Status;

}
 
              WSK应用可以调用WskCaptureProviderNPI多次,每一次的成功调用WskCaptureProviderNPI,都需要相应的调用WskReleaseProviderNPI.WSK应用在调用WskReleaseProviderNPI以后不应该调用任何在WSK_PROVIDER_DISPATCH的函数。

Performing Control Operations on a Client Object

WSK应用在已经成功附加在WSK子系统以后,它可以在其往WSK子系统附加操作中返回的客户对象(WSK_CLIENT)上执行一些控制操作。这些控制操作不需要指定特定的套接字,但是有很多通用的功能。

执行客户对象的控制操作可以通过调用WSK_PROVIDER_DISPATCH结构体中的WskControlClient成员,它是一个指向WskControlClient函数的指针。
 
              如下的代码演示了WSK应用如何使用WSK_TRANSPORT_LIST_QUERY客户控制操作在创建一个新的套接字的时候得到一组可用的指定的网络传输列表。
 
  // Function to retrieve a list of available network transports
  NTSTATUS
  GetTransportList(
   PWSK_PROVIDER_NPI WskProviderNpi,
   PWSK_TRANSPORT TransportList,
   ULONG MaxTransports,
   PULONG TransportsRetrieved
  )
  {
   SIZE_T BytesRetrieved;
   NTSTATUS Status;

// Perform client control operation
   Status =
    WskProviderNpi->Dispatch->
     WskControlClient(
     WskProviderNpi->Client,
     WSK_TRANSPORT_LIST_QUERY,
     0,
     NULL,
     MaxTransports * sizeof(WSK_TRANSPORT),
     TransportList,
     &BytesRetrieved,
     NULL  // No IRP for this control operation
    );

// Convert bytes retrieved to transports retrieved
   TransportsRetrieved = BytesRetrieved / sizeof(WSK_TRANSPORT);

// Return status of client control operation
   return Status;
  }
 
              Creating Sockets

在WSK已经成功附加在WSK子系统上以后,它可以创建套接字来完成网络IO操作。WSK应用通过调用WskSocket函数来创建套接字。这个函数指针通过WSK_PROVIDER_DISPATCH结构的WskSocket成员得到。

WSK应用应该在其创建一个新的套接字的时候,指定套接字的类型。
  
             WSK应用在其创建新的套接字前也应该指定其地址,套接字种类,协议等。
 
             当创建一个新的套接字的时候,如果应用需要在套接字上使能事件回调函数,它必须提供一个套接字上下文和一个客户分发列表结构。
 
             如果套接字创建成功,IRP的IoStatus.Information域包含一个指向新套接字对象结构(WSK_SOCKET)的指针。
 
             如下的代码演示了WSK应用如何创建一个监听套接字。
 
  // Context structure for each socket
  typedef struct _WSK_APP_SOCKET_CONTEXT {
       PWSK_SOCKET Socket;
       .
       .  // Other application-specific members
       .
  } WSK_APP_SOCKET_CONTEXT, *PWSK_APP_SOCKET_CONTEXT;

// Prototype for the socket creation IoCompletion routine
  NTSTATUS
  CreateListeningSocketComplete(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PVOID Context
  );

// Function to create a new listening socket
  NTSTATUS
  CreateListeningSocket(
   PWSK_PROVIDER_NPI WskProviderNpi,
   PWSK_APP_SOCKET_CONTEXT SocketContext,
   PWSK_CLIENT_LISTEN_DISPATCH Dispatch,
  )
  {
   PIRP Irp;
   NTSTATUS Status;

// Allocate an IRP
   Irp =
    IoAllocateIrp(
     1,
     FALSE
    );

// Check result
   if (!Irp)
   {
    // Return error
    return STATUS_INSUFFICIENT_RESOURCES;
   }

// Set the completion routine for the IRP
   IoSetCompletionRoutine(
     Irp,
     CreateListeningSocketComplete,
     SocketContext,
     TRUE,
     TRUE,
     TRUE
   );

// Initiate the creation of the socket
   Status =
   WskProviderNpi->Dispatch->
     WskSocket(
     WskProviderNpi->Client,
     AF_INET,
     SOCK_STREAM,
     IPPROTO_TCP,
     WSK_FLAG_LISTEN_SOCKET,
     SocketContext,
     Dispatch,
     NULL,
     NULL,
     NULL,
     Irp
    );

// Return the status of the call to WskSocket()
   return Status;
  }

// Socket creation IoCompletion routine
  NTSTATUS
  CreateListeningSocketComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  )
  {
   UNREFERENCED_PARAMETER(DeviceObject);

PWSK_APP_SOCKET_CONTEXT SocketContext;

// Check the result of the socket creation
   if (Irp->IoStatus.Status == STATUS_SUCCESS)
   {
    // Get the pointer to the socket context
    SocketContext =
    (PWSK_APP_SOCKET_CONTEXT)Context;

// Save the socket object for the new socket
   SocketContext->Socket =
    (PWSK_SOCKET)(Irp->IoStatus.Information);

// Set any socket options for the new socket
   ...

// Enable any event callback functions on the new socket
   ...

// Perform any other initializations
   ...
   }

// Error status
   else
   {
    // Handle error
    ...
   }

// Free the IRP
   IoFreeIrp(Irp);

// Always return STATUS_MORE_PROCESSING_REQUIRED to
   // terminate the completion processing of the IRP.
    return STATUS_MORE_PROCESSING_REQUIRED;
  }
  
              对于面向连接的套接字,WSK应用可以调用WskSocketConnect函数去创建,绑定,连接套接字在一个单独函数的调用中。
 
             Performing Control Operations on a Socket

WSK应用在成功创建套接字以后,它就可以在套接上进行一些控制操作。这些操作包括设置并得到套接字的设定和执行一些套接字IOCTL操作。
 
            WSK应用通过调用WskControlSocket函数来在套接字上执行一些控制操作。这个函数由套接字提供的派遣函数列表结构的成员WskControlSocket指定。派遣函数列表结构通过套接字对象结构(WSK_SOCKET)的成员Dispatch指定,WSK_SOCKET在创建套接字的操作中由WSK子系统返回。

下面的代码演示了WSK应用如何在数据报套接字上设置SO_EXCLUSIVEADDRUSE套接字设定。
 
  // Prototype for the control socket IoCompletion routine
  NTSTATUS
  ControlSocketComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  );

// Function to set the SO_EXCLUSIVEADDRUSE socket option
  // on a datagram socket
  NTSTATUS
  SetExclusiveAddrUse(
   PWSK_SOCKET Socket
  )
  {
   PWSK_PROVIDER_DATAGRAM_DISPATCH Dispatch;
   PIRP Irp;
   ULONG SocketOptionState;
   NTSTATUS Status;

// Get pointer to the socket's provider dispatch structure
   Dispatch =
    (PWSK_PROVIDER_DATAGRAM_DISPATCH)(Socket->Dispatch);

// Allocate an IRP
   Irp =
    IoAllocateIrp(
       1,
       FALSE
    );

// Check result
   if (!Irp)
   {
    // Return error
    return STATUS_INSUFFICIENT_RESOURCES;
   }

// Set the completion routine for the IRP
   IoSetCompletionRoutine(
    Irp,
    ControlSocketComplete,
    Socket,  // Use the socket object for the context
    TRUE,
    TRUE,
    TRUE
   );

// Set the socket option state to 1 to set the socket option
   SocketOptionState = 1;

// Initiate the control operation on the socket
   Status =
    Dispatch->WskControlSocket(
       Socket,
       WskSetOption,
       SO_EXCLUSIVEADDRUSE,
       SOL_SOCKET,
       sizeof(ULONG),
       &SocketOptionState,
       0,
       NULL,
       NULL,
       Irp
    );

// Return the status of the call to WskControlSocket()
    return Status;
  }

// Control socket IoCompletion routine
  NTSTATUS
  ControlSocketComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  )
  {
   UNREFERENCED_PARAMETER(DeviceObject);

PWSK_SOCKET Socket;

// Check the result of the control operation
   if (Irp->IoStatus.Status == STATUS_SUCCESS)
   {
    // Get the socket object from the context
    Socket = (PWSK_SOCKET)Context;

// Perform the next operation on the socket
    ...
   }

// Error status
   else
   {
    // Handle error
    ...
   }

// Free the IRP
   IoFreeIrp(Irp);

// Always return STATUS_MORE_PROCESSING_REQUIRED to
   // terminate the completion processing of the IRP.
   return STATUS_MORE_PROCESSING_REQUIRED;
  }

如下的代码演示了WSK应用怎样在数据报的套接字上执行SIO_WSK_SET_REMOTE_ADDRESS套接字IOCTL操作。
 
  // Prototype for the control socket IoCompletion routine
  NTSTATUS
  ControlSocketComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  );

// Function to perform the SIO_WSK_SET_REMOTE_ADDRESS socket
  // IOCTL operation on a datagram socket
  NTSTATUS
  SetRemoteAddress(
   PWSK_SOCKET Socket,
   PSOCKADDR RemoteAddress
  )
  {
   PWSK_PROVIDER_DATAGRAM_DISPATCH Dispatch;
   PIRP Irp;
   NTSTATUS Status;

// Get pointer to the socket's provider dispatch structure
   Dispatch =
   (PWSK_PROVIDER_DATAGRAM_DISPATCH)(Socket->Dispatch);

// Allocate an IRP
   Irp =
    IoAllocateIrp(
     1,
     FALSE
    );

// Check result
   if (!Irp)
   {
    // Return error
    return STATUS_INSUFFICIENT_RESOURCES;
   }

// Set the completion routine for the IRP
   IoSetCompletionRoutine(
     Irp,
     ControlSocketComplete,
     Socket,  // Use the socket object for the context
     TRUE,
     TRUE,
     TRUE
   );

// Initiate the IOCTL operation on the socket
   Status =
   Dispatch->WskControlSocket(
     Socket,
     WskIoctl,
     SIO_WSK_SET_REMOTE_ADDRESS,
     0,
     sizeof(SOCKADDR_IN),  // AF_INET
     RemoteAddress,
     0,
     NULL,
     NULL,
     Irp
    );

// Return the status of the call to WskControlSocket()
   return Status;
  }

// Control socket IoCompletion routine
  NTSTATUS
  ControlSocketComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  )
  {
   UNREFERENCED_PARAMETER(DeviceObject);

PWSK_SOCKET Socket;

// Check the result of the control operation
   if (Irp->IoStatus.Status == STATUS_SUCCESS)
   {
    // Get the socket object from the context
    Socket = (PWSK_SOCKET)Context;

// Perform the next operation on the socket
    ...
   }

// Error status
   else
   {
    // Handle error
    ...
   }

// Free the IRP
    IoFreeIrp(Irp);

// Always return STATUS_MORE_PROCESSING_REQUIRED to
   // terminate the completion processing of the IRP.
   return STATUS_MORE_PROCESSING_REQUIRED;
  }

Enabling and Disabling Event Callback Functions

WSK应用可以实现一些事件回调函数,当在套接字上面有关事件发生的时候,WSK子系统会异步调用这些回调函数。WSK应用在其创建一个套接字或在一个监听套接字上接收套接字的时候,可以提供一个客户的派遣函数列表给WSK子系统。派遣列表中包含WSK应用事件回调函数的指针列表。如果WSK应用不需要对特定的套接字执行任何事件回调函数,它就不需要提供派遣函数列表给WSK子系统。

所有的事件回调函数,除了监听套接字的WskInspectEvent和WskAbortEvent事件回调函数,都可以通过SO_WSK_EVENT_CALLBACK套接字设置来使能或关闭。WSK应用在一个套接字上同时对多个回调函数进行使能,但是WSK应用对回调函数的关闭只能单独的关闭。

下面的代码演示了,WSK应用使用SO_WSK_EVENT_CALLBACK套接字设定在面向连接的套接字上去使能WskDisconnectEvent和WskReceiveEvent事件回调函数。

// Function to enable the WskDisconnectEvent and WskReceiveEvent
  // event callback functions on a connection-oriented socket
  NTSTATUS
  EnableDisconnectAndRecieveCallbacks(
    PWSK_SOCKET Socket
  )
  {
   PWSK_PROVIDER_CONNECTION_DISPATCH Dispatch;
   WSK_EVENT_CALLBACK_CONTROL EventCallbackControl;
   NTSTATUS Status;

// Get pointer to the socket's provider dispatch structure
   Dispatch =
    (PWSK_PROVIDER_CONNECTION_DISPATCH)(Socket->Dispatch);

// Specify the WSK NPI identifier
   EventCallbackControl.NpiId = &NPI_WSK_INTERFACE_ID;

// Set the event flags for the event callback functions that
   // are to be enabled on the socket
   EventCallbackControl.EventMask =
    WSK_EVENT_DISCONNECT | WSK_EVENT_RECEIVE;

// Initiate the control operation on the socket
   Status =
    Dispatch->WskControlSocket(
       Socket,
       WskSetOption,
       SO_WSK_EVENT_CALLBACK,
       SOL_SOCKET,
       sizeof(WSK_EVENT_CALLBACK_CONTROL),
       &EventCallbackControl,
       0,
       NULL,
       NULL,
       NULL  // No IRP for this control operation
    );

// Return the status of the call to WskControlSocket()
    return Status;
  }
 
              如下的代码演示了WSK应用如何使用SO_WSK_EVENT_CALLBACK套接字设置在面向连接的套接字上关闭WskReceiveEvnet事件回调函数。
 
  // Prototype for the disable disconnect IoCompletion routine
  NTSTATUS
  DisableDisconnectComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  );

// Function to disable the WskDisconnectEvent event
  // callback functions on a connection-oriented socket
  NTSTATUS
  DisableDisconnectCallback(
   PWSK_SOCKET Socket
  )
  {
   PWSK_PROVIDER_CONNECTION_DISPATCH Dispatch;
   PIRP Irp;
   WSK_EVENT_CALLBACK_CONTROL EventCallbackControl;
   NTSTATUS Status;

// Get pointer to the socket's provider dispatch structure
   Dispatch =
    (PWSK_PROVIDER_CONNECTION_DISPATCH)(Socket->Dispatch);

// Allocate an IRP
   Irp =
    IoAllocateIrp(
     1,
     FALSE
    );

// Check result
   if (!Irp)
   {
    // Return error
    return STATUS_INSUFFICIENT_RESOURCES;
   }

// Set the completion routine for the IRP
   IoSetCompletionRoutine(
       Irp,
    DisableDisconnectComplete,
    Socket,  // Use the socket object for the context
    TRUE,
    TRUE,
    TRUE
   );

// Specify the WSK NPI identifier
   EventCallbackControl.NpiId = &NPI_WSK_INTERFACE_ID;

// Set the event flag for the event callback function that
   // is to be disabled on the socket along with the disable flag
   EventCallbackControl.EventMask =
    WSK_EVENT_DISCONNECT | WSK_EVENT_DISABLE;

// Initiate the control operation on the socket
   Status =
    Dispatch->WskControlSocket(
      Socket,
      WskSetOption,
      SO_WSK_EVENT_CALLBACK,
      SOL_SOCKET,
      sizeof(WSK_EVENT_CALLBACK_CONTROL),
      &EventCallbackControl,
      0,
      NULL,
      NULL,
      Irp
    );

// Return the status of the call to WskControlSocket()
    return Status;
  }

// Disable disconnect IoCompletion routine
  NTSTATUS
  DisableDisconnectComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  )
  {
   UNREFERENCED_PARAMETER(DeviceObject);

PWSK_SOCKET Socket;

// Check the result of the control operation
   if (Irp->IoStatus.Status == STATUS_SUCCESS)
   {
    // The WskDisconnectEvent event callback
    // function is now disabled

// Get the socket object from the context
    Socket = (PWSK_SOCKET)Context;

// Perform the next operation on the socket
    ...
   }

// Error status
   else
   {
    // Handle error
    ...
   }

// Free the IRP
   IoFreeIrp(Irp);

// Always return STATUS_MORE_PROCESSING_REQUIRED to
   // terminate the completion processing of the IRP.
   return STATUS_MORE_PROCESSING_REQUIRED;
  }
 
                对于监听的套接字,在WSK应用已经使能了在套接字上的条件接收的前提下,WskInspectEvent和WskAbortEvent事件回调函数才能被使能。WSK应用通过设置SO_CONDITIONAL_ACCEPT套接字设定来对预前已经绑定了本地传输地址的套接字使能其条件接收模式。

在监听套接字上,条件接收模式已经被使能以后,套接字的WskInspectEvent和WskAbortEvent事件回调函数不能被关闭。

监听套接字可以自动使能在面向连接的套接字的事件回调函数,在其监听套接字的WskAcceptEvent事件回调函数中。WSK应用通过使能在监听套接字上的面向连接的套接字事件回调函数来自动使能这些这些回调函数。

如果WSK应用在其创建的每一个套接字都需要使能事件回调函数,应用可以使用WSK_SET_STATIC_EVENT_CALLBACKS客户控制操作配置WSK子系统自动使能那些事件回调函数。以这种方式使能的事件回调函数,不能够被WSK应用关闭或重新使能。

如下的代码演示了WSK应用如何使用WSK_SET_STATIC_EVENT_CALLBACKS客户控制操作去自动使能在数据报套接字的WskReceiveFromEvent事件回调函数和面向连接的套接字上的WskReceiveEvent事件回调函数.

// Function to set static event callbacks
  NTSTATUS
  SetStaticEventCallbacks(
   PWSK_APP_BINDING_CONTEXT BindingContext,
  )
 {
  WSK_EVENT_CALLBACK_CONTROL EventCallbackControl;
  NTSTATUS Status;

// Specify the WSK NPI identifier
  EventCallbackControl.NpiId = &NPI_WSK_INTERFACE_ID;

// Set the event flags for the event callback functions that
  // are to be automatically enabled on every new socket
  EventCallbackControl.EventMask =
   WSK_EVENT_RECEIVE_FROM | WSK_EVENT_RECEIVE;

// Perform client control operation
  Status =
   BindingContext->
   WskProviderDispatch->
     WskControlClient(
       BindingContext->WskClient,
       WSK_SET_STATIC_EVENT_CALLBACKS,
       sizeof(WSK_EVENT_CALLBACK_CONTROL),
       &EventCallbackControl,
       0,
       NULL,
       NULL,
       NULL  // No IRP for this control operation
     );

// Return status of client control operation
   return Status;
  }
 
              如果WSK应用使用WSK_SET_STATIC_EVENT_CALLBACKS去自动使能事件回调函数,必须在创建任何套接字前完成操作。
 
             Binding a Socket to a Transport Address

WSK应用成功创建套接字以后,它可以将套接字绑定到一个本地的传输地址上。监听套接字必须在接受连接之前绑定到一个本地的传输地址上,数据报套接字在发送或接收数据报前,必须绑定到一个本地的传输地址上。面向连接的套接字必须在连接到远程传输地址之前绑定到一个本地的传输地址上。

注意:基本套接字不支持发送或接收网络数据。因此,WSK应用不能将其绑定到本地传输地址上。
  
             WSK应用通过调用WskBing函数将套接字绑定到本地传输地址上。WskBing函数由套接字的派遣函数列表结构的WskBing成员指定。派遣函数结构由套接字对象结构(WSK_SOCKET)的Dispatch成员指定,WSK_SOCKET在创建套接字的时候由WSK子系统返回。

套接字可以被绑定到一个通配符地址上。
 
            如下代码演示了WSK应用怎样绑定一个监听套接字到本地传输地址上。
 
  // Prototype for the bind IoCompletion routine
  NTSTATUS
  BindComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  );

// Function to bind a listening socket to a local transport address
  NTSTATUS
  BindListeningSocket(
   PWSK_SOCKET Socket,
   PSOCKADDR LocalAddress
  )
  {
   PWSK_PROVIDER_LISTEN_DISPATCH Dispatch;
   PIRP Irp;
   NTSTATUS Status;

// Get pointer to the socket's provider dispatch structure
   Dispatch =
    (PWSK_PROVIDER_LISTEN_DISPATCH)(Socket->Dispatch);

// Allocate an IRP
   Irp =
    IoAllocateIrp(
     1,
     FALSE
    );

// Check result
   if (!Irp)
   {
    // Return error
    return STATUS_INSUFFICIENT_RESOURCES;
   }

// Set the completion routine for the IRP
   IoSetCompletionRoutine(
     Irp,
     BindComplete,
     Socket,  // Use the socket object for the context
     TRUE,
     TRUE,
     TRUE
   );

// Initiate the bind operation on the socket
   Status =
    Dispatch->WskBind(
     Socket,
     LocalAddress,
     0,  // No flags
     Irp
    );

// Return the status of the call to WskBind()
    return Status;
  }

// Bind IoCompletion routine
  NTSTATUS
  BindComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  )
  {
   UNREFERENCED_PARAMETER(DeviceObject);

PWSK_SOCKET Socket;

// Check the result of the bind operation
   if (Irp->IoStatus.Status == STATUS_SUCCESS)
   {
    // Get the socket object from the context
    Socket = (PWSK_SOCKET)Context;

// Perform the next operation on the socket
    ...
   }

// Error status
   else
   {
    // Handle error
    ...
   }

// Free the IRP
   IoFreeIrp(Irp);

// Always return STATUS_MORE_PROCESSING_REQUIRED to
   // terminate the completion processing of the IRP.
   return STATUS_MORE_PROCESSING_REQUIRED;
  }
 
              对于面向连接的套接字,WSK应用可以使用WskSocketConnect函数去创建,绑定,连接套接字。

Listening for and Accepting Incoming Connections

WSK应用绑定监听套接字到一个本地传输地址以后,套接字可以监听从远程传输地址过来的连接。WSK应用可以调用WskAccept接收监听套接字上的连接。应用程序传递给WskAccept函数的IRP被排到队列中,直到连接到来。

如下的代码演示了WSK应用如何调用WskAccept接收收到的连接。
 
  // Prototype for the accept IoCompletion routine
  NTSTATUS
  AcceptComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  );

// Function to accept an incoming connection
  NTSTATUS
  AcceptConnection(
   PWSK_SOCKET Socket,
   PVOID AcceptSocketContext,
   PWSK_CLIENT_CONNECTION_DISPATCH AcceptSocketDispatch
  )
  {
   PWSK_PROVIDER_LISTEN_DISPATCH Dispatch;
   PIRP Irp;
   NTSTATUS Status;

// Get pointer to the socket's provider dispatch structure
   Dispatch =
    (PWSK_PROVIDER_LISTEN_DISPATCH)(Socket->Dispatch);

// Allocate an IRP
   Irp =
    IoAllocateIrp(
     1,
     FALSE
    );

// Check result
   if (!Irp)
   {
    // Return error
    return STATUS_INSUFFICIENT_RESOURCES;
   }

// Set the completion routine for the IRP
   IoSetCompletionRoutine(
    Irp,
    AcceptComplete,
    AcceptSocketContext,
    TRUE,
    TRUE,
    TRUE
   );

// Initiate the accept operation on the socket
   Status =
    Dispatch->WskAccept(
     Socket,
     0,  // No flags
     AcceptSocketContext,
     AcceptSocketDispatch,
     NULL,
     NULL,
     Irp
    );

// Return the status of the call to WskAccept()
    return Status;
  }

// The accept IoCompletion routine
  NTSTATUS
   AcceptComplete(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PVOID Context
  )
  {
   UNREFERENCED_PARAMETER(DeviceObject);

PWSK_SOCKET Socket;
   PVOID AcceptSocketContext;

// Check the result of the accept operation
   if (Irp->IoStatus.Status == STATUS_SUCCESS)
   {
    // Get the accepted socket object from the IRP
    Socket = (PWSK_SOCKET)(Irp->IoStatus.Information);

// Get the accepted socket's context
    AcceptSocketContext = Context;

// Perform the next operation on the accepted socket
    ...
   }

// Error status
   else
   {
    // Handle error
    ...
   }

// Free the IRP
   IoFreeIrp(Irp);

// Always return STATUS_MORE_PROCESSING_REQUIRED to
   // terminate the completion processing of the IRP.
   return STATUS_MORE_PROCESSING_REQUIRED;
  }
 
             可以调用WskAccept函数来接收监听套接字上的接收收到的连接,WSK应用也可以使能套接字上的WskAcceptEvent事件回调函数。如果,WSK应用使能了在监听套接字上的事件回调函数WskAcceptEvent,WSK子系统在套接字接收到一个新的连接的时候,调用这个事件回调函数。

下面的代码演示了WSK怎样通过事件回调函数WskAcceptEvent来接收收到的连接。
 
  // Dispatch table of event callback functions for accepted sockets
  const WSK_CLIENT_CONNECTION_DISPATCH ConnectionDispatch =
  {
   .
   . // Function pointers for the event callback functions
   .
  };

// Pool tag used for allocating the socket context
  #define SOCKET_CONTEXT_POOL_TAG 'tpcs'

// A listening socket's WskAcceptEvent event callback function
  NTSTATUS WSKAPI
  WskAcceptEvent(
   PVOID SocketContext,
   ULONG Flags,
   PSOCKADDR LocalAddress,
   PSOCKADDR RemoteAddress,
   PWSK_SOCKET AcceptSocket,
   PVOID *AcceptSocketContext,
   CONST WSK_CLIENT_CONNECTION_DISPATCH **AcceptSocketDispatch
  )
  {
   PWSK_APP_SOCKET_CONTEXT SocketContext;

// Check for a valid new socket
   if (AcceptSocket != NULL)
   {
    // Allocate the socket context
    SocketContext =
    (PWSK_APP_SOCKET_CONTEXT)
     ExAllocatePoolWithTag(
      NonPagedPool,
      sizeof(WSK_APP_SOCKET_CONTEXT),
      SOCKET_CONTEXT_POOL_TAG
     );

// Check result of allocation
   if (SocketContext == NULL)
   {
    // Reject the socket
    return STATUS_REQUEST_NOT_ACCEPTED;
   }

// Initialize the socket context
    SocketContext->Socket = AcceptSocket;
    ...

// Set the accepted socket's client context
    *AcceptSocketContext = SocketContext;

// Set the accepted socket's dispatch table of callback functions
    *AcceptSocketDispatch = ConnectionDispatch;

// Perform additional operations on the accepted socket
    ...

// Return status indicating that the socket was accepted
    return STATUS_SUCCESS:
   }

// Error with listening socket
   else
   {
    // Handle error
    ...

// Return status indicating that no socket was accepted
    return STATUS_REQUEST_NOT_ACCEPTED;
   }
  }
 
              WSK应用可以配置监听套接字,在套接字上有条件的接收收到的连接。
 
              WSK应用在绑定套接字到本地传输地址之前通过设置SO_CONDITIONAL_ACCEPT使能在监听套接字上的有条件接收模式。
 
              如果在监听套接字上的条件接收模式使能,当套接字上收到一个新的连接的时候,WSK子系统首先会调用WskInspectEvent事件回调函数。套接字的WskInspectEvent事件回调函数可以检查收到的连接请求去探测是否请求应该被接受还是拒绝。接受连接,WskInspectEvent事件回调函数返回InspectAccept,拒绝连接,返回InspectPend.在这种条件下,在对收到的连接请求结束检查流程以后,WSK必须调用WskInspectComplete.如果在套接字连接在完全建立之前,收到的连接请求被放弃。WSK子系统调用WSK应用的WskAbortEvent事件回调函数。

下面的代码演示了WSK应用怎样通过WSK子系统调用监听套接字的WskInspectEvent事件回调函数来检查接收到的连接。
 
  // Inspect ID for a pending inspection
  WSK_INSPECT_ID PendingInspectID

// A listening socket's WskInspectEvent event callback function
  WSK_INSPECT_ACTION WSKAPI
   WskInspectEvent(
   PVOID SocketContext,
   PSOCKADDR LocalAddress,
   PSOCKADDR RemoteAddress,
   PWSK_INSPECT_ID InspectID
  )
  {
   // Check for a valid inspect ID
   if (InspectID != NULL)
   {
    // Inspect local and/or remote address of the incoming
    // connection request to determine if the connection should
    // be accepted or rejected.
    ...

// If the incoming connection should be accepted
    if (...)
    {
     // Return status indicating that the incoming
     // connection request was accepted
     return InspectAccept;
    }

// If the incoming connection should be rejected
    else if (...)
    {
     // Return status indicating that the incoming
     // connection request was rejected
     return InspectReject;
    }

// Cannot determine immediately
    else
    {
     // Save the inspect ID while the inspection is pending.
     // This will be passed to WskInspectComplete when the
     // inspection process is completed.
     PendingInspectID = *InspectID;

// Return status indicating that the result of the
     // inspection process for the incoming connection
     // request is pending
     return InspectPend;
    }
   }

// Error with listening socket
   else
   {
    // Handle error
    ...

// Return status indicating that a socket was not accepted
    return InspectReject;
   }
  }

// A listening socket's WskAbortEvent event callback function
  NTSTATUS WSKAPI
  WskAbortEvent(
   PVOID SocketContext,
   PWSK_INSPECT_ID InspectID
  )
  {
   // Terminate the inspection for the incoming connection
   // request with a matching inspect ID. To test for a matching
   // inspect ID, the contents of the WSK_INSPECT_ID structures
   // must be compared, not the pointers to the structures.
   ...
  }
 
               如果WSK应用发现,在条件接收模式使能的时候,它将接收在监听套接字上收到的连接请求,收到的连接请求将被建立,通过调用WskAccept函数来接受或WSK子系统调用套接字的WskAcceptEvent事件回调函数来实现。

Establishing a Connection with a Destination

在WSK应用绑定一个面向连接的套接字到一个本地传输地址以后,它可以连接套接字到一个远程传输地址为了和远程系统建立连接。WSK应用在套接字上发送或接收数据之前必须将面向连接的套接字连接到远程传输地址。

WSK应用通过WskConnect函数连接套接在到一个远程传输地址。WskConnect函数由套接字提供者派遣结构的WskConnect成员提供。派遣结构由套接字对象结构(WSK_SOCKET)的成员Dispatch提供,套接字对象结构在创建套接字的时候由WSK子系统返回。

下面的代码演示了,WSK应用怎样连接一个面向连接的套接字到远程传输地址。
 
  // Prototype for the connect IoCompletion routine
  NTSTATUS
  ConnectComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  );

// Function to connect a socket to a remote transport address
  NTSTATUS
  ConnectSocket(
   PWSK_SOCKET Socket,
   PSOCKADDR RemoteAddress
  )
  {
   PWSK_PROVIDER_CONNECTION_DISPATCH Dispatch;
   PIRP Irp;
   NTSTATUS Status;

// Get pointer to the socket's provider dispatch structure
   Dispatch =
    (PWSK_PROVIDER_CONNECTION_DISPATCH)(Socket->Dispatch);

// Allocate an IRP
   Irp =
    IoAllocateIrp(
     1,
     FALSE
    );

// Check result
   if (!Irp)
   {
    // Return error
    return STATUS_INSUFFICIENT_RESOURCES;
   }

// Set the completion routine for the IRP
   IoSetCompletionRoutine(
     Irp,
     ConnectComplete,
     Socket,  // Use the socket object for the context
     TRUE,
     TRUE,
     TRUE
   );

// Initiate the connect operation on the socket
   Status =
    Dispatch->WskConnect(
     Socket,
     RemoteAddress,
     0,  // No flags
     Irp
    );

// Return the status of the call to WskConnect()
   return Status;
  }

// Connect IoCompletion routine
  NTSTATUS
  ConnectComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  )
  {
   UNREFERENCED_PARAMETER(DeviceObject);

PWSK_SOCKET Socket;

// Check the result of the connect operation
   if (Irp->IoStatus.Status == STATUS_SUCCESS)
   {
    // Get the socket object from the context
    Socket = (PWSK_SOCKET)Context;

// Perform the next operation on the socket
    ...
   }

// Error status
   else
   {
    // Handle error
    ...
   }

// Free the IRP
   IoFreeIrp(Irp);

// Always return STATUS_MORE_PROCESSING_REQUIRED to
   // terminate the completion processing of the IRP.
   return STATUS_MORE_PROCESSING_REQUIRED;
  }
 
              WSK应用可以调用WskSocketConnect函数去创建,绑定,连接面向连接的套接字。
 
              Sending Data over a Datagram Socket

WSK应用绑定数据报套接字到一个本地传输地址以后,它可以通过套接字来发送数据报。WSK应用通过调用WskSendTo函数在数据报套接字上发送数据报。

下面的代码演示了WSK应用怎样在数据报套接字上发送数据报。
 
  // Prototype for the send datagram IoCompletion routine
  NTSTATUS
  SendDatagramComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
  PVOID Context
  );

// Function to send a datagram
  NTSTATUS
  SendDatagram(
   PWSK_SOCKET Socket,
   PWSK_BUF DatagramBuffer,
   PSOCKADDR RemoteAddress
  )
  {
   PWSK_PROVIDER_DATAGRAM_DISPATCH Dispatch;
   PIRP Irp;
   NTSTATUS Status;

// Get pointer to the provider dispatch structure
   Dispatch =
    (PWSK_PROVIDER_DATAGRAM_DISPATCH)(Socket->Dispatch);

// Allocate an IRP
   Irp =
    IoAllocateIrp(
     1,
     FALSE
    );

// Check result
   if (!Irp)
   {
    //Return error
    return STATUS_INSUFFICIENT_RESOURCES;
   }

// Set the completion routine for the IRP
   IoSetCompletionRoutine(
     Irp,
     SendDatagramComplete,
     DatagramBuffer,  // Use the datagram buffer for the context
     TRUE,
     TRUE,
     TRUE
   );

// Initiate the send operation on the socket
   Status =
    Dispatch->WskSendTo(
     Socket,
     DatagramBuffer,
     0,  // No flags
     RemoteAddress,
     0,
     NULL,  // No associated control info
     Irp
    );

// Return the status of the call to WskSendTo()
   return Status;
  }

// Send datagram IoCompletion routine
  NTSTATUS
  SendDatagramComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  )
  {
   UNREFERENCED_PARAMETER(DeviceObject);

PWSK_BUF DatagramBuffer;
   ULONG ByteCount;

// Check the result of the send operation
   if (Irp->IoStatus.Status == STATUS_SUCCESS)
   {
    // Get the pointer to the datagram buffer
    DatagramBuffer = (PWSK_BUF)Context;

// Get the number of bytes sent
    ByteCount = (ULONG)(Irp->IoStatus.Information);

// Re-use or free the datagram buffer
    ...
   }

// Error status
   else
   {
    // Handle error
    ...
   }

// Free the IRP
   IoFreeIrp(Irp);

// Always return STATUS_MORE_PROCESSING_REQUIRED to
   // terminate the completion processing of the IRP.
   return STATUS_MORE_PROCESSING_REQUIRED;
  }
 
               如果WSK应用为数据报套接字设置了一个固定的远程传输地址或固定的目的传输地址,WskSendTo函数的参数RemoteAddress是任选的,可以为NULL,如果为NULL,数据报被送到固定的远程传输地址或固定的目的传输地址,如果不为NULL,数据报被送到指定的远程传输地址。

数据报套接字上的固定远程传输地址,SIO_WSK_SET_REMOTE_ADDRESS
 
              数据报套接字上的固定目的传输地址,SIO_WSK_SET_SENDTO_ADDRESS.

Receiving Data over a Datagram Socket

WSK应用绑定数据报套接字到一个本地传输地址以后,它就可以在套接字上接收数据报。WSK应用调用WskReceiveFrom函数在数据报套接字上接收数据报。
 
            如下的代码演示了WSK应用怎样在数据报套接字上接收数据报。
 
  // Prototype for the receive datagram IoCompletion routine
  NTSTATUS
  ReceiveDatagramComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  );

// Function to receive a datagram
  NTSTATUS
  ReceiveDatagram(
   PWSK_SOCKET Socket,
   PWSK_BUF DatagramBuffer
  )
  {
   PWSK_PROVIDER_DATAGRAM_DISPATCH Dispatch;
   PIRP Irp;
   NTSTATUS Status;

// Get pointer to the provider dispatch structure
   Dispatch =
    (PWSK_PROVIDER_DATAGRAM_DISPATCH)(Socket->Dispatch);

// Allocate an IRP
   Irp =
    IoAllocateIrp(
     1,
     FALSE
    );

// Check result
   if (!Irp)
   {
    // Return error
    return STATUS_INSUFFICIENT_RESOURCES;
   }

// Set the completion routine for the IRP
   IoSetCompletionRoutine(
    Irp,
    ReceiveDatagramComplete,
    DatagramBuffer,  // Use the datagram buffer for the context
    TRUE,
    TRUE,
    TRUE
   );

// Initiate the receive operation on the socket
   Status =
    Dispatch->WskReceiveFrom(
     Socket,
     DatagramBuffer,
     0,  // No flags are specified
     NULL,  // Not interested in the remote address
     NULL,  // Not interested in any associated control information
     NULL,
     NULL,
     Irp
    );

// Return the status of the call to WskReceiveFrom()
   return Status;
  }

// Receive datagram IoCompletion routine
  NTSTATUS
  ReceiveDatagramComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  )
  {
   UNREFERENCED_PARAMETER(DeviceObject);

PWSK_BUF DataBuffer;
   ULONG ByteCount;

// Check the result of the receive operation
   if (Irp->IoStatus.Status == STATUS_SUCCESS)
   {
    // Get the pointer to the data buffer
    DataBuffer = (PWSK_BUF)Context;
 
    // Get the number of bytes received
    ByteCount = (ULONG)(Irp->IoStatus.Information);

// Process the received datagram
    ...
   }

// Error status
   else
   {
    // Handle error
    ...
   }

// Free the IRP
   IoFreeIrp(Irp);

// Always return STATUS_MORE_PROCESSING_REQUIRED to
   // terminate the completion processing of the IRP.
   return STATUS_MORE_PROCESSING_REQUIRED;
  }
 
               WSK应用可以调用WskReceiveFrom函数在数据报套接字上接收每一个数据报,WSK应用也可以使能套接字上的WskReceiveFromEvent事件回调函数。如果WSK应用在数据报套接字上使能了WskReceiveFromEvent事件回调函数,WSK子系统在套接字上接收到一个新的数据报的时候嗲用套接字的WskReceiveFromEvent事件回调函数。

如下的代码演示了WSK应用如何不同WSK子系统调用数据报套接字的WskReceiveFromEvent事件回调函数来接收数据报。
 
  // A datagram socket's WskReceiveFromEvent
  // event callback function
  NTSTATUS WSKAPI
  WskReceiveFromEvent(
   PVOID SocketContext,
   ULONG Flags,
   PWSK_DATAGRAM_INDICATION DataIndication
  )
  {
   // Check for a valid data indication
   if (DataIndication != NULL)
   {
    // Loop through the list of data indication structures
    while (DataIndication != NULL)
    {
     // Process the data in the data indication structure
     ...

// Move to the next data indication structure
     DataIndication = DataIndication->Next;
    }

// Return status indicating the datagrams were received
    return STATUS_SUCCESS;
   }

// Error
   else
   {
    // Close the socket
    ...

// Return success since no datagrams were indicated
    return STATUS_SUCCESS;
   }
  }
 
               如果数据报套接字的WskReceiveFromEvent事件回调函数在DataIndication参数指向的WSK_DATAGRAM_INDICATION结构列表中没有得到所有的数据报,它可以返回STATUS_PENDING,保留对列表做进一步的处理。在这种条件下,WSK应用在它结束了对结构列表的所有数据报进行处理以后,必须调用WskRelease
函数去释放WSK_DATAGRAM_INDICATION列表结构给WSK子系统。

Sending Data over a Connection-Oriented Socket

WSK应用将面向连接的套接字连接到远程传输地址以后,它就可以在套接字上发送数据了。WSK应用程序可以在面向连接的套接上发送数据,它可以在监听套接字上接收收到的连接。

如下的代码演示了,WSK应用如何在面向连接的套接字上发送数据。
 
  // Prototype for the send IoCompletion routine
  NTSTATUS
  SendComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  );

// Function to send data
  NTSTATUS
  SendData(
   PWSK_SOCKET Socket,
   PWSK_BUF DataBuffer
  )
  {
   PWSK_PROVIDER_CONNECTION_DISPATCH Dispatch;
   PIRP Irp;
   NTSTATUS Status;

// Get pointer to the provider dispatch structure
   Dispatch =
    (PWSK_PROVIDER_CONNECTION_DISPATCH)(Socket->Dispatch);

// Allocate an IRP
   Irp =
    IoAllocateIrp(
     1,
     FALSE
    );

// Check result
   if (!Irp)
   {
    // Return error
    return STATUS_INSUFFICIENT_RESOURCES;
   }

// Set the completion routine for the IRP
   IoSetCompletionRoutine(
     Irp,
     SendComplete,
     DataBuffer,  // Use the data buffer for the context
     TRUE,
     TRUE,
     TRUE
   );

// Initiate the send operation on the socket
   Status =
    Dispatch->WskSend(
     Socket,
     DataBuffer,
     0,  // No flags
     Irp
    );

// Return the status of the call to WskSend()
    return Status;
  }

// Send IoCompletion routine
  NTSTATUS
  SendComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  )
  {
   UNREFERENCED_PARAMETER(DeviceObject);

PWSK_BUF DataBuffer;
   ULONG ByteCount;

// Check the result of the send operation
   if (Irp->IoStatus.Status == STATUS_SUCCESS)
   {
    // Get the pointer to the data buffer
    DataBuffer = (PWSK_BUF)Context;

// Get the number of bytes sent
    ByteCount = (ULONG)(Irp->IoStatus.Information);

// Re-use or free the data buffer
    ...
   }

// Error status
   else
   {
    // Handle error
    ...
   }

// Free the IRP
   IoFreeIrp(Irp);

// Always return STATUS_MORE_PROCESSING_REQUIRED to
   // terminate the completion processing of the IRP.
   return STATUS_MORE_PROCESSING_REQUIRED;
  }

Receiving Data over a Connection-Oriented Socket

WSK应用将面向连接的套接字连接到一个远程传输地址以后,它就可以在套接字上接收数据。WSK应用在面向连接的套接字上接收数据,这可以在监听套接字上接收到。WSK应用通过调用WskReceive函数在面向连接的套接字上接收数据。

如下的代码演示了WSK应用如何在面向连接的套接字上接收数据。
 
  // Prototype for the receive IoCompletion routine
  NTSTATUS
  ReceiveComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  );

// Function to receive data
  NTSTATUS
  ReceiveData(
   PWSK_SOCKET Socket,
   PWSK_BUF DataBuffer
  )
  {
   PWSK_PROVIDER_CONNECTION_DISPATCH Dispatch;
   PIRP Irp;
   NTSTATUS Status;

// Get pointer to the provider dispatch structure
   Dispatch =
    (PWSK_PROVIDER_CONNECTION_DISPATCH)(Socket->Dispatch);

// Allocate an IRP
   Irp =
    IoAllocateIrp(
     1,
     FALSE
     );

// Check result
   if (!Irp)
   {
    // Return error
    return STATUS_INSUFFICIENT_RESOURCES;
   }

// Set the completion routine for the IRP
   IoSetCompletionRoutine(
     Irp,
     ReceiveComplete,
     DataBuffer,  // Use the data buffer for the context
     TRUE,
     TRUE,
     TRUE
   );

// Initiate the receive operation on the socket
   Status =
    Dispatch->WskReceive(
     Socket,
     DataBuffer,
     0,  // No flags are specified
     Irp
    );

// Return the status of the call to WskReceive()
    return Status;
  }

// Receive IoCompletion routine
  NTSTATUS
   ReceiveComplete(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PVOID Context
  )
  {
   UNREFERENCED_PARAMETER(DeviceObject);

PWSK_BUF DataBuffer;
   ULONG ByteCount;

// Check the result of the receive operation
   if (Irp->IoStatus.Status == STATUS_SUCCESS)
   {
    // Get the pointer to the data buffer
    DataBuffer = (PWSK_BUF)Context;
 
    // Get the number of bytes received
    ByteCount = (ULONG)(Irp->IoStatus.Information);

// Process the received data
    ...
   }

// Error status
   else
   {
    // Handle error
    ...
   }

// Free the IRP
   IoFreeIrp(Irp);

// Always return STATUS_MORE_PROCESSING_REQUIRED to
   // terminate the completion processing of the IRP.
   return STATUS_MORE_PROCESSING_REQUIRED;
  }
 
              可以调用WskReceive函数在面向连接的套接字上接收数据,WSK应用也可以使能套接字上的WskReceiveEvent事件回调函数。如果WSK应用在面向连接的套接字上使能了WskReceiveEvent事件回调函数,WSK子系统在套接字上接收到新的数据的时候,调用WskReceiveEvent事件回调函数。

如下代码演示了,WSK应用如何通过WSK子系统调用面向连接的套接字上上WskReceiveEvent事件回调函数来接收数据。
 
  // A connection-oriented socket's WskReceiveEvent
  // event callback function
  NTSTATUS WSKAPI
  WskReceiveEvent(
   PVOID SocketContext,
   ULONG Flags,
   PWSK_DATA_INDICATION DataIndication,
   SIZE_T BytesIndicated,
   SIZE_T *BytesAccepted
  )
  {
   // Check for a valid data indication
   if (DataIndication != NULL)
   {
    // Loop through the list of data indication structures
    while (DataIndication != NULL)
    {
     // Process the data in the data indication structure
     ...

// Move to the next data indication structure
     DataIndication = DataIndication->Next;
    }

// Return status indicating the data was received
    return STATUS_SUCCESS;
   }

// Error
   else
   {
    // Close the socket
    ...

// Return success since no data was indicated
    return STATUS_SUCCESS;
   }
  }
 
               如果面向连接的套接字的WskReceiveEvent事件回调函数没有从通过DataIndication参数指向WSK_DATA_INDICATION结构列表得到所有的数据,它可以返回STATUS_PENDING,保留对列表做进一步的处理。在这种条件下,WSK应用在其从列表得到所有的数据之后,应该调用WskRelease函数释放WSK_DATA_INDICATION结构给WSK子系统。

如果面向连接的套接字的WskReceiveEvent事件回调函数只接收到全部需要接收到的数据的一部分,它必须设置ByteAccepted参数,声明其实际接收到多少数据。如果,套接字的WskReceiveEvent事件回调函数接收到所有的应接收的数据,它就不需要设置BytesAccepts参数了。

Disconnecting a Socket from a Destination

当WSK应用已经结束通过建立的套接字连接完成了发送和接收数据的操作以后,它可以将面向连接的套接字从之前连接的远程传输地址上断开连接。WSK应用通过调用WskDisconnect函数将套接字从远程传输地址上断开连接。WSK应用可以在套接字上执行异常断开或正常断开。

如下的代码演示了WSK应用如何将面向连接的套接从远程传输地址上执行正常的断开连接。
 
  // Prototype for the disconnect IoCompletion routine
  NTSTATUS
  DisconnectComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  );

// Function to disconnect a socket from a remote transport address
  NTSTATUS
  DisconnectSocket(
   PWSK_SOCKET Socket
  )
  {
   PWSK_PROVIDER_CONNECTION_DISPATCH Dispatch;
   PIRP Irp;
   NTSTATUS Status;
 
   // Get pointer to the socket's provider dispatch structure
   Dispatch =
    (PWSK_PROVIDER_CONNECTION_DISPATCH)(Socket->Dispatch);

// Allocate an IRP
   Irp =
    IoAllocateIrp(
     1,
     FALSE
    );

// Check result
   if (!Irp)
   {
    // Return error
    return STATUS_INSUFFICIENT_RESOURCES;
   }

// Set the completion routine for the IRP
   IoSetCompletionRoutine(
    Irp,
    DisconnectComplete,
    Socket,  // Use the socket object for the context
    TRUE,
    TRUE,
    TRUE
   );

// Initiate the disconnect operation on the socket
   Status =
    Dispatch->WskDisconnect(
       Socket,
       NULL,  // No final data to be transmitted
       0,     // No flags (graceful disconnect)
       Irp
    );

// Return the status of the call to WskDisconnect()
   return Status;
  }

// Disconnect IoCompletion routine
  NTSTATUS
  DisconnectComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  )
  {
   UNREFERENCED_PARAMETER(DeviceObject);

PWSK_SOCKET Socket;

// Check the result of the disconnect operation
   if (Irp->IoStatus.Status == STATUS_SUCCESS)
   {
    // Get the socket object from the context
    Socket = (PWSK_SOCKET)Context;

// Perform the next operation on the socket
    ...
   }

// Error status
   else
   {
    // Handle error
    ...
   }

// Free the IRP
   IoFreeIrp(Irp);

// Always return STATUS_MORE_PROCESSING_REQUIRED to
   // terminate the completion processing of the IRP.
   return STATUS_MORE_PROCESSING_REQUIRED;
  }

如果,WSK应用在套接字上执行正常的断开连接,在因为套接字被断开传送WSK_BUF结构的指针给WskDisconnect函数之前,应用可以发送最后空间里的数据到远程传输地址。

如果WSK应用关闭了面向连接的套接字,而没有首先从连接的远程传输地址断开套接字的连接,WSK子系统会在关闭套接字之前,自动执行异常断开连接操作。

Closing a Socket

当WSK应用已经结束了套接字的使用以后,它应该关闭套接字并释放所有相关的资源。WSK应用在其从WSK子系统卸载之前,应该关闭所有打开的套接字。
 
             WSK应用通过调用WskCloseSocket函数来关闭套接字。在调用WskCloseSocket函数之前,WSK应用必须确保在套接字的任何函数中没有在进行中的调用,在应用的其他线程中也没有调用任何套接字的扩展函数。WSK应用仍然可以调用WskCloseSocket,如果仍然有阻塞还没有结束的IRP由于预前调用套接字的函数。

WSK应用在调用WskCloseSocket函数之前,确保在任何线程中没有调用关闭套接字的函数的方法取决于应用的设计。举例来说,如果WSK应用可能在一个线程中关闭套接字,但是其另外的一个函数在一个或另外的线程中被调用,应用程序可以使用引用计数来追踪套接字函数被调用的次数。在这种条件下,WSK应用在调用套接字的函数之前,自动测试并递增套接字的引用计数,然后在函数返回后,自动递减其套接字的引用计数。当其引用计数为0,WSK应用可以安全的调用WskCloseSocket函数去关闭套接字了。

另外,如果WSK应用的设计本身就保证了在调用WskCloseSocket函数之前,在任何线程中都不会有任何关于套接字函数的调用的话,WSP应用就不需要使用引用计数的方法来追踪套接字函数的调用次数了。举例来说,如果WSK应用对指定的套接字操作都在同一个线程中,应用就可以安全的在同一个线程中调用WskCloseSocket函数,而无需引用计数。

调用WskCloseSocket函数将导致WSK子系统取消并结束任何因为调用套接字函数而暂停的IRP.WSK子系统确保在关闭操作结束之前,任何事件回调函数已经返回控制给WSK子系统。

如下的代码演示了WSK应用如何关闭一个套接字。
 
  // Prototype for the socket close IoCompletion routine
  NTSTATUS
  CloseSocketComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  );

// Function to close a socket
  NTSTATUS
  CloseSocket(
   PWSK_SOCKET Socket,
   PWSK_APP_SOCKET_CONTEXT SocketContext
  )
  {
   PWSK_PROVIDER_BASIC_DISPATCH Dispatch;
   PIRP Irp;
   NTSTATUS Status;

// Get pointer to the socket's provider dispatch structure
   Dispatch =
    (PWSK_PROVIDER_BASIC_DISPATCH)(Socket->Dispatch);

// Allocate an IRP
   Irp =
    IoAllocateIrp(
     1,
     FALSE
    );

// Check result
   if (!Irp)
   {
    // Return error
    return STATUS_INSUFFICIENT_RESOURCES;
   }

// Set the completion routine for the IRP
   IoSetCompletionRoutine(
     Irp,
     CloseSocketComplete,
     SocketContext,
     TRUE,
     TRUE,
     TRUE
   );

// Initiate the close operation on the socket
   Status =
   Dispatch->WskCloseSocket(
     Socket,
     Irp
   );

// Return the status of the call to WskCloseSocket()
   return Status;
  }

// Socket close IoCompletion routine
  NTSTATUS
  CloseSocketComplete(
   PDEVICE_OBJECT DeviceObject,
   PIRP Irp,
   PVOID Context
  )
  {
   UNREFERENCED_PARAMETER(DeviceObject);

PWSK_APP_SOCKET_CONTEXT SocketContext;

// Check the result of the socket close operation
   if (Irp->IoStatus.Status == STATUS_SUCCESS)
   {
    // Get the pointer to the socket context
    SocketContext =
    (PWSK_APP_SOCKET_CONTEXT)Context;

// Perform any cleanup and/or deallocation of the socket context
    ...
   }

// Error status
   else
   {
    // Handle error
    ...
   }

// Free the IRP
   IoFreeIrp(Irp);

// Always return STATUS_MORE_PROCESSING_REQUIRED to
   // terminate the completion processing of the IRP.
   return STATUS_MORE_PROCESSING_REQUIRED;
  }
 
              WSK应用调用WskCloseSocket以后,就不应该再调用任何套接字的函数。
 
              如果WSK应用在没有预前在双向上断开套接字的连接的情况下,关闭面向连接的套接字。WSK子系统在关闭套接字之前自动执行异常断开套接字连接的操作。
 
              Registering an Extension Interface

WSK应用成功创建了套接字以后,它可以为套接字注册一个或多个WSK子系统支持的扩展接口。WSK子系统通过检查WSK_PROVIDER_DISPATCH的成员Version,在应用附加在其上面的的时候返回给应用哪些扩展接口是支持的。

每个扩展接口通过NPI定义,独立于WSK NPI.但是扩展接口的NPI不支持NPI指定的特性。
 
            WSK应用通过在套接字上执行SIO_WSK_REGISTER_EXTENSION套接字IOCTL操作来注册特殊的接口。
 
            如果WSK应用试图去注册一个WSK子系统不支持的扩展接口,SIO_WSK_REGISTER_EXTENSION套接字IOCTL操作返回STATUS_NOT_SUPPORTED.
 
            举例来说,扩展接口安装如下代码例子定义。
 
  const NPIID EXAMPLE_EXTIF_NPIID = {...};

typedef struct _EXAMPLE_EXTIF_PROVIDER_DISPATCH {
   .
   . // Function pointers for the functions that are
   . // defined by the extension interface.
   .
  } EXAMPLE_EXTIF_PROVIDER_DISPATCH, *PEXAMPLE_EXTIF_PROVIDER_DISPATCH;

typedef struct _EXAMPLE_EXTIF_CLIENT_DISPATCH {
   .
   . // Function pointers for the callback functions
   . // that are defined by the extension interface.
   .
  } EXAMPLE_EXTIF_CLIENT_DISPATCH, *PEXAMPLE_EXTIF_CLIENT_DISPATCH;
 
  如下代码演示了WSK应用为面向连接的套接字注册扩展接口。
 
  // Client dispatch structure for the extension interface
  const EXAMPLE_EXTIF_CLIENT_DISPATCH ExtIfClientDispatch = {
   .
   . // The WSK application's callback functions
   . // for the extension interface
   .
  };

// Context structure type for the example extension interface
  typedef struct _EXAMPLE_EXTIF_CLIENT_CONTEXT
  {
   const EXAMPLE_EXTIF_PROVIDER_DISPATCH *ExtIfProviderDispatch;
   PVOID ExtIfProviderContext;
   .
   .  // Other application-specific members
   .
  } EXAMPLE_EXTIF_CLIENT_CONTEXT, *PEXAMPLE_EXTIF_CLIENT_CONTEXT;

// Function to register the example extension interface
  NTSTATUS
  RegisterExampleExtIf(
   PWSK_SOCKET Socket,
   PEXAMPLE_EXTIF_CLIENT_CONTEXT ExtIfClientContext
  )
  {
   PWSK_PROVIDER_CONNECTION_DISPATCH Dispatch;
   WSK_EXTENSION_CONTROL_IN ExtensionControlIn;
   WSK_EXTENSION_CONTROL_OUT ExtensionControlOut;
   NTSTATUS Status;

// Get pointer to the socket's provider dispatch structure
   Dispatch =
    (PWSK_PROVIDER_CONNECTION_DISPATCH)(Socket->Dispatch);

// Fill in the WSK_EXTENSION_CONTROL_IN structure
   ExtensionControlIn.NpiId = &EXAMPLE_EXTIF_NPIID;
   ExtensionControlIn.ClientContext = ExtIfClientContext;
   ExtensionControlIn.ClientDispatch = &ExtIfClientDispatch;

// Initiate the IOCTL operation on the socket
   Status =
    Dispatch->WskControlSocket(
      Socket,
      WskIoctl,
      SIO_WSK_REGISTER_EXTENSION,
      0,
      sizeof(WSK_EXTENSION_CONTROL_IN),
      &ExtensionControlIn,
      sizeof(WSK_EXTENSION_CONTROL_OUT),
      &ExtensionControlOut,
      NULL,
      NULL  // No IRP used for this IOCTL operation
    );

// Check result
   if (Status == STATUS_SUCCESS)
   {
    // Save provider dispatch table and provider context
    ExtIfClientContext->ExtIfProviderDispatch =
      (const EXAMPLE_EXTIF_PROVIDER_DISPATCH *)
    ExtensionControlOut.ProviderDispatch;
    ExtIfClientContext->ExtIfProviderContext =
    ExtensionControlOut.ProviderContext;
   }

// Return the status of the call to WskControlSocket()
    return Status;
  }
 
               WSK应用注册扩展接口基于套接字到套接字之间。
 
              Unregistering a Winsock Kernel Application

WSK应用通过调用WskRegister函数成功将自己注册为WSK客户端后,必须确保在驱动的Unload例程返回之前,调用WskDeregister并返回。WSK应用在成功调用WskRegister后在没有调用WskDeregister的情况下,不应该卸载,它必须等待反注册WSK客户对象直到所有的WSK捕获实例提供的NPI都使用WskReleaseProviderNPI释放,所有的套接字被WSK应用关闭。如有有任何传递给WSK_PROVIDER_DISPATCH中的函数的IRP阻塞,WskDeregister也将爱那个等到这些阻塞的IRP完成。WSK应用在调用WskReleaseProviderNPI后,不应该调用WSK_RPOVIDER_DISPATCH.

// Unload function
  VOID
  Unload(
   IN PDRIVER_OBJECT DriverObject
  )
  {
   // Unregister the WSK application
   WskDeregister(
    &WskRegistration
   );

}
 
               WSK应用不需要总在其Unload例程中调用WskDeregister函数,举例来说,如果WSK应用知识一个复杂驱动的子部件,WSK应用可以在WSK应用子部件不再激活的时候调用WskDeregister.在这种情况下,在驱动的Unload例程返回前,必须确保WSK应用通过WskDeregister调用被成功的卸载。

Resolving Host Names and IP Addresses

开始于Windows 7,内核名字解决功能允许内核部件可以在主机名和传输地址间执行协议独立的转换。你可以使用如下的Winsock Kernel客户级别函数执行这些名字解决办法:

WskFreeAddressInfo
  
               WskGetAddressInfo
  
               WskGetNameInfo
  
               这些函数执行的名字和地址的转换跟用户模式下的函数,FreeAddInfoW,GetAddInfoW,和GetNameInfoW各自类似。
   
              为了使用这些功能,你必须将将NTDDI_VERSION宏设置为NTDDI_WIN7或更大以后,重新编译你的驱动。
 
              为了你的驱动使用内核名字功能,比如执行如下的调用次序:
 
             1,调用WskRegister注册WSK.
 
             2, 调用WskCaptureProviderNPI去捕获WSK提供的NPI.
 
             3, 当你完成使用WSK提供NPI,调用WskReleaseProviderNPI去释放WSK提供的NPI.
 
             4, 调用WskDeregister去反注册WSK应用。
 
            如下的代码,使用了上面的次序,显示WSK应用如何调用WskGetAddressInfo函数去转换主机名和传输地址。

NTSTATUS
  SyncIrpCompletionRoutine(
   __in PDEVICE_OBJECT Reserved,
   __in PIRP Irp,
   __in PVOID Context
  )
  {   
   PKEVENT compEvent = (PKEVENT)Context;
   UNREFERENCED_PARAMETER(Reserved);
   UNREFERENCED_PARAMETER(Irp);
   KeSetEvent(compEvent, 2, FALSE);   
   return STATUS_MORE_PROCESSING_REQUIRED;
  }

NTSTATUS
  KernelNameResolutionSample(
   __in PCWSTR NodeName,
   __in_opt PCWSTR ServiceName,
   __in_opt PADDRINFOEXW Hints,
   __in PWSK_PROVIDER_NPI WskProviderNpi
  )
  {
   NTSTATUS status;
   PIRP irp;
   KEVENT completionEvent;
   UNICODE_STRING uniNodeName, uniServiceName, *uniServiceNamePtr;
   PADDRINFOEXW results;

PAGED_CODE();
   //
   // Initialize UNICODE_STRING structures for NodeName and ServiceName
   //
 
   RtlInitUnicodeString(&uniNodeName, NodeName);

if(ServiceName == NULL) {
    uniServiceNamePtr = NULL;
   }
   else {
    RtlInitUnicodeString(&uniServiceName, ServiceName);
    uniServiceNamePtr = &uniServiceName;
   }

//
   // Use an event object to synchronously wait for the
   // WskGetAddressInfo request to be completed.
   //
 
   KeInitializeEvent(&completionEvent, SynchronizationEvent, FALSE);

//
   // Allocate an IRP for the WskGetAddressInfo request, and set the
   // IRP completion routine, which will signal the completionEvent
   // when the request is completed.
   //
 
   irp = IoAllocateIrp(1, FALSE);
   if(irp == NULL) {
    return STATUS_INSUFFICIENT_RESOURCES;
   }

IoSetCompletionRoutine(irp, SyncIrpCompletionRoutine,
    &completionEvent, TRUE, TRUE, TRUE);

//
   // Make the WskGetAddressInfo request.
   //
 
   WskProviderNpi->Dispatch->WskGetAddressInfo (
    WskProviderNpi->Client,
    &uniNodeName,
    uniServiceNamePtr,
    NS_ALL,
    NULL, // Provider
    Hints,
    &results,
    NULL, // OwningProcess
    NULL, // OwningThread
    irp);

//
   // Wait for completion. Note that processing of name resolution results
   // can also be handled directly within the IRP completion routine, but
   // for simplicity, this example shows how to wait synchronously for
   // completion.
   //
 
   KeWaitForSingleObject(&completionEvent, Executive,
                        KernelMode, FALSE, NULL);

status = irp->IoStatus.Status;

IoFreeIrp(irp);

if(!NT_SUCCESS(status)) {
    return status;
   }

//
   // Process the name resolution results by iterating through the addresses
   // within the returned ADDRINFOEXW structure.
   //
 
   results; // your code here

//
   // Release the returned ADDRINFOEXW structure when no longer needed.
   //
 
   WskProviderNpi->Dispatch->WskFreeAddressInfo(
    WskProviderNpi->Client,
    results);

return status;
  }

Windows驱动_WSK驱动之二WSK的操作相关推荐

  1. Windows驱动_WSK驱动之一WSK是什么

    感觉自己还是不够成熟,才过了一天,就开始全盘否定自己,否定Windows内核编程,否定自己之前所做的一切努力.自己还是太容易受环境的影响了,在这全是Android的时代,在这APP满天飞的年代,在这物 ...

  2. Windows驱动_WSK驱动之三WSK编程注意事项

    今天在天涯上看到一篇帖子,真的是好震撼,原来对于那些人来说,自己已经足够幸福了,我们无法去体会那样的痛苦,只有经历的人才才能真正感受,我都无法想象自己,在那种情况下,需要怎样一种担当啊.虽然,男人真的 ...

  3. Windows驱动_WSK驱动之四WSK例程

    最近看了好多文章,也写了好多博客,但是,最近少了很多实践的关系,这一部分是项目的问题,其实大部分还是自己的问题,今天,竟然,知道,随便修改一点微软的例子还可以赚钱,之前自己还真是没有想到的.后续,自己 ...

  4. Windows文件系统过滤驱动开发教程(0,1,2)

    0. 作者,楚狂人自述 我长期网上为各位项目经理充当"技术实现者"的角色.我感觉Windows文件系统驱动的开发能找到的资料比较少.为了让技术经验不至于遗忘和引起大家交流的兴趣我以 ...

  5. linux下装windows驱动,linux下安装windows xp无线网卡驱动

    //如果不采取默认的安装路径,则可以用.configure --prefix="/etc/local"来指定安装目录(此目录是自建) . (5)查看安装后的版本ndiswrappe ...

  6. 如何安装Windows操作系统(五)驱动安装

    VMware的驱动安装选项在这里. 自定义安装截图,安装过程就不截图了. 完成后提示机器重启!!!! 其他的驱动安装方法 安装前后的情况对比.安装完驱动后机器的性能可以得到提升或更好的支持硬件. PS ...

  7. Windows 下TSI721驱动软件使用

    Windows 下TSI721驱动软件使用 文章目录 Windows 下TSI721驱动软件使用 一.概述 二.TSI721使用说明 1.板卡介绍 2.驱动安装包介绍 3.测试用例 结尾 一.概述 随 ...

  8. linux使用windows无线网卡,linux下安装windows xp无线网卡驱动

    linux下安装windows xp无线网卡驱动 发布时间:2008-08-16 20:59:51来源:红联作者:Htgiot 大概对瑞银有用 一.安装及配置步骤: (1)解压: tar -zxvf ...

  9. Windows 文件系统过滤驱动开发教程 (第二版)

    Windows 文件系统过滤驱动开发教程 (第二版)       楚狂人-2007-上海 (MSN:walled_river@hotmail.com)          -1.  改版序....... ...

最新文章

  1. 辩论届人机大战:IBM新AI完胜人类冠军!
  2. IOS 程序插件及功能动态更新思路┊
  3. java中方法的具体化_我为什么要关心Java没有具体化的泛型?
  4. Eclipse上Maven环境配置使用
  5. php做一个微信退款,PHP实现微信退款功能
  6. HDU 1213 How Many Tables(并查集模板)
  7. 信息学奥赛一本通 1112:最大值和最小值的差 | OpenJudge NOI 1.9 05
  8. hutol json null值没了_一篇长文带你在python里玩转Json数据
  9. Java实现redis管道
  10. Android mc怎么和win10联机,我的世界手机版/win10版联机完美互通方法
  11. 一些用的上的在线网站
  12. Apple有史以来屏幕最大的手机iPhone 12最新超详细揭秘
  13. 离职结婚面试买房蚂蚁上市—过山车般魔幻的2020
  14. 江苏大学毕业设计TEX排版(三)
  15. 电动车、船等 机械结构DIY
  16. ansys【经典】——查看应力应变分布
  17. 参加中国移动开发者大会有感
  18. 腾讯QQ圈子——数据挖掘的典型
  19. 从一个Uiautomator的官方demo说源码
  20. 旗点商学院第八期区块链改革(链改)总裁班即将扬帆起航!

热门文章

  1. 为什么不使用多线程?
  2. 第十二节 SprnigBoot使用定时任务
  3. Cello-operator-dashboard的调试设置
  4. CRM客户关系管理系统主要有哪些功能?
  5. 【软件测试】思维开拓—用软件测试的思维测试QQ好友是在线或者离线
  6. JavaSE Set HashSet LinkedHashSet TreeSet 集合练习
  7. 【云海轻站可视化DIY建站系统V1.0.28】功能模块+可视化编辑建站系统+商用多开版+插件+公众号
  8. kettle案例4.1.1--抽取文本数据---TSV文件的抽取
  9. 【mcuclub】温湿度传感器DHT11
  10. 自由软件基金会三十载(一)