关于作者

用户名:hzgmaxwell
笔名:hzgmaxwell
地区:
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



我的收藏夹



查看本站访问记录

访问统计:
文章个数:30
评论个数:11
留言条数:2




Powered by BlogDriver 2.1

需要一个隽永深沉的名字

 

文章

我打磨的小刀  (作者置顶)
网络共享监视器

监视Windows共享文件的存取记录,并记录详细日志……
更新2005-10-13
更新2006-04-09

Who Are Using Me删除文件时,提示有程序在使用它,想知道是谁在使用吗?
文件搜索器refrain具备一些Windows自带搜索无法实现的功能,比如:
重复文件搜索
多目录、并排除某些子目录
文件名支持正则表达式

- 作者: hzgmaxwell 2005年12月26日, 星期一 13:36  回复(0) |  引用(1) 加入博采

网络共享监视器 更新2006-04-09

   

    感谢各位朋友试用网络共享监视器shareMonitor.exe,能有这么多朋友觉得它还有点用,我很高兴。也有些朋友提出了建议,我根据这些建议做了第三次修改,主要有两点:

  1. 日志将不再写在系统的事件查看器中,因为这样的确不太利于日志分析、统计,而是直接写文本文件,文件名由注册表项HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Net Share Monitor下的LogFile指定,默认值为“\netshare.log”;
  2. 轮询时间间隔由原来固定的10秒,改为由用户指定,时间间隔由注册表项HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Net Share Monitor下的Interval指定,默认值为5,单位为秒;

    http://www.codeproject.com 上有很多巧妙的开放源码,这次读写注册表,我破例不再直接调用win32 api,而是用了来自codeproject的一个模板类 http://www.codeproject.com/system/registry_value.asp ,而写日志的部分,我也直接用了我工作中写出的一个通过类CTraceLog ,以至于这次修改只花了我几分钟。

   新的下载链接

EXE下载 源码下载

- 作者: hzgmaxwell 2006年04月9日, 星期日 17:32  回复(9) |  引用(1) 加入博采

I/O完成端口

    I/O完成端口允许应用程序使用线程池来处理异步I/O请求的机制。线程池中的线程都负责处理I/O请求。相对于收到I/O请求时创建线程,通过I/O完成端口应用程序可以更快更有效的处理异步I/O请求。

    CreateIoCompletionPort函数把一个或多个文件句柄关联到一个I/O完成端口。当对其中某个文件的异步I/O操作完成时,一个I/O完成包在相应的I/O完成端口排队。这可以把多个文件的同步点组合到一个对象中。

    线程调用GetQueuedCompletionStatus等待一个完成包到达完成端口,而不是直接等待异步I/O操作完成。线程池中所有线程都阻塞在这个函数,当一个完成包到达完成端口时,线程按后入先出(LIFO)的顺序释放。这意味着当一个完成包到达完成端口时,系统释放最后一个在该函数阻塞的线程来处理完成请求。

    一个线程调用GetQueuedCompletionStatus之后,它就被绑定到这个完成端口,直到它退出、或者绑定到不同的完成端口、或者调用CloseHandle撤销到该完成端口的绑定。一个线程最多只能绑定到一个完成端口。

完成端口最重要的特性就是并发量,其值是在完成端口创建时指定的。它限定了绑定到本完成端口的线程的可运行个数。当绑定到某完成端口的可运行的线程个数超过并发量,系统会阻塞后续线程的执行,直到可运行线程的个数低于并发量。当完成端口的队列中有完成包在排队,而可运行线程的个数也达到并发量,这时系统是最高效的,因为当一个运行着的线程调用GetQueuedCompletionStatus,它会立刻取得完成包,这样避免了操作系统内部的“线程上下文环境切换”。

    一般情况下,并发量设为CPU的个数,但如果对一个完成包的处理是比较耗时的操作,应该设个较大值,具体多少最好由试验值来定。

    PostQueuedCompletionStatus函数允许应用程序不通过发起一个异步I/O操作就可以发送一个自定义的完成包,这样就可以把外部事件通知线程池中的工作线程。

    完成端口也是通过引用计数来维持它的生命周期,当没有外部引用时,完成端口被释放。所有与完成端口绑定的文件句柄都引用了它,所以在释放完成端口之前,必须释放所有绑定到它的文件句柄。

