Free Web Hosting Provider - Web Hosting - E-commerce - High Speed Internet - Free Web Page
Search the Web



Visual Basic Online

MARCH, 1996

[Help Magician]

TCP/IP through calls to WINSOCK.DLL

By: Bob Butler
Recently a number of questions have asked about using TCP/IP through calls to WINSOCK.DLL from within Visual BASIC. While VB does not directly support all of the necessary functionality to do this, use of a few Windows API calls makes it very practical. The following code was extracted from the routines that I have been successfully using in various production applications. Everything here is VB3 although I’m hoping to excapsulate a lot of it into VB4 class modules in the near future.

NETWORK.BAS:
This file contains all of the WINSOCK call definitions and various supprting routines needed to utilize them from within VB. The biggest hurdle to using TCP/IP is resolving hostnames. The GETHOSTBYNAME function performs name resolution but does not pass back a simple value. Instead, it returns the address of a structure that it has built in a scratch area of memory. You must copy the data you need from this structure into your own variables before making another winsock call or you risk it being lost. The structure that the return value points to has the format:


	[address of primary name]		4-byte pointer to the full, primary hostname

	[address of aliases]		4-byte pointer

	[??]

	[address of resolutions array]	4-byte pointer

The last field is the one that is most often needed. It is a pointer to an array in memory which has the following format:

	[address of resolved IP]		4-byte pointer

	[address of resolved IP]		4-byte pointer

		

	[0]				4-bytes, value zero

For most hosts the list will only have a single pointer followed by the null entry which signals the end of the list. Systems that have multiple interfaces may have multiple IP addresses assigned and in this case the name resolution will return all of them. In general, few routines process anything but the first returned IP and the code I use does not try any except the first. What we have, therefore, is a pointer from GETHOSTBYNAME which gets us to a pointer to an array of pointers to the IP address that we really want. Since VB does not offer any way to easily handle memory pointers, we need to slip in a couple of API calls to extract the info we need. This can be done, carefully, with LSTRCPY which normally copies strings in memory but has a secondary function of returning the memory address for a variable if the same item is passed for both the source and the destination, coupled with HMEMCPY which copies data from one memory address to another. The RESOLVE_NAME routine given here traverses the various pointers to return the first IP address to a 4-byte string.

All TCP/IP I/O is done over ‘sockets’ which are similar to channel numbers for normal disk I/O in many ways. In both cases you must allocate one (FREEFILE for disk I/O, SOCKET for TCP/IP), open the channel (OPEN vs CONNECT/ACCEPT), perform I/O (READ/WRITE/GET/PUT vs SEND/RECV) and close the channel when done (CLOSE vs CLOSESOCKET). In order to accept a connection from a remote host you need to first define a socket and specify the TCP/IP port number that you want to monitor. Port numbers run from 1 to 4095 (zero is used to indicate ‘any available’ sometimes). The lower numbers are reserved for specific uses. Some of the most common ones are:


	21	FTP server 			70	Gopher server

	23	TELNET server			80	WWW server

To avoid potential conflicts your should design your own servers to use ports in the upper numbers but you can use any port that is not currently in use on the local host. Conversely, to connect to a remote system you need both the IP address and port number for the remote task. The IP address can be found by using a name resolution lookup but the port number must generally be configured. In most cases you are connecting to (1) a standard service like FTP which has a pre-defined port number or (2) your own server process for which you configured the port or have provided an alternate way to find it. In some cases being unable to identify the correct port is an added security measure as in a company with a public FTP server on port 21 and a private one on another port.

NETWORK.BAS
contents:


	Option Explicit

	Type sockaddr_vb	‘ Standard TCP/IP uses sockaddr_in

	  sin_family As Integer      ‘ address family: only AF_INET (TCP/IP INTERNET) is valid

	  sin_port As Integer          ‘ TCP/IP port number

	  sin_addr As String * 4	‘ IP address:  sockaddr_in uses a 4-byte numeric field

	  sin_zero As String * 8	‘ unused - null fill: sockaddr_in uses an array of 8 1-byte fields

	End Type



	Type wsadata_type	‘ WINSOCK implementation parameters

	   wVersion As Integer	‘ this data is all returned by the WSAstartup call

	   wHighVersion As Integer

	   szDescription As String * 257

	   szSystemStatus As String * 129

	   iMaxSockets As Integer

	   iMaxUdpDg As Integer

	   lpVendorInfo As String * 200

	End Type

	Global wsadata As wsadata_type ‘ reserve memory to hold data



	Rem   Socket function prototypes

