WIN32汇编语言教程:第16章 TCP/IP和网络通信 · 16.5 ICMP协议编程(6)

目标主机的IP地址由h_list字段来返回,如图16.15所示,gethostbyname函数返回hostent结构指针,hostent结构中的h_list字段本身也是一个指针,它指向一个IP地址的指针列表,当主机名对应多个IP地址的时候,这个指针列表中就存在多个表项,最后一个表项总是NULL,用来指示列表的结束。真正的IP地址数据的存放位置由指针列表中的多个指针指出。


图16.15 从gethostbyname函数的返回值得到IP地址

所以对gethostbyname函数的返回值要经过多次指针的转换后才能得到IP地址数据,一般的处理代码如下(比较麻烦):

   invoke gethostbyname,addr szHostName

   .if    eax

          mov    eax,[eax + hostent.h_list] ;取h_list指针

          .while dword ptr [eax]

                  mov    ecx,[eax]      ;取一个IP地址的指针

                  mov    ecx,[ecx]      ;用指针取出IP地址

                   ;现在ecx中就是IP地址,可以进行处理了!

                   add    eax,4          ;指向下一个IP地址指针

           .endw

   .endif

这样得到的IP地址已经是网络字节顺序的,可以直接用在其他函数中。

gethostbyname函数是利用DNS来解析主机地址的,解析需要的时间可能是几秒的数量级,但函数的执行是阻塞方式的,也就是说解析完成之前函数不会返回。如果在窗口程序中使用这个函数的话,最好将它安排在另一个线程中执行,否则在解析的过程中窗口会一动不动地呆在屏幕上。

另外,使用gethostname函数可以获取本地计算机的主机名:

   invoke gethostname,lpbuffer,size

函数将在lpbuffer指定的缓冲区中返回主机名字符串,size参数指定缓冲区的大小。

2. 阻塞方式的工作流程

前面已经讲述过,当使用WSAAsyncSelect函数将一个套接字设置为非阻塞模式后,如果有某种动作发生,WinSock接口会用消息通知指定的窗口过程,程序不必费心地去随时检测套接字的状态。但是当套接字工作于阻塞模式的话就不行了,这时必须有一种方法来检测套接字的状态,以便确定能否对套接字进行某种操作,否则对套接字的操作会无休止地等待。

比如要读一个阻塞模式的套接字的时候,如果缓冲区中没有数据,那么recv函数就会等待直到有数据到达,如果对方永远不发送数据的话,那么函数就永远地等待下去,只有在预先知道缓冲区中已经有数据的情况下再去读才能避免这种情况。

使用select函数可以进行这种检测,select函数可以同时检测多个套接字是否可读,是否可写,或是否有错误发生,并且可以指定检测的超时时间,它的用法是:

   invoke select,nfds,lpreadfds,lpwritefds,lpexceptfds,lptimeout

nfds参数是为了和BSD Unix Socket的兼容而设置的,函数将这个参数忽略,lpreadfds,lpwritefds和lpexceptfds分别指向不同的fd_set结构,用来指定需要检测的套接字句柄。fd_set结构的定义如下:

fd_set STRUCT

 fd_count DWORD     ?                 ;fd_array中存放的套接字句柄数量

 fd_array SOCKET FD_SETSIZE dup(?)    ;套接字句柄列表

fd_set ENDS

假如要检测#1和#2套接字是否可读,那么可以在lpreadfds参数指向的fd_set结构的fd_array中放入它们的句柄并将fd_count设置为2。如果同时要检测#1、#3和#4套接字是否可写,那么可以在lpwritefds指向的fd_set结构中的fd_array中放入它们的句柄并将fd_count设置为3。同样,lpexceptfds指向的fd_set结构存放的是要检测出错状态的套接字句柄列表。如果没有某种操作需要检测,那么可以将相应的指针设置为NULL。

当函数返回的时候,函数会将这些fd_set结构中就绪的套接字句柄保留,而将没有就绪的套接字句柄清零,这时扫描结构中的fd_array列表并对还存在的套接字句柄进行相应的操作就能保证函数不会进入等待状态。

select函数的lptimeout指向一个timeval结构,用来指定检测的超时时间,该结构定义如下:

timeval STRUCT

 tv_sec        DWORD ?      ;s

 tv_usec       DWORD ?      ;us,注意不是ms!

timeval ENDS

lptimeout参数的用法有以下几种:

● 如果lptimeout参数为NULL,那么函数将永远等待下去,直到列表中有某个套接字就绪时才返回。

● 如果lptimeout指向了一个timeval结构,而且结构中的时间定义为0,那么不管有没有套接字就绪函数都会马上返回。

● 如果lptimeout指向了一个timeval结构,而且结构中的时间定义不为0,如果经过了timeval指定的时间后还没有套接字就绪,那么函数出错返回,如果在指定的时间有套接字就绪,那么函数马上返回。

下列几种情况可以让lpreadfds指定的套接字就绪:处于监听的套接字可以进行accept操作了,套接字有数据到达(可以进行读操作了),套接字的连接被关闭。下列情况可以让lpwritefds指定的套接字就绪:调用了connect函数的非阻塞套接字连接成功,套接字可以用来发送数据了。调用了connect函数的非阻塞套接字连接失败,可以让lpexceptfds指定的套接字就绪。

如果select函数因为超时而返回,返回值是0;因为失败而返回,返回值是SOCKET_ERROR;如果因为某个套接字就绪而返回,返回值是就绪套接字的数量。

一般来说,使用select构成的循环可以让所有阻塞方式的套接字协同工作,循环的结构如下:

.while TRUE

       ;将可能用来接收数据的套接字填入readfds

       ;将可能用来发送数据、等待连接成功的套接字填入writefds

       ;将等待连接成功的套接字填入exceptfds

       ;规定超时时间

       invoke select,NULL,lpreadfds,lpwritefds,lpexceptfds,lptimeout

       .if    ! eax

               .continue

       .elseif eax == SOCKET_ERROR

               ;出错

       .else

               ;扫描readfds并对留在其中的套接字句柄进行recv或accept操作

               ;扫描writefds并对留在其中的套接字句柄进行send操作

       .endif

.endw

在例子程序Ping.asm中使用的正是这样的结构,读者可以对比源代码分析:

.while TRUE

       ;构造ICMP数据包

       ;通过sendto函数发送“请求回显”ICMP数据包

       ;将套接字句柄填入readfds并将超时时间设置为1秒

       invoke select,0,lpreadfds,NULL,NULL,lptimeout

       .if    eax

               ;用recvfrom接收“回显应答”ICMP数据包

               ;显示Ping的时间、地址等各种信息

       .else

               ;显示Ping超时

       .endif

.endw

上页:第16章 TCP/IP和网络通信 · 16.5 ICMP协议编程(5) 下页:第16章 TCP/IP和网络通信 · 16.5 ICMP协议编程(7)

第16章 TCP/IP和网络通信

版权所有 © 云南伯恩科技 证书:粤ICP备09170368号