Page 1 of 1

Network terminology question, esp re Telnet

Posted: Fri Mar 06, 2015 1:47 am
by ozzie
In my PB program I currently support Telnet control of the program and also sending Telnet messages to other systems. I now need to add UDP support, primarily to send OSC (Open Sound Control) messages to a digital mixer. I initially started building completely separate structures, modules, etc for the UDP handling, but I'm wondering if it would be better to combine this with the existing Telnet code (obviously with some changes), and replace the term 'Telnet' with 'Network' in my program and documentation. I would need the user to select the network protocol (TCP or UDP) but apart from that it seems to me that everything else could be common.

Any thoughts on this? I note that the PB documentation itself doesn't mention 'Telnet' apart from noting that Telnet uses port 23. PB uses 'Network' to cover both TCP and UDP, so i think I should do the same.

Re: Network terminology question, esp re Telnet

Posted: Sun Mar 08, 2015 11:04 pm
by RichAlgeni
Telnet is simply a type of application protocol that runs on networks. All telnets use a network, but not all networks use telnet. Telnet at its most basic, utilizes control characters (ASCII 0-31 and/or ASCII 127-255) to add color, line control, page control, blinking and other attributes to terminal sessions. You can turn on a telnet client in Windows (Control Panel, Programs and Features, Turn Windows Features on or off).

What I did was that I wrote a generic TCP Network Reader and Writer procedures. I did the same for UDP. I then pass data to and from these procedures. I put these procedures in a PBI file, then I can include them as needed. For instance: Note that these are Windows ONLY procedures.

Code: Select all

; ------------------------------------------------------------
; Program name: network_procs.pbi
; Written by:   Rich Algeni, Jr.
; Date written: 02/17/2011
;
; Purpose: to include network procedures into PB programs
;
; (c) Copyright 2015, Orion Tek Solutions, All rights reserved.
; ------------------------------------------------------------

; **********************************************************************************
; initialization of networking variables
; **********************************************************************************

Procedure.i InitializeNetwork()

    Protected result.i

; add this program name and version to the list of includes the main program is running

    AddElement(programList())
    programList() = "network_procs.pbi"

    AddElement(progVersions())
    progVersions() = "2.0.02b"

; define the structure we will use for connect and send

    Structure connectSendData
        csdHost.s
        csdPort.i
        csdText.s
        csdLeng.i
    EndStructure

; make sure we can initialize the network

    result = InitNetwork()

    Global hostName.s = Hostname()

    ProcedureReturn result

EndProcedure

; **********************************************************************************
; this procedure set the TCP receive buffer size, and keepalive
; **********************************************************************************