使用I/O完成端口

    I/O完成端口是使用线程池的一种机制,除了负责处理异步I/O请求的工作者线程之外,还需要一个主线程创建、管理完成端口和线程池中的工作者线程。

    主线程的执行过程如下:

  1. 调用CreateIoCompletionPort创建完成端口;
  2. 根据CPU个数创建一定数量的线程;
  3. 调用CreateIoCompletionPort异步I/O操作涉及的文件句柄和第1步创建的完成端口关联。

    工作者线程的执行过程如下:

  1. 调用GetQueuedCompletionStatus等待异步I/O完成包到达;
  2. 根据完成包的的内容做响应处理。
  3. 循环执行1~2。

    整个机制中,最关键的是I/O完成包的流向。

    当调用一个异步操作接口(如调用WSARecv、WSASend或者调用ReadFileExWriteFileEx去读一个以FILE_FLAG_OVERLAPPED标志打开的文件)时,应用程序就向系统内核发起了一次异步I/O请求。这时需要传递一个LPOVERLAPPED结构和一个LPOVERLAPPED_COMPLETION_ROUTINE回调函数指针(指向异步I/O完成时回调的函数)给异步操作接口作参数。

    当系统内核处理完某异步I/O请求后,如果异步I/O请求发起时传入的LPOVERLAPPED_COMPLETION_ROUTINE不为空,该函数会被调用。对于I/O完成端口模型,该值都为空,这样系统内核构造一个I/O完成包把它放入I/O完成端口的队列。

    线程池中的工作者线程不断调用GetQueuedCompletionStatusI/O完成端口的队列中取出I/O完成包,根据完成包的的内容做响应处理。

    由此可见,I/O完成端口是个抽象的实体,它拥有(对应、管理)一个线程池(其中包括若干工作者线程)和一个I/O完成包的队列。

    同样,I/O完成包也是个抽象实体,它至少对应一个异步I/O操作的目标(可以是文件和Socket)句柄和一个描述一次异步I/O操作的结构,这样工作者线程就有了作出响应的依据。

    GetQueuedCompletionStatus的三个输出参数都可以看成是I/O完成包的内容,其中

BOOL GetQueuedCompletionStatus(
    [in]HANDLE CompletionPort,
    [out]LPDWORD lpNumberOfBytes,
    [out]PULONG_PTR lpCompletionKey,
    [out]LPOVERLAPPED* lpOverlapped,
    [in]DWORD dwMilliseconds
);

    lpNumberOfBytes    返回本次异步I/O操作完成传输的字节数,该值由系统内核填充;

    lpCompletionKey    返回本次异步I/O操作目标的相关信息,该值返回的仅仅是个内存地址,具体是些什么数据如何分布,由把异步I/O操作目标句柄和完成端口关联时(即主线程在第3步)传CreateIoCompletionPort的第三个参数指定,即CreateIoCompletionPort第三个参数指向的东西就是lpCompletionKey指向的东西,系统不作任何改动。要注意的是,CreateIoCompletionPort在把多个文件句柄管关联到同一个完成端口时,为每个句柄指定不同的CompletionKey值,内核在完成一次异步I/O操作时,取出相应的CompletionKey值放入I/O完成包。因而在Anthony Jones, Jim Ohlund写的《Network Programming for Microsoft Windows》中,这部分数据被称为Per-handle Data,也就是说这部分数据和完成端口关联的诸多文件句柄是一一对应的。

HANDLE CreateIoCompletionPort(
    [in]HANDLE FileHandle,
    [in]HANDLE ExistingCompletionPort,
    [in]ULONG_PTR CompletionKey,
    [in]DWORD NumberOfConcurrentThreads
);

    lpOverlapped        表面上它返回的就是一个LPOVERLAPPED,但在实际应用中通常通过它来传递描述一次异步I/O操作的相关信息,所以该值返回的也仅仅是个内存地址,具体是些什么数据如何分布,与发起异步I/O请求时传给异步API的LPOVERLAPPED/LPWSAOVERLAPPED 参数有关。在Anthony Jones, Jim Ohlund写的《Network Programming for Microsoft Windows》中,这部分数据被称为Per-I/O Operation Data,因为这部分数据和一次异步I/O操作一一对应。和lpCompletionKey不同的是,系统要求传入的lpOverlapped所指向的必须是个LPOVERLAPPED/LPWSAOVERLAPPED 结构,并且也会对这部分进行修改,但对lpOverlapped+sizeof(LPOVERLAPPED)之外的内存不作任何修改。

