WIN32汇编语言教程:第16章 TCP/IP和网络通信 · 16.5 ICMP协议编程(1)
前面几节中已经讨论了以下内容:
● WinSock函数的基本使用方法。
● 非阻塞模式下网络通信程序的结构。
● 在应用程序层次上使用流套接字和数据报套接字进行通信。
● 网络通信中的客户机/服务器模型以及它们的实现。
● TCP协议和UDP协议的使用。
在这一节中将继续讨论下列内容:
● 阻塞模式下网络通信程序的结构。
● 在IP协议层次上使用原始套接字进行通信。
● 主机名到IP地址的解析。
● ICMP协议的使用。
为了说明这些内容,本节使用一个控制台界面的Ping程序来当做例子,控制台程序的结构类似于DOS方式下的顺序执行方式,非常适合用于阻塞型的通信程序。有关控制台的操作比较简单,所以本书前面的内容中并没有用独立的章节来介绍,在这里首先以一个小节的篇幅补一补课。
16.5.1 题外话:控制台程序
随着用户界面设计的发展,人机交互的方式同过去也有了很大的不同,主流的用户界面已经从DOS下的字符终端界面发展成图形的窗口界面,但是在一些特定的程序中,过去这种老式的界面还是有着很大的用途。实际上,Windows中也保留了这种字符终端形式的界面,使用这种界面的程序被称为控制台程序。
控制台界面最主要的用途是用于网络的远程维护中。进行远程维护时一般使用Telnet等工具登录到远程主机并在上面执行命令,如果执行的是图形界面的程序,这个界面是无法远程操作的,所以我们可以发现Windows中用于网络的命令大多数是控制台界面的,如Ping,Netstat,Tracert,Arp,Route,Ipconfig和Finger等,与此相比,很难想像类似于Office一类的软件会用在远程操作中。另外在Windows系统中,以前的16位DOS程序也采用控制台窗口的方式执行。
但是不要因为控制台程序和老的DOS程序在运行界面上的相似性就认为它们是DOS下的16位exe文件,实际上,它们是不折不扣的32位的PE程序,它们可以使用Win32 API函数,文件头中同样有导入表和导出表,可以在程序中建立多个线程执行,总之,除了界面操作上的不同外,它们可以使用Win32编程中的所有东西。进一步来说,如果一定要让它有一个窗口的话,也可以在其中使用CreateWindow函数来创建一个窗口,这样控制台程序可以在使用终端界面输入输出的同时使用窗口上的菜单来操作(但估计没有人会做这样的事情)。
下面是一个包含了控制台操作中常用代码的_Console.asm文件,这个文件会用在后面的Ping例子中:
;####################################################################
; 控制台程序的公用子程序
;####################################################################
.data?
hStdIn dd ? ;控制台输入句柄
hStdOut dd ? ;控制台输出句柄
.code
;####################################################################
; 控制台 Ctrl+C 捕获例程
; 这个子程序和Ping例子的主程序配合使用,dwOption变量在Ping.asm中定义
;####################################################################
_CtrlHandler proc _dwCtrlType
pushad
mov eax,_dwCtrlType
.if eax == CTRL_C_EVENT || eax == CTRL_BREAK_EVENT
or dwOption,F_ABORT
.endif
popad
mov eax,TRUE
ret
_CtrlHandler endp
;####################################################################
; 控制台初始化
;####################################################################
_ConsoleInit proc
invoke GetStdHandle,STD_INPUT_HANDLE
mov hStdIn,eax
invoke GetStdHandle,STD_OUTPUT_HANDLE
mov hStdOut,eax
invoke SetConsoleCtrlHandler,addr _CtrlHandler,TRUE
ret
_ConsoleInit endp
;####################################################################
; 控制台输出子程序
; 注意: 用 WriteConsole 输出则执行时无法用 > 重定向到文件
; 因此用 WriteFile
;####################################################################
_ConsolePrint proc _lpsz
local @dwCharWritten
pushad
invoke lstrlen,_lpsz
lea ecx,@dwCharWritten
invoke WriteFile,hStdOut,_lpsz,eax,ecx,NULL
popad
ret
_ConsolePrint endp
1. 控制台的输入和输出句柄
和DOS程序的界面类似,控制台程序的输入输出都在一个终端类型的窗口中完成,这个终端窗口可以用一个句柄来引用,获取这个句柄可以使用GetStdHandle函数:
invoke GetStdHandle,nStdHandle
.if eax != INVALID_HANDLE_VALUE
mov hStd,eax
.endif
虽然控制台程序的输入输出都在这个终端窗口中完成,但由于用于输入和输出的句柄是不同的,为了获取不同的句柄,nStdHandle参数需要指定为以下不同的取值:
● STD_INPUT_HANDLE——标准输入句柄。
● STD_OUTPUT_HANDLE——标准输出句柄。
● STD_ERROR_HANDLE——标准出错信息句柄。
函数执行成功将返回对应的句柄,否则将返回INVALID_HANDLE_VALUE值。在上面的控制台初始化子程序_ConsoleInit中,程序分别获取输入和输出句柄并保存到hStdIn和hStdOut变量中以供后用。
2. 控制台窗口的输入和输出
如果需要向控制台窗口输出文本的话,可以使用两种方法,第一种方法是使用专用的控制台输出函数WriteConsole:
invoke WriteConsole,hConsoleOutput,lpBuffer,\
nNumberOfCharsToWrite,lpNumberOfCharsWritten,lpReserved
参数hConsoleOutput指定为前面获取的标准输出句柄,lpBuffer参数指向输出的内容,nNumberOfCharsToWrite参数为要输出的数据长度,lpNumberOfCharsWritten指向一个双字变量,用来返回实际显示的数据长度。
另一个方法就是将控制台句柄当做文件句柄来使用,使用WriteFile函数就可以将输出内容“写”到控制台窗口上,正如上面的_ConsolePrint子程序所演示的那样。使用这两种方法的区别是:WriteConsole函数不支持输出内容的重定向;而WriteFile函数支持重定向。使用命令行方式的管道操作符可将内容重定向到一个文件中,如下面的命令将Ping的结果存放到result.txt文件中:
ping www.yahoo.com > result.txt
但是,如果使用WriteConsole函数来输出,那么结果仍然会被显示在控制台窗口中,而不是被定向到result.txt文件中去。如果程序希望某些内容可以重定向,而某些内容不允许重定向,那么可以混合使用这两种方法。
从控制台窗口接收键盘输入可以用ReadConsole或ReadFile两种方式来完成,ReadConsole函数的使用方法是:
invoke ReadConsole,hConsoleInput,lpBuffer,\
nNumberOfCharsToRead,lpNumberOfCharsRead,lpReserved
上页:第16章 TCP/IP和网络通信 · 16.4 UDP协议编程(5) 下页:第16章 TCP/IP和网络通信 · 16.5 ICMP协议编程(2)