Note:
The ‘vb’ versions of the calls are so flagged as a reminder that the standard sockaddr_in structure has been replaced by the sockaddr_vb structure - this was done because it is generally simpler to deal with the IP addresses as strings, especially if you want to display them in the standard a.b.c.d format which can easily be done using ASC(MID$(ipstring,x,1)) to get each byte individually

	Declare Function socket Lib "winsock.dll" (ByVal afinet As Integer, ByVal socktype As Integer,

		 ByVal protocol As Integer) As Integer

	Declare Function bindvb Lib "winsock.dll" Alias "bind" (ByVal s As Integer,

		addr As sockaddr_vb, ByVal namelen As Integer) As Integer

	Declare Function connectvb Lib "winsock.dll" Alias "connect" (ByVal sock As Integer, 

		sockstruct As sockaddr_vb, ByVal structlen As Integer) As Integer

	Declare Function send Lib "winsock.dll" (ByVal sock As Integer, ByVal msg As String,

		ByVal msglen As Integer, ByVal flag As Integer) As Integer

	Declare Function recv Lib "winsock.dll" (ByVal sock As Integer, ByVal msg As String,

		ByVal msglen As Integer, ByVal flag As Integer) As Integer

	Declare Function htonl Lib "winsock.dll" (ByVal a As Long) As Long

	Declare Function ntohl Lib "winsock.dll" (ByVal a As Long) As Long

	Declare Function htons Lib "winsock.dll" (ByVal a As Integer) As Integer

	Declare Function ntohs Lib "winsock.dll" (ByVal a As Integer) As Integer

	Declare Function gethostbyname Lib "winsock.dll" (ByVal hn As String) As Long

	Declare Function WSAgetlasterror Lib "winsock.dll" () As Long

	Declare Function gethostname Lib "winsock.dll" (ByVal hn As String,

		ByVal nbytes As Integer) As Long

	Declare Function closesocket Lib "winsock.dll" (ByVal sn As Integer) As Integer

	Declare Function getsocknamevb Lib "winsock.dll" Alias "getsockname" (ByVal sn As Integer,

		 saddr As sockaddr_vb, salen As Integer) As Integer

	Declare Function listen Lib "winsock.dll" (ByVal sn As Integer, 

		ByVal blog As Integer) As Integer

	Declare Function acceptvb Lib "winsock.dll" Alias "accept" (ByVal sn As Integer, 

		saddr As sockaddr_vb, namelen As Integer) As Integer



	Rem   Microsoft Windows Extension function prototypes

	Declare Function WSAstartup Lib "winsock.dll" (ByVal a As Integer,

		b As wsadata_type) As Integer

	Declare Function WSAcleanup Lib "winsock.dll" () As Integer



	‘ Functions needed to manipulate memory directly when resolving hostnames

	Declare Function lstrcpy Lib "Kernel" (x1 As Any, x2 As Any) As Long ' used to get addresses

	Declare Sub hmemcpy Lib "Kernel" (dst As Long, src As Long, ByVal nbytes As Long)



	Rem   WINSOCK constants

	Global Const SOCK_STREAM = 1

	Global Const AF_INET = 2

	Global Const MSG_PEEK = 2



	' Windows Sockets definitions of regular Microsoft C error constants

	Global Const WSAEINTR = 10004

	Global Const WSAEBADF = 10009

	Global Const WSAEACCES = 10013

	Global Const WSAEFAULT = 10014

	Global Const WSAEINVAL = 10022

	Global Const WSAEMFILE = 10024

	' Windows Sockets definitions of regular Berkeley error constants

	Global Const WSAEWOULDBLOCK = 10035

	Global Const WSAEINPROGRESS = 10036

	Global Const WSAEALREADY = 10037

	Global Const WSAENOTSOCK = 10038

	Global Const WSAEDESTADDRREQ = 10039

	Global Const WSAEMSGSIZE = 10040

	Global Const WSAEPROTOTYPE = 10041

	Global Const WSAENOPROTOOPT = 10042

	Global Const WSAEPROTONOSUPPORT = 10043

	Global Const WSAESOCKTNOSUPPORT = 10044

	Global Const WSAEOPNOTSUPP = 10045

	Global Const WSAEPFNOSUPPORT = 10046

	Global Const WSAEAFNOSUPPORT = 10047

	Global Const WSAEADDRINUSE = 10048

	Global Const WSAEADDRNOTAVAIL = 10049

	Global Const WSAENETDOWN = 10050

	Global Const WSAENETUNREACH = 10051

	Global Const WSAENETRESET = 10052

	Global Const WSAECONNABORTED = 10053

	Global Const WSAECONNRESET = 10054

	Global Const WSAENOBUFS = 10055

	Global Const WSAEISCONN = 10056

	Global Const WSAENOTCONN = 10057

	Global Const WSAESHUTDOWN = 10058

	Global Const WSAETOOMANYREFS = 10059

	Global Const WSAETIMEDOUT = 10060

	Global Const WSAECONNREFUSED = 10061

	Global Const WSAELOOP = 10062

	Global Const WSAENAMETOOLONG = 10063

	Global Const WSAEHOSTDOWN = 10064

	Global Const WSAEHOSTUNREACH = 10065

	Global Const WSAENOTEMPTY = 10066

	Global Const WSAEPROCLIM = 10067

	Global Const WSAEUSERS = 10068

	Global Const WSAEDQUOT = 10069

	Global Const WSAESTALE = 10070

	Global Const WSAEREMOTE = 10071

	' Extended Windows Sockets error constant definitions

	Global Const WSASYSNOTREADY = 10091

	Global Const WSAVERNOTSUPPORTED = 10092

	Global Const WSANOTINITIALISED = 10093

	Global Const WSAHOST_NOT_FOUND = 11001

	Global Const WSATRY_AGAIN = 11002

	Global Const WSANO_RECOVERY = 11003

	Global Const WSANO_DATA = 11004

	Global Const WSANO_ADDRESS = 11004



	Function initnet () As Long

	initnet = WSAstartup(257, wsadata) ‘ initialize network - zero is successful return

	End Function

