在本系列文章的第一部分,我们要学习一些基本的Winsock API函数。这些函数在如初始化Winsock服务、域名解析、异常处理等方面提供了必要的功能。如果你不理解这些函数,你就不能完成一些有趣的事情如:建立socket、发送接收数据等。以下就是这一部分涉及的函数的列表:
- WSAStartup
- WSACleanup
- gethostbyaddr
- gethostbyname
- gethostname
- getservbyname
- getprotobynumber
- getprotobyname
- getservbyport
- inet_addr
- inet_ntoa
- htons
- htonl
- ntohl
- ntohs
为了测试这些函数的功能及使用方法大约需要十来个示例程序,所以我建议最好的方法是建立一个VB项目模板,里面包含所有这些API函数、自定义函数、一些窗体及代码用来运行我们将要进行的测试示例程序。
建立项目
- 所有的示例都是标准的EXE程序,所以选择菜单File | New Project,在新弹出的对话框里选择,点击OK就建立了项目。
- 将项目改名为BasicWinsockAPI。
- 将默认窗体改名为frmMain。
- 选择菜单Project | Add Module建立一个代码模块,该模块用来包含一些API函数的声明及一些自定义函数的定义。
- 将代码模块改名为modWinsock。
插入API声明
打开modWinsock模块的代码,插入以下代码。现在我们将不对这些声明作解释,在以后的文章里有这些函数的详细说明。
Option Explicit
Public Const INADDR_NONE = &HFFFF
Public Const SOCKET_ERROR = -1
Public Const WSABASEERR = 10000
Public Const WSAEFAULT = (WSABASEERR + 14)
Public Const WSAEINVAL = (WSABASEERR + 22)
Public Const WSAEINPROGRESS = (WSABASEERR + 50)
Public Const WSAENETDOWN = (WSABASEERR + 50)
Public Const WSASYSNOTREADY = (WSABASEERR + 91)
Public Const WSAVERNOTSUPPORTED = (WSABASEERR + 92)
Public Const WSANOTINITIALISED = (WSABASEERR + 93)
Public Const WSAHOST_NOT_FOUND = 11001
Public Const WSADESCRIPTION_LEN = 257
Public Const WSASYS_STATUS_LEN = 129
Public Const WSATRY_AGAIN = 11002
Public Const WSANO_RECOVERY = 11003
Public Const WSANO_DATA = 11004
Public Type WSAData
wVersion As Integer
wHighVersion As Integer
szDescription As String * WSADESCRIPTION_LEN
szSystemStatus As String * WSASYS_STATUS_LEN
iMaxSockets As Integer
iMaxUdpDg As Integer
lpVendorInfo As Long
End Type
Public Type HOSTENT
hName As Long
hAliases As Long
hAddrType As Integer
hLength As Integer
hAddrList As Long
End Type
Public Type servent
s_name As Long
s_aliases As Long
s_port As Integer
s_proto As Long
End Type
Public Type protoent
p_name As String 'Official name of the protocol
p_aliases As Long 'Null-terminated array of alternate names
p_proto As Long 'Protocol number, in host byte order
End Type
Public Declare Function WSAStartup _
Lib "ws2_32.dll" (ByVal wVR As Long, lpWSAD As WSAData) As Long
Public Declare Function WSACleanup Lib "ws2_32.dll" () As Long
Public Declare Function gethostbyaddr _
Lib "ws2_32.dll" (addr As Long, ByVal addr_len As Long, _
ByVal addr_type As Long) As Long
Public Declare Function gethostbyname _
Lib "ws2_32.dll" (ByVal host_name As String) As Long
Public Declare Function gethostname _
Lib "ws2_32.dll" (ByVal host_name As String, _
ByVal namelen As Long) As Long
Public Declare Function getservbyname _
Lib "ws2_32.dll" (ByVal serv_name As String, _
ByVal proto As String) As Long
Public Declare Function getprotobynumber _
Lib "ws2_32.dll" (ByVal proto As Long) As Long
Public Declare Function getprotobyname _
Lib "ws2_32.dll" (ByVal proto_name As String) As Long
Public Declare Function getservbyport _
Lib "ws2_32.dll" (ByVal port As Integer, ByVal proto As Long) As Long
Public Declare Function inet_addr _
Lib "ws2_32.dll" (ByVal cp As String) As Long
Public Declare Function inet_ntoa _
Lib "ws2_32.dll" (ByVal inn As Long) As Long
Public Declare Function htons _
Lib "ws2_32.dll" (ByVal hostshort As Integer) As Integer
Public Declare Function htonl _
Lib "ws2_32.dll" (ByVal hostlong As Long) As Long
Public Declare Function ntohl _
Lib "ws2_32.dll" (ByVal netlong As Long) As Long
Public Declare Function ntohs _
Lib "ws2_32.dll" (ByVal netshort As Integer) As Integer
Public Declare Sub RtlMoveMemory _
Lib "kernel32" (hpvDest As Any, _
ByVal hpvSource As Long, _
ByVal cbCopy As Long)
Public Declare Function lstrcpy _
Lib "kernel32" Alias "lstrcpyA" (ByVal lpString1 As String, _
ByVal lpString2 As Long) As Long
Public Declare Function lstrlen _
Lib "kernel32" Alias "lstrlenA" (ByVal lpString As Any) As Long建立辅助函数
Winsock API广泛地使用C/C++的无符号数据类型,如:无符号短整型(unsigned short,2字节)、无符号长整型(unsigned long,4字节)。Visual Basic同样有2或4字节的整型数据类型Integer和Long,但是,不幸的是它们都是有符号的,VB并不直接支持无符号数据类型。
语言 数据类型 长度 范围 C/C++ u_short 2 bytes 0 to 65535 C/C++ u_long 4 bytes 0 to 4294967295 VB Integer 2 bytes -32768 to +32767 VB Long 4 bytes -2147483648 to +2147483647 不过,即然Winsock API接收的参数或返回的值都一些字节,我们就能用VB的数据类型来代替,不过我们需要一些子程序来进行这些值的转换。幸运的是我们并不需要发明什么算法,微软的支持团队已经解决了这个问题。查看一下微软知识库 HOWTO: Convert Between Signed and Unsigned Numbers,我们将要使用这些函数,所以将以下代码插入modWinsock代码模块。Private Const OFFSET_4 = 4294967296#
Private Const MAXINT_4 = 2147483647
Private Const OFFSET_2 = 65536
Private Const MAXINT_2 = 32767
Public Function UnsignedToLong(Value As Double) As Long
'
'The function takes a Double containing a value in the
'range of an unsigned Long and returns a Long that you
'can pass to an API that requires an unsigned Long
'
If Value < 0 Or Value >= OFFSET_4 Then Error 6 ' Overflow
'
If Value <= MAXINT_4 Then
UnsignedToLong = Value
Else
UnsignedToLong = Value - OFFSET_4
End If
'
End Function
Public Function LongToUnsigned(Value As Long) As Double
'
'The function takes an unsigned Long from an API and
'converts it to a Double for display or arithmetic purposes
'
If Value < 0 Then
LongToUnsigned = Value + OFFSET_4
Else
LongToUnsigned = Value
End If
'
End Function
Public Function UnsignedToInteger(Value As Long) As Integer
'
'The function takes a Long containing a value in the range
'of an unsigned Integer and returns an Integer that you
'can pass to an API that requires an unsigned Integer
'
If Value < 0 Or Value >= OFFSET_2 Then Error 6 ' Overflow
'
If Value <= MAXINT_2 Then
UnsignedToInteger = Value
Else
UnsignedToInteger = Value - OFFSET_2
End If
'
End Function
Public Function IntegerToUnsigned(Value As Integer) As Long
'
'The function takes an unsigned Integer from and API and
'converts it to a Long for display or arithmetic purposes
'
If Value < 0 Then
IntegerToUnsigned = Value + OFFSET_2
Else
IntegerToUnsigned = Value
End If
'
End Function一些API函数返回的值为指向字符的指针,这些值并不能直接存入VB中的String型变量。Win32API为我们提供了一个函数lstrcpy让我们将一指针指向的字符串Copy到另一变量内。
Public Declare Function lstrcpy Lib _
"kernel32" Alias "lstrcpyA" (ByVal lpString1 As String, _
ByVal lpString2 As Long) As Long
参数lpString2就是字符串的指针,参数lpString1就是存储字符串的buffer。该buffer必须要有足够的长度来容纳字符串,所以我们在调用该函数前必须知道该字符串的长度。另一个Win32API函数lstrlen帮我们完成此功能——得到一个指针所指向的字符串的长度。Public Declare Function lstrlen Lib "kernel32" Alias "lstrlenA" (ByVal lpString As Any) As Long
现在,我们就可以写一个函数StringFromPointer通过一个指针来得到一个字符串。将以下代码插入modWinsock模块:
Public Function StringFromPointer(ByVal lPointer As Long) As String
'
Dim strTemp As String
Dim lRetVal As Long
'
'prepare the strTemp buffer
strTemp = String$(lstrlen(ByVal lPointer), 0)
'
'copy the string into the strTemp buffer
lRetVal = lstrcpy(ByVal strTemp, ByVal lPointer)
'
'return a string
If lRetVal Then StringFromPointer = strTemp
'
End Function好了,modWinsock模块已经准备好了,我们现在将要修改这个项目的默认窗体。
修改默认窗体
打开frmMain窗体的设计界面,增加两个Button,将这两个按钮的属性改为如下表:
Name Caption Default Cancel cmdGet &Get True cmdExit E&xit True
打开代码设计界面,将以下代码加入Form_Load事件处理过程。Private Sub Form_Load()
'
Dim lngRetVal As Long
Dim strErrorMsg As String
Dim udtWinsockData As WSAData
Dim lngType As Long
Dim lngProtocol As Long
'
'start up winsock service
lngRetVal = WSAStartup(&H101, udtWinsockData)
'
If lngRetVal <> 0 Then
'
'
Select Case lngRetVal
Case WSASYSNOTREADY
strErrorMsg = "The underlying network subsystem is not " & _
"ready for network communication."
Case WSAVERNOTSUPPORTED
strErrorMsg = "The version of Windows Sockets API support " & _
"requested is not provided by this particular " & _
"Windows Sockets implementation."
Case WSAEINVAL
strErrorMsg = "The Windows Sockets version specified by the " & _
"application is not supported by this DLL."
End Select
'
MsgBox strErrorMsg, vbCritical
'
End If
'
End Sub在我们的程序调用任何Winsock API函数前,我们需要初始化Winsock服务。为此我们需要在窗体的Form_Load事件处理过程中调用WSAStartup函数。因为我们在任何用到Winsock的程序中用到这个函数,所以我们将这个函数加入项目模板,这样今后就不用重复拷贝这个函数了。同样,在窗体Unload时也有一些工作要做,在窗体的Form_Unload事件处理过程中我们调用WSACleanup函数来告之系统不再需要使用Winsock服务,ShowErrorMsg用来显示Winsock的错误描述对话框。Private Sub Form_Unload(Cancel As Integer)
Call WSACleanup
End Sub
Private Sub ShowErrorMsg(lngError As Long)
'
Dim strMessage As String
'
Select Case lngError
Case WSANOTINITIALISED
strMessage = "A successful WSAStartup call must occur " & _
"before using this function."
Case WSAENETDOWN
strMessage = "The network subsystem has failed."
Case WSAHOST_NOT_FOUND
strMessage = "Authoritative answer host not found."
Case WSATRY_AGAIN
strMessage = "Nonauthoritative host not found, or server failure."
Case WSANO_RECOVERY
strMessage = "A nonrecoverable error occurred."
Case WSANO_DATA
strMessage = "Valid name, no data record of requested type."
Case WSAEINPROGRESS
strMessage = "A blocking Windows Sockets 1.1 call is in " & _
"progress, or the service provider is still " & _
"processing a callback function."
Case WSAEFAULT
strMessage = "The name parameter is not a valid part of " & _
"the user address space."
Case WSAEINTR
strMessage = "A blocking Windows Socket 1.1 call was " & _
"canceled through WSACancelBlockingCall."
End Select
'
MsgBox strMessage, vbExclamation
'
End Sub哦,还有一个别忘了加上:Private Sub cmdExit_Click()
Unload Me
End Sub所有该项目的工作都已经完成,现在就剩将该项目存为模板了。
将项目存为模板
Visual Basic项目模板就是存储于子目录Template/Projects下的普通项目,该目录位于VB安装目录下。
所以,选择菜单File | Save Project As就可以将我们的项目保存为项目模板,在弹出的对话框里选择VB/Template/Projects 目录并命名为Basic Winsock API.vbp,项目中所有的内容将会保存至此目录。现在,选择菜单File | New Project在弹出的New Project窗口中我们将看到我们刚才新加的模板。