WIN32汇编语言教程:第16章 TCP/IP和网络通信 · 16.2 WinSock接口(2)
在源文件中包含了对应的inc文件和lib文件后,在使用其他WinSock函数之前,必须首先使用WSAStartup函数来装入并初始化动态链接库,否则对其他任何WinSock函数的调用都不会成功:
invoke WSAStartup,wVersionRequested,lpWSAData
.if eax
;初始化库出现错误
.endif
wVersionRequested是一个16位的参数,用来指定动态链接库将支持哪个版本的WinSock函数,其中的低8位指定主版本号,高8位用来指定副版本号,假如要使用1.1版的,可以在这里使用0101h,假如需要使用2.0版本函数,则可以将参数指定为0002h。
lpWSAData参数指向一个WSADATA结构,用来返回动态链接库的详细信息,结构的定义为:
WSADATA STRUCT
Wversion WORD ? ;库文件建议应用程序使用的版本
wHighVersion WORD ? ;库文件支持的最高WinSock版本
szDescription BYTE WSADESCRIPTION_LEN + 1 dup (?) ;库描述字符串
szSystemStatus BYTE WSASYS_STATUS_LEN + 1 dup (?) ;系统状态字符串
iMaxSockets WORD ? ;同时支持的最大套接字数量
iMaxUdpDg WORD ? ;2.0版中已废弃的参数
lpVendorInfo DWORD ? ;2.0版中已废弃的参数
WSADATA ENDS
szDescription字段中返回的字符串一般是“WinSock 2.0”之类的库描述串,szSystemStatus字段中返回的是类似于“Running”一类的运行状态字符串。如果库装入成功,函数将返回0,否则将返回下面的出错代码:
● WSASYSNOTREADY——网络子系统未准备好。
● WSAVERNOTSUPPORTED——不支持指定的版本。
● WSAEINPROGRESS——另一个阻塞方式的WinSock 1.1操作正在进行中。
● WSAEPROCLIM——WinSock接口已经到达了所支持的最大任务数。
● WSAEFAULT——输入参数lpWSAData指定的指针无效。
如果不再需要使用WinSock函数了,那么必须使用WSACleanup 函数将库释放:
invoke WSACleanup
WSACleanup函数没有输入参数,它将释放动态链接库并自动释放所有被创建的套接字等资源。如果函数执行成功将返回0,否则将返回SOCKET_ERROR。
WinSock函数中有些参数是16位的,如WSAStartup函数中的wVersionRequested参数,但是在传递这些参数时,并不是堆栈中压入一个字(word),而是将它扩展到32位的双字(dword)以后再压入这个双字,所以在源程序的invoke语句中并不需要特殊的处理。
2. 套接字的创建和关闭
套接字是通信的一端,当装载WinSock库以后,在开始通信之前必须为通信进程建立一个套接字,如果在一个程序中同时使用多个通信进程(比如用TCP协议同时连接到几个不同的主机),那就必须为每个通信连接创建一个套接字,创建套接字使用socket函数:
invoke socket,af,type,protocol
.if eax != INVALID_SOCKET
mov hSocket,eax
.endif
函数的第一个参数af用来指定套接字使用的地址格式。在不同操作系统下,这个参数可以指定为AF_UNSPEC,AF_UNIX或AF_OSI等不同的值,但是在WinSock中只能使用AF_INET(也可以使用PF_INET,在Windows.inc中PF_INET被定义为与AF_INET等效)。
第二个参数type用来指定套接字的类型。正如前面介绍的,套接字有流套接字、数据报套接字和原始套接字等,下面是最常用的几种套接字类型定义:
● SOCK_STREAM——流套接字,使用TCP协议提供有连接的和可靠的传输。
● SOCK_DGRAM——数据报套接字,使用UDP协议提供无连接的和不可靠的传输。
● SOCK_RAW——原始套接字,WinSock接口并不使用某种特定的协议去封装它,而是由程序自行处理数据包以及协议首部。
protocol参数配合type参数使用,用来指定使用的协议类型,当type参数指定为SOCK_STREAM或者SOCK_DGRAM的时候,系统已经明确确定使用TCP和UDP协议来工作,所以这时这个参数并没有什么意义,可以指定为0,但是当type参数指定为SOCK_RAW类型的时候,使用protocol参数可以更详细地指定原始套接字的工作方式。
当type参数指定为SOCK_RAW类型时,可以将protocol参数指定为以下的数值:
● IPPROTO_IP,IPPROTO_ICMP,IPPROTO_TCP和IPPROTO_UDP——分别指定使用IP,ICMP,TCP和UDP协议,这时系统会自动为数据加上IP首部,并且将IP首部中的上层协议字段设置为指定的这些协议名称。但是使用这个套接字接收数据时,系统却不会将IP首部自动去除,需要程序自行分析处理(如果在以后将套接字的属性设置上IP_HDRINCL选项的话,那么发送时系统将不自动添加IP首部,这时需要自己封装数据包)。
● IPPROTO_RAW——系统将数据包直接送到网络访问层发送,程序需要自己添加IP首部以及其他协议首部,并且需要自己计算和填充协议首部中的校验和字段。当使用IPPROTO_RAW协议类型的原始套接字时,这个套接字只能用来发送数据包而无法接收数据包。这是因为所有的IP包都是先递交给系统核心,然后再传输到用户程序,当发送这样一个原始数据包的时候,核心并不知道,也没有这个数据被发送或者连接建立的记录,因此当远端主机回应的时候,系统核心就把这些包给丢弃而不是送到应用程序中。
当套接字被成功创建的时候,函数将返回一个套接字句柄,否则函数将返回INVALID_SOCKET,这时可以继续调用WSAGetLastError函数获取更详细的出错信息。
当不再需要使用套接字的时候,需要使用closesocket函数将它关闭:
invoke closesocket,s
参数s就是创建套接字时返回的套接字句柄。
当出错的时候,大部分WinSock函数的返回值是INVALID_SOCKET或者SOCKET_ERROR,如果需要进一步的出错代码,必须马上调用WSAGetLastError来获取,在以后说明“出错代码”时,指的就是这样得到的出错代码而不是出错函数的返回值。但惟一的例外是WSAStartup,它出错时会直接返回出错代码,因为这时WinSock库尚未装入,程序无法通过调用WSAGetLastError函数来获取出错代码。
3. 设置套接字的工作模式
当一个套接字被创建的时候,它默认工作在阻塞模式下,所以如果不需要改变它的工作模式,可以直接跳过“设置为非阻塞模式”这一步,但是因为Windows程序的工作特点,WinSock强烈建议程序员使用非阻塞模式。有两个函数可以用来改变一个套接字的模式:ioctlsocket函数和WSAAsyncSelect函数。
ioctlsocket函数从BSD UNIX Socket规范中延续过来,它的用法是:
invoke ioctlsocket,s,cmd,argp
参数s指定需要被设置模式的套接字句柄,cmd为命令参数,argp是一个指针,指向一个被cmd命令使用的参数。当cmd被指定为FIONBIO时,函数被用来改变套接字模式,这时如果argp指向的变量值为1,那么套接字的工作模式被设置为非阻塞模式;如果变量中的值为0,那么套接字被设置为阻塞模式。如下面的代码将一个套接字设置为非阻塞模式:
.data
dwArg dd 1
.code
mov dwArg,1
invoke ioctlsocket,hSocket,FIONBIO,addr dwArg
.if eax == SOCKET_ERROR
;发生错误
.endif
但是,这个函数用起来并不方便,因为用这种方法将套接字设置为非阻塞模式后,其结果就是对这个套接字的操作会马上返回,而在操作最终完成以后,函数并不会通过某种机制通知程序操作已经完成,还需要程序去不停地查询操作结果,所以建议使用WinSock接口的WSAAsyncSelect函数来设置工作模式。
WSAAsyncSelect函数是WinSock接口中的特有函数,它仅针对Windows的消息体系工作,所以BSD UNIX Socket中并没有这个函数。这个函数将套接字设置为非阻塞模式,并且打开操作完成后的通知机制,通知消息可以被绑定到某个窗口句柄中,这样程序就不必不停地去查询套接字的操作是否已经完成,而是在窗口过程中等收到通知消息后再进行处理。
上页:第16章 TCP/IP和网络通信 · 16.2 WinSock接口(1) 下页:第16章 TCP/IP和网络通信 · 16.2 WinSock接口(3)