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.
AddObject "newObjects.net.NSMain", "NS" AddObject "newObjects.utilctls.SFMain", "FS"
|GetHost||Returns a socket address|
|NewSocket||Creates a socket for an address|
|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.|
|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.|
|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|
|A||List of Standard Port Numbers|
Sets up the address for the socket.
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.
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.
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.
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.
indicating each of the above states are as follows:
bit 0 - error
bit 1 - write
bit 2 - read
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:
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.
SamplesThese 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: httpThis 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 NumbersTo 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