|
|
Tech Note 06: Communications - TCP/IP and IRDA
|
The NetStreams control is used for TCP/IP and IRDA communications. It is included with NS Basic. The information in the document is for the most part copied from the official NetStreams documentation on NewObject's website. For further information, please refer to the official documentation. The information on this page is copyright ZmeY soft and published with their permission.
Background: This control replaces the Winsock control in NS Basic/CE and NS Basic/Desktop. Code written using this control is interoperable between both NS Basic products. It's a newer control than Winsock, and does not have any redistribution restrictions.
The TCP/IP control can be used for all forms of communication under TCP/IP: exchanging data between sockets, http, email and more. It can also be used for IRDa communication.
The methods and properties documented here are a subset of the full capabilities of this control. To see the rest of the features, look at the full documentation from NewObjects. The features not included here will still work well with NS Basic, but are for advanced users.
The methods and properties to manage TCP/IP communications are in three groups. The NetStreams contol itself is used to create a socket. Once established, the actual passing of data back and forth is handled by the same functions as in the File System Control, so it will be familiar to many developers.
TCP/IP communications implement a simple method of sending text back and forth. There are various protocols designed for different functions built on this method, such as http to get web pages, pop for sending mail, etc. Each of these uses a standard port number - see Appendix A for a list of these ports.
This control can also be used for IRDA connections. Please see the NetStreams documentation for more information.
Installation: This control requires that NetStreams.dll and NewObjectsPack1.dll be installed and registered.
Creation:
AddObject "newObjects.net.NSMain", "NS" AddObject "newObjects.utilctls.SFMain", "FS"
| GetHost | Returns a socket address | |
| NewSocket | Creates a socket for an address | |
| Socket Options | ||
| Socket | Initializes the socket object. | |
| Bind | Binds the socket to an address. | |
| Connect | Connects to the listening socket on the remote machine. | |
| Listen | Puts socket in listening state. | |
| Accept | Returns a new connection. | |
| Close | Closes the socket. | |
| LastError | Returns the last error. | |
| Select | Returns info about the socket. | |
| Connection Options | ||
| SetStream | Initializes the connection for reading and writing. | |
| Blocking | Sets whether the connection should wait for response. | |
| ReadText | Reads text from the file (line, specified number of characters or the entire file) | |
| WriteText | Writes text to the File. | |
| Sample Code | ||
| http | Simple program to get a web page. | |
| TCPClient2 | Simple program to implement a client. | |
| TCPServer2 | Simple program to implement a server. | |
| msrv | More complex web server | |
| Appendix | ||
| A | List of Standard Port Numbers |
Sets up the address for the socket.
Details
address - can be a string or an integer (long 4 byte) value. The passed value can be an IP address or a DNS name. If it is an IP the returned address object will be initialized with it. If it is a DNS name a name lookup will be performed which may result in an error if the lookup is unsuccessful.
You can pass for example:
"www.myserver.com" - a DNS name to be resolved
"192.168.1.12" - an IP to be packet in address object
&HC0A8010C - an integer containing IP address to be packed into an address object.
Comments:
Using this method you do not have to care about what you have IP or a DNS name. The returned value will be IP address ready for further usage. IRDA devices lookup requires certain limited resources and is implemented as option query through the Socket object in order to avoid tempting the developers write code that will involve more resources allocation.
Syntax:
Set socket = NS.NewSocket
Creates a new uninitialized Socket object.
bSuccess = socket.Socket
Initialize the socket.
bSuccess - A Boolean value indicating the success or failure of the operation. If False (Failure) is returned the LastError property can be checked for the error text.
bSuccess = socket.Connect(address)
Connects the client to a listening socket on the remote machine with the address specified.
address - is an initialized address.
IP: The address should be initialized with IP address and a port number. It can be obtained from GetHost
IRDA: The address of the device and the ServiceName must be specified. The device address can be obtained by using IRDADeviceInfo object and the SocketStream's GetOption method (See the IRDADeviceInfo on NetObject's site for more information how to do it).
bSuccess = socket.Bind(address)
Binds the socket to the address specified. If the bind is successful True is returned and if not the method will return False and the LastError can be queried for the error text.
address - is an initialized address.
IP protocol: Usually the address should specify IP and a port number. However one of them or both can be zeros. If that case for a zero IP the socket will be bound to all the local machine IP interfaces. If the port is 0 a free port will be selected by the system. It can be queried through the SocketAddress property.
IRDA: The address should be zero, but the ServiceName must be set. That is the service name the other devices will try to connect to when they are looking for the service you provide.
bSuccess = socket.Listen([backlog])
Called after successful binding (see Bind) to an address. If successful this method puts the socket in a listening state and the incoming connection requests are queued for processing. The system queues a number of connection requests. To get one from the queue you must call the Accept method.
bSuccess - A Boolean value indicating the success or failure of the operation. If False (Failure) is returned the LastError property can be checked for the error text.
backlog - optional. Numeric value specifying the size of the queue for the incoming connection requests. The allowed maximum may very from protocol to protocol and and the OS. In most cases applications written with maximum portability in mind should skip it and rely on the defaults in order to avoid the need of adjustment for each OS/protocol.
Set new_socket = socket.Accept
Returns a newly accepted connection. Can be called after the socket is bound (Bind) and is in listening mode (Listen). The returned object is a Socket as well. It is connected to the connecting party (the client) and can be used immediately for data transfer. It can be queried also for the addresses of the other side (PeerAddress) and the local address (SocketAddress).
bSuccess = socket.Close
Closes the socket. By default all sockets are configured to disconnect gracefully when closed
The returned success/failure Boolean value is of very little use as it is not much you can do if an error occurs - if this happens it is a failure in the machine's socket stack.
v = object.Select( [timeout [, r [, w [, e]]]])
Obtains information about the state of the SocketStream object. Rarely needed in blocking mode, but vital in non-blocking mode.
Parameters:
timeout - (default 0). Timeout in seconds specifying how long the system may wait to complete the state obtaining operation.
r, w, e - all are Boolean and True by default (if omitted). They specify which characteristics should be obtained - r - ready to read, w - ready to write, e - error state.
The bits
indicating each of the above states are as follows:
bit 0 - error
bit 1 - write
bit 2 - read
Remarks:
Note that the names of these states were defined long ago and they do not exactly represent the applications for which they are used most frequently today. It is recommended to read the MSDN chapters for them. Here is short explanation of what they indicate in various cases:
read
write
error
v = socket.LastError
Returns the error text for the last error. The value is meaningful only after an error indicated by the return result of one of the object's methods. If called after a successful operation the property may still hold the text for the previous error - do not use this property to determine the success of the last operation.
v = conn.SetStream(socket)
Initializes the connection for reading and writing. The socket must be connected.
object.Blocking = value
v = object.Blocking
Gets/Sets whether the calls should be blocked if the other side is not ready. By default the SocketStreams are created in blocking mode (the property will return True if not set before). If you want to change the SocketStream to non-blocking you should set this property to False, usually right after NewSocket is used to create the socket.
Reads text from the connection.
variable = conn.ReadText(numChars)
positive number - the specified number of characters will be read.
negative numbers have special meaning:
-2 - The entire file (or remaining portion of it depending on the current position) is read as single string
-1 - One line of text is read. The text line is assumed to end with the line separator as it is specified by the textLineSeparator. property.
-3 - Advanced version of the previous (-1). This reads one line but applies more heuristic logic - recommended if you work with text files created by unknown operating system (e.g. the line separator can be different).
Writes the string to the connection.
conn.WriteText string [, option]
string - The string to write.
option - Defines how the text will be written:
0 (default) - just write the string "as is"
1 - Write the string and place "new line" separator after it.
Samples
These samples show the critical TCP/IP code. They are not complete programs. Complete programs are in the NetStreams folder in the NS Basic Samples folder. Note that real world implementations should have stronger error detection and recovery.
'Show how to use http using NetStreams
'This program retrieves a web page
ShowOKButton True 'for CE
AddObject "newObjects.net.NSMain", "NSMain"
AddObject "newObjects.utilctls.SFStream","conn"
Sub CommandButton1_Click
Set addr = nsMain.GetHost(address.Text)
msg.Text = "Connecting to: " & addr.TextAddress
addr.Port = 80 'http
Set socket = nsMain.NewSocket
If Not socket.Socket Then
msg.Text= "Error: " & socket.lastError & vbCrLf & msg.Text
Exit Sub
End If
If Not socket.Connect(addr) Then
msg.Text= "Error: " & socket.lastError & vbCrLf & msg.Text
Exit Sub
End If
conn.SetStream socket
' Post request
Msg.text = "GET " & page.text & " HTTP/1.0" & vbCrLf & "Host: " & address.text & vbCrLf & Msg.text
conn.WriteText "GET " & page.text & " HTTP/1.0" & vbCrLf & "Host: " & address.text & vbCrLf & vbCrLf
Msg.text = "Waiting for response:"& vbCrLf & Msg.text
Do
s = conn.ReadText(-2)
Msg.text = s & vbCrLf & Msg.text
Loop While s <> ""
msg.Text= "Done." & vbCrLf & msg.Text
socket.Close
End Sub
ShowOKButton True 'Set minimize button to close app
'This program acts as a client. It establishes contact with the server,
'sends it a message, then waits for the reply.
Dim socket, state, strm
On Error resume next
AddObject "newObjects.net.NSMain", "NSMain"
If err Then
MsgBox "AXPack1.dll not installed. Please check the ReadMe file's section on ""Install Device Components"" for more information.",,"Winsock"
Bye
End If
On Error Goto 0
AddObject "newObjects.utilctls.SFStream", "strm"
Sub cmdConnect_Click
'First, set up the address
Set addr = nsMain.GetHost(tbIP.Text)
addr.Port = CInt(tbPort.text)
'Create the socket and coonect to it
Set socket = nsMain.NewSocket
If Not socket.Socket Then
tbStatus.Text = "Error: " & socket.lastError & vbCrLf & msg.Text
Exit Sub
End If
If Not socket.Connect(addr) Then
tbStatus.Text = "Error: " & socket.lastError & vbCrLf & msg.Text
Exit Sub
End If
tbStatus.Text = "Connected to " & addr.TextAddress
state = "Connected"
'set up the object that reads and write to the socket
strm.SetStream socket
End Sub
Sub cmdDisconnect_Click
If state = "Connected" Then
socket.close
state = ""
tbStatus.Text = "Connection closed."
End If
End Sub
Sub cmdSend_Click
'Normally would have some checking here
strm.WriteText tbData.text,1
cmdSend.Timer=1000
End Sub
Sub cmdClear_Click
tbReceived.text=""
End Sub
Sub cmdSend_Timer
'This sub checks for incoming data
If state="Connected" Then
line = ""
Do While socket.Select And &H04
'append characters to the buffer as long as they come in
'in the real world, a limit would be a good idea
char = strm.ReadText(1)
line = line & char
Loop
If Len(line)>0 Then
tbReceived.text = line & tbReceived.text
End If
End If
End Sub
Sample 3: TCPServer2
'TCPServer2
'This sample shows how to use the NetStreams control to talk to a device.
'Use this sample with the TcpClient samples that come in NS Basic/CE and
'NS Basic/Palm.
AddObject "newObjects.net.NSMain", "NSMain"
AddObject "newObjects.utilctls.SFStream","conn"
Dim listener, so, state, connection, strm
Sub cmdClear_Click()
lbStatus.Clear
End Sub
Sub cmdClose_Click()
If state = "Connected" Then
connection.close
state = "Listening"
fldState.text = "State is " & state & " " & listener.Select
lbStatus.AddItem "Connection closed."
End If
'listener.close
'Bye
End Sub
Sub Form1_Load()
'Establish the socket connection
'Create a new non-blocking socket
Set listener = nsMain.NewSocket
listener.blocking = False
'Initialize the socket.
If Not listener.Socket("AF_INET","SOCK_STREAM","IPPROTO_TCP") Then
lbStatus.AddItem "Socket Error: " & listener.lastError
Exit Sub
End If
lbStatus.AddItem "Socket Created"
'Prepare the address For binding
Set bindAddress = NSMain.NewAddress
bindAddress.AddressFamilyName = "AF_INET"
bindAddress.Port = 1010
'Try To bind
b = listener.Bind(bindaddress)
If Not b Then
lbStatus.AddItem "Bind Error: " & listener.LastError
Exit Sub
End If
lbStatus.AddItem "Socket Bound"
b=listener.listen
If Not b Then
lbStatus.AddItem "Listen Error: " & listener.LastError
Exit Sub
End If
lbStatus.AddItem "Listening..."
state = "Listening"
lbStatus.Timer=1000 'send an event every second
End Sub
Sub lbStatus_Timer
fldState.text = "State is " & state & " " & listener.Select
Select Case State
Case "Listening":
If listener.Select And &H04 Then
Set connection = listener.Accept
Set strm = CreateObject("newObjects.utilctls.SFStream")
strm.setstream connection
lbStatus.AddItem "Connected"
state = "Connected"
End If
Case "Connected":
line = ""
Do While connection.Select And &H04
char = strm.ReadText(1)
line = line & char
Loop
If Len(line)>0 Then
lbStatus.AddItem line
strm.WriteText "Thank you!",1
End If
End Select
End Sub
Sample 4: http
This program sets up a server that can handle multiple connections. It is pure VBScript. It will run pretty much without change under NS Basic, but check it through before using it.
' Global variables loaded from arguments or configuration
Dim listenPort
listenPort = 8118 ' Defaults
serverPassword = "SrvPass"
sleepTime = 200
maxMsgLen = 4096
' Overridable functions
' On Success: Returns the User Name
' On Failure: Returns empty string
' Currently a single server password is used but this function can be changed to use some database
Function AuthenticateUser(ByVal user, ByVal password, socket, cons)
On Error Resume Next
AuthenticateUser = ""
If Trim(password) = serverPassword And Trim(user) <> "" Then AuthenticateUser = user
End Function
' Implementation
Set sf = CreateObject("newObjects.utilctls.SFMain")
Set Sleeper = CreateObject("newObjects.utilctls.COMSleeper")
Set NSMain = CreateObject("newObjects.net.NSMain")
Set varDict = CreateObject("newObjects.utilctls.varDictionary")
varDict.itemsAssignmentAllowed = True
varDict.enumItems = True
varDict.allowUnnamedValues = True
varDict.allowDuplicateNames = True
Function Max(x,y)
Max = x
If y > x Then Max = y
End Function
Function Min(x, y)
Min = x
If y < x Then Min = y
End Function
Sub Msg(s)
WScript.Echo s
End Sub
' s - Socket, strm - stream to send
Function NewConnection(s)
Dim o
Set o = varDict.CreateNew
o.Add "Socket", s
Dim strm
Set strm = CreateObject("newObjects.utilctls.SFStream")
strm.SetStream s
o.Add "Stream", strm
o.Add "Msg",""
o.Add "Authenticated", False
o.Add "User", ""
SendMessage o, "Enter and press enter."
Set NewConnection = o
End Function
Function GetMessageFromBuff(s)
Dim nCr,r
nCr = Max(InStr(s,vbCr),InStr(s,vbLf))
If nCr > 0 Then
r = Left(s,nCr)
s = Mid(s,nCr + 1)
nCr = Min(InStr(r,vbCr),InStr(r,vbLf)) - 1
GetMessageFromBuff = Left(r,nCr)
Else
GetMessageFromBuff = ""
End If
End Function
Sub ReadIncomingData(src,cons)
On Error Resume Next
Dim t, itm, arr
Dim state
Dim iters
iters = 0
Do
state = src("Socket").Select
If state And &H04 Then
t = src("Stream").ReadText(256)
If Err.Number <> 0 Then
t = ""
Exit Do
End If
If t = "" Then Exit Do
src("Msg") = src("Msg") & t
If iters > 1000 Then
Msg "DEBUG: Over 1000 attempts to read incoming data on the same socket!"
Exit Do
End If
' Size check
If Len(src("Msg")) > maxMsgLen Then
Msg "DEBUG: Message buffer full! Forcing send."
src("Msg") = src("Msg") & vbCrLf
Exit Do
End If
Else
Exit Do
End If
iters = iters + 1
Loop
Dim sTemp
sTemp = src("Msg")
t = GetMessageFromBuff(sTemp)
src("Msg") = sTemp
If t <> "" Then
If Not src("Authenticated") Then
' Try to authenticate
arr = Split(t," ")
If IsArray(arr) And UBound(arr) = 1 Then
Msg "Authenticating " & t & " User=[" & arr(0) & "] Pass=[" & arr(1) & "]"
Dim usr
usr = AuthenticateUser(arr(0),arr(1),src("Socket"), cons)
If usr <> "" Then
src("Authenticated") = True
src("User") = usr
Msg "User " & usr & " from " & src("Socket").PeerAddress.TextAddress & " Authenticated"
t = ""
SendHelp src
BroadcastMessage "#Server: " & src("User") & " logged in. Total users: " & cons.Count, cons
Else
src("Stream").WriteText "Invalid password", 1
Msg "User " & arr(0) & " from " & src("Socket").PeerAddress.TextAddress & " failed to authentcate"
src("Socket").Close
End If
Else
Msg "User from " & src("Socket").PeerAddress.TextAddress & " invalid syntax"
src("Stream").WriteText "Please input: username password", 1
src("Socket").Close
End If
End If
End If
If src("Authenticated") And t <> "" Then
If Left(t,1) = "#" Then
' A command
t = Mid(t,2)
arr = Split(t," ")
If IsArray(arr) And UBound(arr) >= 0 Then
Select Case UCase(arr(0))
Case "U"
t = ""
For I = 1 To cons.Count
t = t & cons(I)("User") & " "
Next
SendMessage src, t
Case "X"
BroadcastMessage "User " & src("User") & " is exiting", cons
src("Socket").Close
Case "L"
t = ""
For I = 1 To cons.Count
t = t & cons(I)("User") & " (" & cons(I)("Socket").PeerAddress.TextAddress & ")" & vbCrLf
Next
SendMessage src, t
Case "P"
If UBound(arr) >= 1 Then
For I = 1 To cons.Count
If cons(I)("User") = Trim(arr(1)) Then
SendMessage cons(I), src("User") & " to " & Mid(t,3)
End If
Next
Else
SendMessage src, "Error: Please spcify username (#P User )"
End If
t = ""
Case Else
SendMessage src, "#Server: Unrecognized command"
SendHelp src
End Select
End If
Else
' Translate the message
Msg "Spreading from " & src("User") & ": " & t
For Each itm in cons
state = itm("Socket").Select
If CBool(state And &H02) And (Not (src Is itm)) And itm("Authenticated") Then
Err.Clear
itm("Stream").WriteText src("User") & ": " & t, 1
If Err.Number <> 0 Then itm("Socket").Close
End If
Next
End If
End If
End Sub
Sub BroadcastMessage(s,cons)
On Error Resume Next
Msg "Broadcasting: " & s
For Each itm in cons
state = itm("Socket").Select
If CBool(state And &H02) And itm("Authenticated") Then
Err.Clear
itm("Stream").WriteText s, 1
If Err.Number <> 0 Then itm("Socket").Close
End If
Next
End Sub
Sub SendMessage(s,m)
On Error Resume Next
Msg "Sending to " & s("User") & ": " & m
state = s("Socket").Select
If state And &H02 Then
Err.Clear
s("Stream").WriteText m, 1
If Err.Number <> 0 Then s("Socket").Close
End If
End Sub
Sub SendHelp(src)
SendMessage src, "#Server: Valid commands are:"
SendMessage src, "#Server: #U - Lists all the current users"
SendMessage src, "#Server: #L - Lists all the current users with their addresses"
SendMessage src, "#Server: #X - Exit"
SendMessage src, "#Server: Any input not starting with # will be sent to all the connected users."
End Sub
Sub Main
Dim b ' Helper variables
Dim listener, bindAddress ' Sockets and dispatchers
Msg "Creating listening socket ..."
Set listener = NSMain.NewSocket
listener.Blocking = False
b = listener.Socket("AF_INET","SOCK_STREAM","IPPROTO_TCP")
If Not b Then
Msg "Cannot initialize the listener: " & listener.LastError
Exit Sub
End If
Msg "Determining the listen address ..."
Set bindAddress = NSMain.NewAddress
bindAddress.AddressFamilyName = "AF_INET"
bindAddress.Port = listenPort
bindAddress.TextAddress = "0.0.0.0"
Msg "Binding to 0.0.0.0:" & listenPort
b = listener.Bind(bindAddress)
If Not b Then
Msg "Cannot listen on port " & listenPort
Msg "Error: " & listener.LastError
Exit Sub
End If
Msg "Starting to listen for connections ..."
b = listener.Listen
If Not b Then
Msg "Cannot listen: " & listener.LastError
Exit Sub
End If
Dim c ' A new connection
Dim cons ' The accepted connections
Dim state, I
Set cons = varDict.CreateNew
Do
' Msg " Checking listener"
state = listener.Select
If state And &H04 Then
' New connection
Set c = listener.Accept
Msg "Accepted new connection from " & c.PeerAddress.TextAddress
cons.Add "", NewConnection(c)
Msg "Connections: " & cons.Count
End If
' Look for errors
' Msg " Looking for errors"
For I = cons.Count To 1 Step -1
If Not cons(I)("Socket").Valid Then
Msg "Disconnecting " & cons(I)("User")
cons.Remove I
Else
On Error Resume Next
state = cons(I)("Socket").Select
If state And &H01 Or Err.Number <> 0 Then
Msg "Disconnecting " & cons(I)("User")
cons.Remove I
End If
On Error GoTo 0
End If
Next
' Cycle through the connections to see if there is anything to read
' Msg " Spread data"
On Error Resume Next
For Each c In cons
Err.Clear
If c("Socket").Valid Then
state = c("Socket").Select
If Err.Number <> 0 Then state = 0 ' Skip the cycle
If state And &H04 Then
' Data can be read
ReadIncomingData c, cons
End If
End If
Next
Sleeper.Sleep(sleepTime)
' Msg "Connections: " & cons.Count
Loop
End Sub
Appendix A: List of Standard Port Numbers
To get the commands supported by each port, consult the relevant RFC documentation. A complete list of RFC documents is at http://www.faqs.org/rfcs/.
tcpmux 1/tcp # TCP port multiplexer (RFC1078)
echo 7/tcp
echo 7/udp
discard 9/tcp sink null
discard 9/udp sink null
systat 11/tcp users
daytime 13/tcp
daytime 13/udp
netstat 15/tcp
qotd 17/tcp quote
chargen 19/tcp ttytst source
chargen 19/udp ttytst source
ftp 21/tcp
telnet 23/tcp
smtp 25/tcp mail
time 37/tcp timserver
time 37/udp timserver
rlp 39/udp resource # resource location
nameserver 42/tcp name # IEN 116
whois 43/tcp nicname
domain 53/tcp nameserver # name-domain server
domain 53/udp nameserver
mtp 57/tcp # deprecated
bootps 67/udp bootp # bootp server
bootpc 68/udp # bootp client
tftp 69/udp
gopher 70/tcp
rje 77/tcp netrjs
finger 79/tcp
link 87/tcp ttylink
webserver 80/tcp
supdup 95/tcp
hostnames 101/tcp hostname # usually from sri-nic
tsap 102/tcp # part of ISODE.
pop2 109/tcp # old pop port
pop 110/tcp pop3 postoffice
sunrpc 111/tcp
sunrpc 111/udp
ident 113/tcp auth tap authentication
sftp 115/tcp
uucp-path 117/tcp
nntp 119/tcp readnews untp # USENET News Transfer Protocol
ntp 123/udp ntpd
imap 143/tcp
snmp 161/udp # network time protocol
snmp-trap 162/udp
smux 199/tcp