Note:
The following routines get the addresses of variables for later use with hmemcpy. They are type-specific to minimize GPF possibilities and because of the need to use the BYVAL keyword on strings in order to prevent getting the address of the converted, null-delimited string

	Function addressofinteger (x As Integer) As Long

	addressofinteger = lstrcpy(x, x) ‘ return memory address of integer variable

	End Function



	Function addressoflong (x As Long) As Long

	addressoflong = lstrcpy(x, x)’ return memory address of  long variable

	End Function



	Function addressofstring (x As String) As Long

	addressofstring = lstrcpy(ByVal x, ByVal x)’ return memory address of string

	End Function



	Function resolve_name (inname As String, ipaddr As String) As Long

	‘ input hostname and return 4-byte string with IP address in ipaddr variable

	‘ return value zero is success, otherwise error code

	Dim lngRval                   ‘ return value from gethostbyname call

	Dim hn As String * 128  ' temporary hostname area

	Dim hostent As String    ' work area for hostent structure

	Dim haddrlist As Long   ' pointer to list of addresses

	Dim lngipptr As Long    ‘ address of  ip address

	Dim lngAddrHostent As Long	‘ address of hostent structure

	Dim lngAddrPointers As Long	‘ address of pointers

	Dim lngAddrhaddrlist As Long	‘ address of address list

	Dim lngAddripaddr As Long	‘ address of return string

	Dim lngAddripptr As Long	‘ address of ipptr



	hn = inname + Chr$(0) 	' null-delimit hostname

	hostent = String$(16, 0)	' null-fill hostent work area (makes VB allocate space)

	ipaddr = String$(4, 0)	' null fill ip addr

	lngRval = gethostbyname(hn)	 ' call standard winsock routine

	lngAddrHostent = addressofstring(hostent)	‘ get memory address of workspace

	lngAddrPointers = lngAddrHostent + 12	‘ calculate address of list of pointers

	lngAddrhaddrlist = addressoflong(haddrlist)’ get memory address haddrlist

	lngAddripaddr = addressofstring(ipaddr)	‘ get memory address of return string

	lngAddripptr = addressoflong(lngipptr)	‘ get memory address of ipptr

	If lngRval Then  ‘ gethostbyname returned address of a hostent structure

	  ' copy structure from winsock memory to local string

	  Call hmemcpy(ByVal lngAddrHostent, ByVal lngRval, 16)

	  ' copy address of list of pointers into haddrlist variable

	  Call hmemcpy(ByVal lngAddrhaddrlist, ByVal lngAddrPointers, 4)

	  ' copy value of first pointer into ipptr variable

	  Call hmemcpy(ByVal lngAddripptr, ByVal haddrlist, 4)

	  ' copy first IP address into return string

	  Call hmemcpy(ByVal lngAddripaddr, ByVal lngipptr, 4)

	  resolve_name = 0 ' success!

	Else

	  ipaddr = String$(4, 0)	 ' failed, return null IP and error

	  resolve_name = wsagetlasterror()

	End If

	End Function