typedef struct _PER_IO_DATA
{
    OVERLAPPED Overlapped;
    WSABUF DataBuf;
    char buffer[1024];
    int BufferLen;
    int OperationType;
}PER_IO_DATA, *LPPER_IO_DATA;

typedef struct _PER_HANDLE_DATA
{
    SOCKET Socket;
    SOCKADDR_STORAGE ClientAddr;
}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

LPPER_IO_DATA lpPerIoData = (LPPER_IO_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_DATA));
...
lpPerIoData->OperationType = 1001; // 任意值,只要自己明白其含义即可,这里用1001标识这是一次WSARecv操作,这样线程池中的工作者线程在获得此次异步I/O操作的完成包时,就可以知道刚刚完成的是一次WSARecv操作。
WSARecv(?,?,?,?,?,(LPOVERLAPPED)lpPerIoData,NULL);

//这里把Overlapped作为PER_IO_DATA的第一个成员,所以直接转换即可,否则

WSARecv(?,?,?,?,?,&(lpPerIoData->Overlapped),NULL);

GetQueuedCompletionStatus取得I/O完成包后,如果Overlapped是PER_IO_DATA的第一个成员,lpOverlapped所指的也就是lpPerIoData的地址,工作者线程可以获取OperationType成员的值;如果Overlapped不是PER_IO_DATA的第一个成员,可以使用CONTAINING_RECORD宏来获取lpPerIoData的地址:

lpPerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(lpOverlapped, PER_IO_DATA, Overlapped);

- 作者: hzgmaxwell 2006年02月28日, 星期二 21:43  回复(1) |  引用(1) 加入博采

优化的艺术

    通常有一些数组,我们要根据输入的值变化主、次下标的值,比如:
    char map[100][100];
    假设有下面这样一段代码:

int inKey,i,j;
switch(inKey)
{
    case 65:
        if(i<99)
            i++;
        break;
    case 66:
        if(i>0)
            i--;
        break;
    case 67:
        if(j<99)
            j++;
        break;
    case 68:
        if(j>0)
            j--;
    break;
}

    为了简洁、高效,做下面的优化:

int inKey,i,j,m,n,step[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
m = i+step[inKey-65][0];
n = j+step[inKey-65][0];
if(m>=0 && m<100)
    i = m;
if(n>=0 && n<100)
    j = n;

    通常要对m和n的有效性作检查,确保(0=<m,n<100),而这样的检查也应该放在变换之后来做,如果在变换之前做,需要四个if语句,也就是说即使不设step这个数组变量。这段代码也应该这样来写:

int inKey,i,j,m,n;
switch(inKey)
{
    case 65:
        m = i+1;
        break;
    case 66:
        m = i-1;
        break;
    case 67:
        n = j+1;
        break;
    case 68:
        n = j-1;
        break;
}
if(m>=0 && m<100)
    i = m;
if(n>=0 && n<100)
    j = n;


    如果既不想多设个辅助变量(如上面的step数组),又不想使用拖沓、低效的switch语句,怎么办?先做一道题。

    已知整数inKey与整数step之间的对应关系如下表所示:

inKeystep
01
1-1

    请写出一个满足上述关系的表达式。

    这里直接给出答案:2*inKey+step = 1;

    对上例就有了:2*(inKey-65)+step = 1;所以step = 131-2*inKey;因而上面的代码也就写成:

int inKey,i,j,m,n;
if(inKey>64 && inKey<67)
    m = i+131-2*inKey;
if(inKey>66 && inKey<69)
    n = j+135-2*inKey;
if(m>=0 && m<100)
    i = m;
if(n>=0 && n<100)
    j = n;

    这样的优化看起来是不是很美啊!


    有时候,输入值和下标差值之间的关系并不是这么明显,但写都应该基于这样的原则来写,才会简洁、高效,当然降低了一点点可读性。

  1. 找出输入值与下标差值之间的关系,用简单的整数运算式代替switch语句。
  2. 先做转换再检查合法性。

- 作者: hzgmaxwell 2006年02月28日, 星期二 21:36  回复(0) |  引用(1) 加入博采