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.
Network terminology question, esp re Telnet
- RichAlgeni
- Addict

- Posts: 935
- Joined: Wed Sep 22, 2010 1:50 am
- Location: Bradenton, FL
Re: Network terminology question, esp re Telnet
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.
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
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
EndProcedureRich
-
ozzie
- Enthusiast

- Posts: 444
- Joined: Sun Apr 06, 2008 12:54 pm
- Location: Brisbane, Qld, Australia
- Contact:
Re: Network terminology question, esp re Telnet
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?
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?
- RichAlgeni
- Addict

- Posts: 935
- Joined: Wed Sep 22, 2010 1:50 am
- Location: Bradenton, FL
Re: Network terminology question, esp re Telnet
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.
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.
-
ozzie
- Enthusiast

- Posts: 444
- Joined: Sun Apr 06, 2008 12:54 pm
- Location: Brisbane, Qld, Australia
- Contact:
Re: Network terminology question, esp re Telnet
Thanks, Rich. There are some useful ideas in your code.
- RichAlgeni
- Addict

- Posts: 935
- Joined: Wed Sep 22, 2010 1:50 am
- Location: Bradenton, FL
Re: Network terminology question, esp re Telnet
You're welcome! 