Now let’s look at the various functions:

INITNET


	Function: Initialize network - must be done once by every task doing network functions

	Input: none

	Output: zero if successful, error otherwise

	Example:

		If initnet() Then

		  MsgBox “Unable to initialize network”

		  End

		End If

Note:
This is a wrapper around the WSAstartup function to make it a bit cleaner to use.

WSACLEANUP


	Function: close down network access for task - should be called before termination

	Input: none

	Output: zero if successful, generally ignored

	Example:

		Sub Form_Unload()

		Dim X as Integer

		X=WSAcleanup()

		End Sub



SOCKET


	Declare Function socket Lib "winsock.dll" (ByVal afinet As Integer, ByVal socktype As Integer,

		 ByVal protocol As Integer) As Integer

	Function: allocates a TCP/IP socket

	Input:	Protocol Family (must be AF_INET)

		Socket type:	SOCK_STREAM	reliable TCP/IP service

				SOCK_DGRAM		unreliable datagram service

				SOCK_RAW		TCP/IP control packets

		Protocol: use zero to default to standard protocol based on socket type.

	Output: socket number, zero or negative is error

	Example:

		Dim intSocket As Integer

		intSocket=Socket(AF_INET,SOCK_STREAM,0)

		If intSocket=0 then MsgBox “Unable to allocate socket”

	

BINDVB (BIND)


	Declare Function bindvb Lib "winsock.dll" Alias "bind" (ByVal s As Integer,

		addr As sockaddr_vb, ByVal namelen As Integer) As Integer

	Function: bind socket to local port as setup for LISTEN call

	Input:	Socket		Socket number as assigned by SOCKET call

		Socket structure	Specify IP address to accept connections from and/or

				TCP/IP port to accept connections on.  Use zero for the IP

				to allow connections from any host and zero for the port

				to get the next available local port number.

		Structure length	Bytecount for structure argument

	Output:	zero if successful, otherwise error

	Example:

		Dim intSocket As Integer

		Dim saiSocket As Integer

		intSocket=Socket(AF_INET,SOCK_STREAM,0)

		saiSocket.sin_family=AF_INET

		saiSocket.sin_addr=String$(4,0)

		saiSocket.sin_port=0

		saiSocket.sin_zero=String$(8,0)

		If BindVB(intSocket,saiSocket,Len(saiSocket)) Then MsgBox “Bind failed!”



GETHOSTNAME


	Declare Function gethostname Lib "winsock.dll" (ByVal hn As String,

		ByVal nbytes As Integer) As Long

	Function: retrieves the local hostname

	Input:	Hostname	String to receive name (pre-allocate room with spaces)

		Buffer size	Maximum bytes to return to Hostname

	Output:	Null-delimited hostname

	Example:

		Dim strMyName As String

		Dim lngTrash As Long

		strMyName=Space$(32)

		lngTrash=GetHostName(strMyName,Len(strMyName))





HtoNs, HtoNl, NtoHs, NtoHl

	Declare Function htonl Lib "winsock.dll" (ByVal a As Long) As Long

	Declare Function ntohl Lib "winsock.dll" (ByVal a As Long) As Long

	Declare Function htons Lib "winsock.dll" (ByVal a As Integer) As Integer

	Declare Function ntohs Lib "winsock.dll" (ByVal a As Integer) As Integer

	Function: convert integer or long data types from host-format to network-format

		or from network-format to host-format.

	Input:	Value	Numeric value to be converted

	Output:	Value	Converted value

	Examples:

		saiSocket.sin_port=HtoNs(21)

		intPort=NtoHs(saiSocket.sin_port)