Procedure.i SetTCPBufferSize(connectNumber.i, bufferSize.i)

    Protected result.l       = 0; winsock utlizes a 32 bit integer only!
    Protected keepAlive.i    = 1
    Protected socketHandle.i = 0

    socketHandle = ConnectionID(connectNumber)

    result = setsockopt_(socketHandle, #SOL_SOCKET, #SO_RCVBUF,    @bufferSize, SizeOf(bufferSize))

    result = setsockopt_(socketHandle, #SOL_SOCKET, #SO_KEEPALIVE, @keepAlive,  SizeOf(keepAlive))

    ProcedureReturn result

EndProcedure

; **********************************************************************************
; process to open a client socket connection, and set the receive buffer size
; **********************************************************************************

ProcedureDLL.i OpenClientSocket(*socketNumber, *serverName, serverPortNumber.i, setBufferRecvSize.i, networkMode.i, openTimeout.i, *bindToIP, bindToPort.i)

    Protected result.i
    Protected logText.s
    Protected socketNumber.i
    Protected bindToIP.s     = PeekS(*bindToIP)
    Protected serverName.s   = PeekS(*serverName)

; attempt to open the socket here, then poke in the result

    socketNumber = OpenNetworkConnection(serverName, serverPortNumber, networkMode, openTimeout, bindToIP, bindToPort.i)
    PokeI(*socketNumber, socketNumber)

; if the socket opened successfully, set the socket number, then the receive buffer size

    If socketNumber > 0
        If networkMode = #PB_Network_TCP
            result = SetTCPBufferSize(socketNumber, setBufferRecvSize)
            If result <> 0
                logText = "OpenClientSocket() > SetTCPBufferSize() returned " + Str(result)
                WriteToLog(@logText)
            EndIf
        EndIf
    Else
        logText = "OpenClientSocket() > Unable to open socket to " + serverName + " on port " + serverPortNumber
        WriteToLog(@logText)
    EndIf

; this procedure returns the socket number if the socket is open, the socket number is also Poked into *socketNumber

    ProcedureReturn socketNumber

EndProcedure

; **********************************************************************************
; this procedure writes binary data to a network connection
; **********************************************************************************

Procedure.i NetworkWrite(connectNumber.i, *memoryLoc, lenSendText.i, *netSource, debugThis.i)

    Protected totalSent.i = 0
    Protected errNumber.i = 0
    Protected errorText.s = ""
    Protected lenToSend.i
    Protected result.i
    Protected length.i

    If lenSendText < 1 Or *memoryLoc < 1; just in case
        ProcedureReturn -999
    EndIf

; try to send the data, loop until all done, or error encountered

    While lenSendText  > 0
        If lenSendText > maxSendSize; make sure our packet size is not too large
            lenToSend  = maxSendSize
        Else
            lenToSend  = lenSendText
        EndIf

        result = SendNetworkData(connectNumber, *memoryLoc + totalSent, lenToSend)

; if we've received an error, set the condition to get out of the loop

        If result < 0
            totalSent   = result
            lenSendText = 0
            errNumber   = WSAGetLastError_()
        Else
            totalSent   = totalSent   + result
            lenSendText = lenSendText - result
            If lenSendText
                Delay(packetDelay)
            EndIf
        EndIf
    Wend

; send the data we have transmitted to the debugger listing, or if needed the error

    If debugThis
        DebugString(*memoryLoc, totalSent, *netSource, #debugOut, errNumber, @errorText)
    EndIf

    ProcedureReturn totalSent

EndProcedure

; **********************************************************************************
; this procedure reads binary data from a network connection in one read attempt
; **********************************************************************************

Procedure.i NetworkRead(connectNumber.i, *memoryLoc, memLength.i, *netSource, debugThis.i, socketTimeout.i, maxReadTrys.i)

    Protected socketHandle.i = 0
    Protected attemptCount.i = 0
    Protected amountRead.i   = 0
    Protected errNumber.i    = 0
    Protected length.i       = 0
    Protected result.l       = 0; winsock utlizes a 32 bit integer only!

    socketHandle = ConnectionID(connectNumber)

; loop until we have received data, or our timeout has been exceeded

    Repeat
        result = ioctlsocket_(socketHandle, #FIONREAD, @length)

; get out of the loop if ShutDownNow is true

        If ShutDownNow
            debugThis = #False
            Break
        EndIf

; if we encountered a socket error, we need to exit the loop

        If result <> 0
            errNumber  = WSAGetLastError_()
            amountRead = -1
            Break
        EndIf

; if nothing has been received, increment the attempt counter and sleep, else get out of the loop

        If length = 0
            attemptCount = attemptCount + 1
            If attemptCount > maxReadTrys
                Break
            Else
                Delay(socketTimeout)
            EndIf

; else get the data in the network receive buffer

        Else
            amountRead = ReceiveNetworkData(connectNumber, *memoryLoc, memLength)

            Break
        EndIf
    ForEver

; send the data we have read to the debugger listing, if needed

    If debugThis
        DebugString(*memoryLoc, amountRead, *netSource, #debugIn, errNumber, 0)
    EndIf

; now return the amount we have received, if anything

    ProcedureReturn amountRead

EndProcedure

; **********************************************************************************
; this procedure reads predefined fixed amount of binary data from a network connection
; **********************************************************************************

Procedure.i NetworkReadFixed(connectNumber.i, *memoryLoc, memLength.i, *netSource, debugThis.i, socketTimeout.i, maxReadTrys.i)

    Protected socketHandle.i = 0
    Protected attemptCount.i = 0
    Protected amountRead.i   = 0
    Protected errNumber.i    = 0
    Protected length.i       = 0
    Protected result.l       = 0; winsock utlizes a 32 bit integer only!

    socketHandle = ConnectionID(connectNumber)

; loop until we have received the predetermined amount of data, or our timeout has been exceeded

    Repeat
        result = ioctlsocket_(socketHandle, #FIONREAD, @length)

; get out of the loop if ShutDownNow is true

        If ShutDownNow
            debugThis = #False
            Break
        EndIf

; if we encountered a socket error, we need to exit the loop

        If result <> 0
            errNumber  = WSAGetLastError_()
            amountRead = -1
            Break
        EndIf

; if nothing has been received, increment the attempt counter and sleep, else get out of the loop

        If length = 0
            attemptCount = attemptCount + 1
            If attemptCount > maxReadTrys
                Break
            Else
                Delay(socketTimeout)
            EndIf

; else get the data in the network receive buffer

        Else
            result = ReceiveNetworkData(connectNumber, *memoryLoc + amountRead, memLength)
            If result > 0
                amountRead = amountRead + result
                memLength  = memLength  - result
            EndIf

            If memLength <= 0
                Break
            EndIf
        EndIf
    ForEver

; send the data we have read to the debugger listing, if needed

    If debugThis
        DebugString(*memoryLoc, amountRead, *netSource, #debugIn, errNumber, 0)
    EndIf

; now return the amount we have received, if anything

    ProcedureReturn amountRead

EndProcedure

; **********************************************************************************
; this procedure writes binary data to a UDP network connection, never debug UDP writes
; **********************************************************************************

Procedure.i NetworkUDPWrite(connectNumber.i, *memoryLoc, lenSendText.i)

    Protected lenToSend.i

    If lenSendText < 1 Or *memoryLoc < 1; just in case
        lenToSend = -999
    Else

; we only send 2048 bytes total, and never received an error with UDP

        If lenSendText > 2048
            lenToSend  = 2048
        Else
            lenToSend  = lenSendText
        EndIf

        SendNetworkData(connectNumber, *memoryLoc, lenToSend)
    EndIf

    ProcedureReturn lenToSend

EndProcedure

; **********************************************************************************
; this procedure reads binary data from a UDP network connection, never debug UDP
; **********************************************************************************

Procedure.i NetworkUDPRead(connectNumber.i, *memoryLoc, memLength.i)

    Protected result.i

; in theory, there are no socket errors with UDP

     result = ReceiveNetworkData(connectNumber, *memoryLoc, memLength)

; now return the aounnt we have received, if anything

    ProcedureReturn result

EndProcedure
Use as needed. As some of the work I do is for government entities, they require a copyright notice in all code I use. If you have any questions, just let me know.

Rich

Re: Network terminology question, esp re Telnet

Posted: Mon Mar 09, 2015 8:32 am
by ozzie
Thanks for the detailed reply, Rich. I've now decided to combine my Telnet and UDP processing, with the addition of a 'Protocol' option for to select TCP or UDP (plus a few other changes).

I was interested in the 'keep alive' option in the function setsockopt_(). That could be useful. However, apart from that I'm puzzled as to why you are using various Windows functions. In particular, in your procedure NetworkRead() you call ioctlsocket_(). In my existing code I use the PB function NetworkClientEvent(). Is there a particular reason for not using NetworkClientEvent() in your code?

Re: Network terminology question, esp re Telnet

Posted: Mon Mar 09, 2015 8:55 pm
by RichAlgeni
In regards to the keep alive option, honestly, I'm not sure that works outside of a Windows network on the same subnet. I have it set to true, but have never really seen it make a difference! Regardless, set it to 1.

For the first SetSockOpt_(), I use that to up the buffer size to up to 65k bytes. That way we can pretty much get an entire packet with one read.

The reason that I decided to use ioctlsocket_() was that I was trying to work around a Microsoft network bug when closing a server socket. Note that there has been a longstanding bug in closing a server socket on Windows machines. Never attempt to close a server socket. You can reassign the variable you use, Windows will handle closing the server socket. The length variable will return the amount of data available to be read in the network read buffer. The PureBasic function does not do this.

You will probably notice a number of variables that are not assigned in the include process? That is because I have an ini process that creates them as globals. Yes, even strings! In multithreaded programs! The reason I can get away with this is that all the variables I use from an ini file are treated as read-only. I never update them after they are read in.

Re: Network terminology question, esp re Telnet

Posted: Tue Mar 10, 2015 6:37 am
by ozzie
Thanks, Rich. There are some useful ideas in your code.

Re: Network terminology question, esp re Telnet

Posted: Tue Mar 10, 2015 5:13 pm
by RichAlgeni
You're welcome! :D