|Issue 73 June 18 2009|
Creating Tcal: Part Three, Sockets
As I mentioned before, Tcal is a client/server calendar. The idea is to have several Tcal clients on an intranet or connected via the internet that send and receive data from TcalServer (the server) using TCP/ip. TcalServer would be launched on a computer together with the database which would be either FileMaker Pro or Microsoft Access according to the platform, and would manage the messages between users and database.
I will talk about Tcalserver > Database communication in a later note. In this article I will describe how I managed to do socket communication between the clients and the server, the personal solutions I have chosen and the mechanics of the overall communication of my calendar. I owe a great deal to all the chatting examples I found on the RunRev Forum and in the Revolution examples (so much that I often did not even bother to change command names...)
Server and clients must be set with a number of pieces of information before they can connect. Tcalserver needs:
Security is a complex issue so, due to my limited experience, I reduced it to the fact that the Server must have on its preferences each IP, name and password of would be clients.
Another security precaution is that any message sent to the Server has a special character and the name of the client at the beginning of the message. In order to see other people's events and appointments, the logic has been that Tcal can talk only to Tcalserver, but Tcalserver talks to each client in its approved list. This way, on connection, each Tcal calendar receives a complete list of every user's events (there is a user settable limit on past events...you mostly don’t need to load the events of the previous year...)
TcalServer sits waiting for connection. The script that makes it work is
accept connections on port ThePortNumber with message \ chatConnected
For a good explanation of socket connections, see the “Internet Chat” stack in the Revolution Resource Center.
Both in Tcal and TcalServer I created a substack to contain all the socket scripting, so as to get it more manageable. On the preopenstack I needed of course to do a
start using stack "ChatLibrary"
so that I could use the scripts from anywhere.
When a client connects to Tcalserver, he sends name and password. TcalServer cheks if the connection is allowed and, if yes, it sends (in this case only to the specific user) some basic information: a list of users and Jobs previously loaded from the database.
At this point TcalServer waits for the user to do some action. I will describe the creation of a new event (a record to be created on the event database). Before that, let me remark on a few facts concerning the composition of messages:
- each message is a text string split into different items with a arbitrarily choosen itemdelimiter. Since the message may contains any kind of text, I had to choose an itemdelimiter that would always allow Tcal and TcalServer to extract from the message the starting character, the sender name and the real message.
- I may have return chars on the string, so before sending and on receiving, the return char is substituted with a special character. Due to the differences between OSX and Windows, I had to be sure that the chars were the same on both OSes.
- In Italy we have several accented words. If you send an accented char from OSX to Windows, it does not arrive the way you intended because of ASCII differences; so, before sending, both on Tcal and TcalServer I had to run this line:
if the platform is not "MacOs" then put MACToISO(Thedata) into \ Thedata
- each message ends with a chosen combination of characters. This way the receiving script knows where the end of the message is.
- Finally (I will explain this later on) on the Tcal message there is a unique ID number that gets sent back from Tcalserver and that allows Tcal to cancel the script that handles the no-answer or delayed-answer.
Of course, all of this message composition is taken care of by a command, like this for example:
on chatMessage Thedata put endOfMessage after Thedata --Ive set this var before \ with |##, as a ending of message mark send "MysocketTimeOut" to me in 10 seconds --explained \ later put the result into TheMessageID --explained later put "*|" & TheMessageID & "|" & MyName & "|" before Thedata if the platform is not "MacOs" then put IsoToMac(Thedata) \ into Thedata put false into TcalServerAnswered --explained later write Thedata to socket TheChatSocket --the \ TheChatSocket variable -- contains the host and port of the server, received on -- connection end chatMessage
TheData has been previously cleaned up with
replace return with "^" in Thedata replace quote with "§" in Thedata --this because we do not \ want to mess with quotes --in the Server/database Applescript or VBscript talking
When the user creates an event on its calendar, a rect fields gets created holding the event info in its custom properties and the event data are sent to Tcalserver. Tcalserver polls the “createRecord” script (an Applescript or VBscript saved on the stack's custom properties), replaces what’s needed in the script (event data, name of the database, password of the database, etc) and does a
Do ThisScript as Language --Language is Applescript or VBScript
The alternate language script, in this example, upon creation of the record in the database, gets back from it the unique ID number of the record, replace the FakeID on the original received message from Tcal with the unique ID and send the message to all the users with something like this (taken from the Chat example):
put keys(lChatterArray) into tChatterList -- cycle through all of the currently connected clients -- placing the host and port for each one into the variable -- "tSocket" put "*|" & TheMessageID & "|" & sender & "|" & TheMessage \ into invio if the platform is not "MacOs" then put ISOToMAC(invio) into \ invio repeat for each line tSocket in tChatterList write invio to socket tSocket -- send the data \ contained in the message variable to each client end repeat
The Tcal calendar that created the event receives the message and replaces the fakeID with the uniqueID on the event data. All the other connected calendars save internally the new event data (or show the event if in that particular moment they are inside the sender calendar). Note that in this way the user does not have to wait for the TcalServer answer, in order to draw the new event on the screen: it would have been a really poor interface with such delay...
But Intranet or internet connections don’t happen instantly: the Server may be busy doing something else or it may have been shut by the IT manager of the company (they do this kind of thing). So, how long has Tcal to wait for an answer by Tcalserver ?
Probably my way of doing socket communication is not the best and it may be flawed by some unforeseen mistake. But, before discovering this solution, I tried several times to use the socketTimeout command available in RunRev and I never was able to make it work. So let me explain the meaning of the send "MysocketTimeOut" to me in 10 seconds and TcalServerAnswered flag of the ChatMessage script above. Reading from the RunRev dictionary about the send command:
If you specify a time, the message is sent to the object after the specified time has elapsed. The message is added to the pendingMessages function. The ID of the message is returned by the result function. To cancel the message, use the cancel command with this ID.
In my script I tell Revolution to wait 10 seconds before calling off the back answer from TcalServer. The TcalServerAnswered variable is set to false before sending and, upon receiving the message back from Tcalserver, if the message is correct, to true. Tcal also cancels the pending message using TheMessageID inside the message returned.
So my MysocketTimeOut looks like this:
on MysocketTimeout theID if lChatSocket is not empty and TcalServerAnswered = false \ then put true into TcalServerAnswered answer "Can't connect. TcalServer is off..." with "OK" send "MouseUp" to fld "Disconnect" of card 1 of stack \ "Tcal" exit to top end if end MysocketTimeout
On top of this, I had to take into account the fact that Tcal, while waiting for an answer from Tcalserver, might receive other messages, concerning the creation of events by different users. My solution was to give priority to the return messages owned by the user, while saving in an array the pending messages from other users; messages to be done once the owned message was accomplished. This is the script that handles the receiving messages on Tcal:
on chatReceived s,data -- this command comes from the connect script: read from -- socket s until endOfMessage with message "chatReceived" set itemdelimiter to "|" if item 1 of data is "*" then -- correct message, the \ flag character put item 2 of data into TheMessageID --this is the \ ID of the MySocketTimeOut if the platform is not "MacOs" then put \ MacToIso(data) into data --clean up the data put item 3 of data into TheSender --could be me or \ not if TheSender <> ThisIsMe then --is not my answer if TcalServerAnswered is false then --I was \ waiting for an answer by Tcalserver --I store the message for later use if Chiave is empty or Chiave is not a \ number then --chiave is a local variable put 0 into Chiave end if add 1 to Chiave put s && data into \ ThePendingData[Chiave] --ThePendingData is a -- global var that holds the pending messages -- received else --I was not waiting for a message so I \ can take care of this and that's it chatRicevuti s,data --this is the \ command that actually takes care of the --message inside the calendar end if else --The message is mine (the answer back from \ TcalServer) if TcalServerAnswered is false then put true into TcalServerAnswered --set \ the flag cancel TheMessageID --no more pending \ messaage chatRicevuti s,data --take care of my \ data if the keys of ThePendingData is not \ empty then --there are messages -- from other user waiting to be taken care of repeat for each line LaChiave in \ the keys of ThePendingData put word 1 of \ ThePendingData[LaChiave] into S delete word 1 of \ ThePendingData[LaChiave] chatRicevuti \ s,ThePendingData[LaChiave] --do the message end repeat delete global ThePendingData put 0 into Chiave end if else cancel TheMessageID chatRicevuti s,data --do the message end if end if end if read from socket s until endOfMessage with message \ chatReceived end chatReceived
Looking at it, nowadays I wonder how I managed to create it! Revolution scripting is very easy but the logic of communication is not (see human beings...). Mostly, I believe, because you need to take timing into account.
In the next article I will describe the AppleScript and VbScript that allowed me to modify FileMaker Pro and Microsoft Access database which is much easier than everything above.