TCP/IP structires and packets store all numeric data with the most significant byte first while many hosts, including MSDOS PCs, store values in memory with the least significant byte first. Using the Host-To-Network (Short or Long) and Network-To-Host (Short or Long) functions ensures that your code will work across platforms. In the first line of the example I assign 21 to the TCP/IP port. If I had done the assignement directly with a statement like “saiSocket.sin_port=21” it would have assigned the two bytes of the integer 21 in PC format (<21><0>) instead of TCP/IP format (<0><21>). The resulting port would have been 5376 (21 *256 + 0) instead of 21 and I’d start getting some strange behavior.

WSAGETLASTERROR


	Declare Function WSAgetlasterror Lib "winsock.dll" () As Long

	Function: return the error code from the last WinSock call

	Input:	none

	Output:	Error code, or zero if no error occurred

	Example:

		If  WSAGetLastError()=WSAECONNRESET Then 

			MsgBox “Connection Reset by Remote System”

		End If



CONNECTVB (CONNECT)


	Declare Function connectvb Lib "winsock.dll" Alias "connect" (ByVal sock As Integer, 

		sockstruct As sockaddr_vb, ByVal structlen As Integer) As Integer

	Function: opens TCP/IP connection to remote process

	Input:	Socket		Socket number as returned by SOCKET call

		Socket structure	Structure specifying remote IP and port

		Structure Size	Byte size of structure

	Output:	zero if successful, otherwise error

	Example:

		Function ConnectMeTo(intSocket As Integer,strHostName As String) As Long

		Dim saiSocket As Sockaddr_vb

		Dim strRemoteIP As String

		If Resolve_Name(“www.microsoft.com”,strRemoteIP) =0 Then

		  saiSocket.sin_family=AF_INET

		  saiSocket.sin_port=HtoNs(80)

		  saiSocket.sin_zero=String$(8,0)

		  saiSocket.sin_addr=strRemoteIP

		  ConnectMeTo=ConnectVB(intSocket,saiSocket,Len(saiSocket))

		Else

		  ConnectMeTo=WSAGetLastError()

		End If

		Exit Function

GETSOCKNAMEVB (GETSOCKNAME)


	Declare Function getsocknamevb Lib "winsock.dll" Alias "getsockname" (ByVal sn As Integer,

		 saddr As sockaddr_vb, salen As Integer) As Integer

	Function: fill in socket structure with all current information for a bound or connected socket

	Input:	Socket	  Socket number from SOCKET call

		Structure  Socket data structure

		Length	  Size of structure

	Output:	Socket data structure filled in, return value of zero if successful, otherwise error

	Example:

		Function GetIPFromSocket(intSocket As Integer) As String

		Dim saiSocket as Sockaddr_VB

		Dim intSize As Integer

		intSize=len(saiSocket)

		If GeSockNameVB(intSocket,saiSocket,intSize) Then

			GetIPFromSocket=“”

		Else

			GetIPFromSocket=saiSocket.sin_addr

		End If

SEND


	Declare Function send Lib "winsock.dll" (ByVal sock As Integer, ByVal msg As String,

		ByVal msglen As Integer, ByVal flag As Integer) As Integer

	Function: transmit data to remote host

	Input:	Socket	Socket number as returned by SOCKET function

		Buffer	Data to send

		Bytes	Number of bytes to send

		Flag	Special handling flag

	Output:	Number of bytes sent

	Example:

		Function SendIt(intSocket As Integer, strBuff As String) As Long

		SendIt=Send(intSocket,strBuff,Len(strBuff),0)

		Exit Function

	Note: the special handling flag is normally zero for no special handling.  There is a MSG_OOB

	flag which can be used to send ‘Out-Of-Band’ data for priority handling but this is rarely, if

	ever, needed.





RECV

	Declare Function recv Lib "winsock.dll" (ByVal sock As Integer, ByVal msg As String,

		ByVal msglen As Integer, ByVal flag As Integer) As Integer

	Function: receive data from socket

	Input:	Socket	Socket number from SOCKET call

		Buffer	Receive buffer

		Bytes	Maximum number of bytes to read

		Flag	Special handling flag

	Output: number of bytes received

	Example:

		Function GetData(intSocket as Integer) As String

		Dim strBuff As String * 2048

		Dim intBytes As Integer

		intBytes=Recv(intSocket,strBuff,Len(strBuff),0)

		if intBytes<1 then

			GetData=“”

		Else

			GetData=Left$(strBuff,intBytes)

		End If

	Note: The MSG_PEEK option can be used in the special handling flag to return a copy

	of the data waiting to be received without removing it from the TCP/IP buffer.  Another

	call to RECV will get the same information.  

LISTEN

	Declare Function listen Lib "winsock.dll" (ByVal sn As Integer, 

		ByVal blog As Integer) As Integer

	Function: prepare socket to accept connections from remote hosts

	Input:	Socket	Socket number from SOCKET call

		Backlog	Number of connection requests to queue before refusing

	Output:	zero if successful, otherwise error

	Example:

		If Listen(intSocket,2) Then MsgBox “Unable to listen for connections”

ACCEPTVB (ACCEPT)

	Declare Function acceptvb Lib "winsock.dll" Alias "accept" (ByVal sn As Integer, 

		saddr As sockaddr_vb, namelen As Integer) As Integer

	Function: accept connection from remote system

	Input:	Socket	   Socket number from SOCKET call

		Structure   Socket data structure

		Length	   Size of socket data structure

	Output:	Socket number for new connection

	Example:

		Function AcceptRequest(intSocket As Integer) As Integer

		Dim intSize As Integer

		Dim saiSocket As Sockaddr_VB

		intSize=Len(saiSocket)

		AcceptRequest=AcceptVB(intSocket,saiSocket,intSize)

		Exit Function

	Note: the socket that is input to the Accept function must have been bound using the BIND

	call and been the target of a LISTEN call.  The output socket is a completely new socket that

	has been connected to the remote system.  The original socket is still usable for additional

	ACCEPT calls.





CLOSESOCKET

	Declare Function closesocket Lib "winsock.dll" (ByVal sn As Integer) As Integer

	Function: close a socket

	Input:	Socket number

	Output:	zero if successful, otherwise error

	Example:

		If CloseSocket(intSocket) Then MsgBox “Error closing socket”





Now we can do a simple application to get a copy of Microsoft’s WWW Home Page:


Option Explicit

Dim intSocket As Integer



Sub Form_Load()

Dim saiSocket As sockaddr_vb

Dim intSize As Integer

Dim strBuff As String

Dim intChannel As Integer

Dim strRemoteIP As String

If initnet() Then MsgBox “Unable to initialize”:End

intSocket=Socket(AF_INET,SOCK_STREAM,0)

If intSocket<1 Then MsgBox “Socket call failed”:End

If  resolve_name(“www.microsoft.com”,strRemoteIP) Then MsgBox “Name Lookup error”:End

saiSocket.sin_family=AF_INET

saiSocket.sin_addr=strRemoteIP

saiSocket.sin_port=htons(80)

saiSocket.sin_zero=String$(8,0)

If connectvb(intSocket,saiSocket,Len(saiSocket)) Then MsgBox “Connect failure”:End

strBuff=“GET /” & Chr$(13) & Chr$(10) ‘ WWW syntax for getting top-level

If send(intSocket,strBuff,Len(strBuff),0)<>Len(strBuff) Then MsgBox “Send failure”:End

intChannel=FreeFile

Open “MSHOME.HTM” For Output As #intChannel

strBuff=String$(2048,0)

Do While True

	intSize=recv(intSocket,strBuff,Len(strBuff),0)

	If IntSize<1 Then Exit Do

	Print #intChannel,Left$(strBuff,intSize);

Loop

intSize=LOF(intChannel)

Close #intChannel

If CloseSocket(intSocket) Then MsgBox “Socket close error”

MsgBox “Received “ & Format$(intSize) & “ bytes”

End

End Sub



Sub Form_Unload(Cancel As Integer)

Dim x As Integer

If intSocket>0 then x=CloseSocket(intSocket)

x=WSACleanUp()

End Sub



The following “server” accepts connections and returns the current date and time (warning: it has no built-in way out!):

Option Explicit

Dim intSocket As Integer



Sub Form_Load()

Dim saiSocket As sockaddr_vb

Dim intSize As Integer

Dim strBuff As String

Dim intChannel As Integer

Dim intNewSocket As Integer

If initnet() Then MsgBox “Unable to initialize”:End

intSocket=Socket(AF_INET,SOCK_STREAM,0)

If intSocket<1 Then MsgBox “Socket call failed”:End

saiSocket.sin_family=AF_INET

saiSocket.sin_addr=String$(4,0) ‘ accept from any host

saiSocket.sin_port=htons(2121) ‘ wait for connections to port 2121

saiSocket.sin_zero=String$(8,0)

If bindvb(intSocket,saiSocket,Len(saiSocket)) Then MsgBox “Bind failure”:End

if listen(intSocket,5) Then MsgBox “Listen failure”:End

Do While True

  intSize=Len(saiSocket)

  intNewSocket=acceptvb(intSocket,saiSocket,intSize)

  if intNewSocket<1 Then MsgBox “Accept failed”:End

  strBuff=format$(now,”yymmddhhmmss”)

   If send(intNewSocket,strBuff,Len(strBuff),0)<>Len(strBuff) Then MsgBox “Send failure”:End

   If CloseSocket(intNewSocket) Then MsgBox “Error closing new socket”

Loop

If CloseSocket(intSocket) Then MsgBox “Socket close error”

End

End Sub



Sub Form_Unload(Cancel As Integer)

Dim x As Integer

If intSocket>0 then x=CloseSocket(intSocket)

x=WSACleanUp()

End Sub



The last topic that needs to be mentioned is the use of async socket calls. All of the preceeding information has dealt strictly with synchronous operation -- when you issue an ACCEPT call your task will hang until a connection request is received. When you use the RECV call your task will hang until data is received or the remote task closes the socket. For most client applications these are not significant drawbacks. I have written FTP, gopher and WWW clients which perform fine using only the synchronous versions of the calls. For server applications, however, it is hardly practical since you can only service one client at a time.

Here we run into another limitation of Visual Basic. In order to be able to process accept and recv calls asynchronously we need to be able to either (1) check first to see if a connection request or data is waiting or (2) be notified when one of these events occurs. Both of these methods are theoretically possible using standard WinSock calls and both are difficult from Visual Basic.

Method 1:
Checking to see if any requests or data are ready One of the standard calls is IOCTLSOCKET (I/O Control for Socket) which has an FIONBIO (File I/O: Non-blocking I/O) parameter. By using this call the selected socket is set for non-blocking mode so that an ACCEPT or RECV request will fail with an error if it can not return immediately with valid data. I’ve used this call quite successfully in C under other operating systems but have yet to find a way to get it to work on a PC with WinSock. The definitions all seem to be there, but every variation I try returns an error on the IOCTLSocket call.

Method 2:
Receiving notifications when a socket is active For this we need to add another function definition:


WSAASYNCSELECT

	Declare Function WSAAsyncSelect Lib "winsock.dll" (ByVal s As Integer,

		ByVal hwnd As Integer, ByVal wmsg As Integer, ByVal flgs As Long) As Integer

	Function: puts a socket in asynch mode

	Input:	Socket	  Socket number from SOCKET call

		Hwnd	  HWND property for form or control to receive message

		Message	  Type of message sent

		Flags	  Conditions on which to send message

	Output: zero if successful, otherwise error

	Example:

		If WSAAsyncSelect(intSocket,txtSocket.hwnd,WM_LBUTTONDOWN,63) Then

			MsgBox “Unable to set Asynch mode”

In the above example “txtSocket” is the name of a text box on the current form. The Visible property for this box is set to False so that I know the user can not see it or affect it in any way. The message value is also defined in my full NETWORK.BAS file as:

	Global Const WM_LBUTTONDOWN = &H201

The Flags value is a summation of values for various conditions that can trigger activity for a socket including data received, connection request received, socket closed, etc. I generally lump them all together because only one or two conditions are possible for a given socket at any given time so when I get the message it is usually easy to handle whatever caused it. For example, if I expect to receive data but the sender closes the socket instead I will get an error return from the RECV call that I try and that is less work than setting up different messages for different activities. When I have two or more sockets that need to be handled asynchronously I define a text box for each, always invisible, and use the same message code. To save on controls you could also use the same control but a unique message for each socket (e.g. WM_RBUTTONDOWN or WM_LBUTTONUP, etc). When socket activity is triggered the WinSock routines send the given message to the specified control. In the example given, the txtSocket_MouseDown event is triggered with BUTTON set to 1 (Left). The MouseDown event effectively becomes a SocketActive event and my code handles the activity as it happens. By the way, I use text boxes only because they were the first control I tried that had a HWND property. It is possible to obtain the HWND for a label, but that adds extra API calls and obfuscates things even more.

Click here to go back to the March '96 Article Index