Mailing List Archive: 49091 messages
  • Home
  • Script library
  • AltME Archive
  • Mailing list
  • Articles Index
  • Site search
 

[REBOL] The REBOL async:// tutorial - take 1

From: maarten:vrijheid at: 3-Mar-2004 15:48

See below. Enjoy it. Republish it. ================================================== The REBOL async:// tutorial - Take 1 Maarten Koopmans, code samples and root protocol by Gabriele Santilli What is async:// anyway? It is an asynchronous TCP root protocol that can be used to drive REBOLs hidden async TCP features. This allows network code to be processed automagically upon certain network events, such as getting a connection, ready to read/write, and closing a conection. Once you have set the code to be called upon these events, things will go by themself if you're in an event loop. This is much like GUI events are processed automagically when you call 'view. The first thing you need to do is get the async:// root protocol by Gabriele. Download it at http://www.rebol.it/giesse/async-protocol.r Note that this version supports binary transfers only, /lines support is being worked on. Now let's start with a simple client script, downloading images in the background in REBOL/View. First the code: handler: func [port [port!] state [word! error!] /local tmp cmd] [ if error? :state [print mold disarm state return true] switch state [ connect [ ; do HTTP request insert port {GET /fg/anen.jpg HTTP/1.0^M^JHost: www.3dwallpaper.com^M^J^M^J} false ] read [false] write [false] close [ ; get data data: copy port close port ;print copy/part data find data "^M^J^M^J" data: to binary! find/tail data "^M^J^M^J" other/image: attempt [load data] other/text: "" show other false ] ] ] port: open async://www.3dwallpaper.com:80 port/awake: :handler view layout [ across me: box 100x100 random 255.255.255 0:00:00.5 feel [ engage: func [f a e] [ if a = 'time [ me/color: random 255.255.255 show me ] ] ] other: box 100x100 255.255.255 "Downloading image..." Return Area 208x100 "You can type here while downloading." ] Here is how it works: you open an async port using normal URI syntax in the middle of the script. You can see this in the lines: port: open async://www.3dwallpaper.com:80 port/awake: :handler The second line sets the callback function. This function is the crucial part for async:// based implementations, as it provides code that can be executed for all the events. So, let's take a look a the handler function! It starts with the following piece of code: handler: func [port [port!] state [word! error!] /local tmp cmd] [ if error? :state [print mold disarm state return true] As you can see, we get two parameters: The port that we can read/write to and the state. The state can be an error! or a word!. If it is an error we need to clean up and the above code shows briefly one way how this can be done on the second line. If it is a word it can have any of four values: - 'connect, signalling that we have just acquired a connection (client-side only) - 'read , signalling that we can have data to read - 'write , signalling that we can write data (again) - and 'close , signalling that the other side has closed the connection. So what we do next in our handler is a switch based on the state, executing different code for each possible state. switch state [ connect [ ; do HTTP request insert port {GET /fg/anen.jpg HTTP/1.0^M^JHost: www.3dwallpaper.com^M^J^M^J} false ] read [false] write [false] close [ ; get data data: copy port close port ;print copy/part data find data "^M^J^M^J" data: to binary! find/tail data "^M^J^M^J" other/image: attempt [load data] other/text: "" show other true ] ] As you see, we simply do a HTTP GET for an image, and write it to file and the view it. Upon 'connect we immediately make our request. Why not in the 'write event? The 'write event is triggered everything we have something written and we canw rite more. So the first time we always have to write from another event!!! Now, once the data is inserted we return false. This indicates that we shouldn't be removed from the system/ports/wait-list list, where all ports are stored (at least async:// and event:// ones). As you can see we did return true on error. The read and write states COULD be used to do some reading from data or write more, but as this is a very simple request-response, that's not necessary, so they just return false. So... the other side returns the data and closes the connection. Upon close we simple read it ALL (again this could have been done in 'read using a temporary buffer) and close the port. We update the image and.... done! Now for a simple server: First we add a listening server port to the system/ports/wait-list, like: either error? try [listen: open/no-wait tcp://:8000] [ port: open async://localhost:8000 port/awake: do handler ] [ listen/awake: func [l /local p] [ print "Got connection." p: first listen remove find system/ports/wait-list listen port: make port! [scheme: 'async sub-port: p] open port port/awake: do handler false ] insert tail system/ports/wait-list listen port: none ] As you can see, its awake function convert the accepted port to an async one and sets the handler. So what is the handler then? handler: [ use [ buffer ][ buffer: copy [] func [port [port!] state [word! error!] /local tmp cmd] [ if error? :state [print mold disarm state return true] switch state [ connect [print "Connected." false] read [ append buffer copy port while [tmp: find buffer newline] [ cmd: copy/part buffer tmp remove/part buffer next tmp do-cmd cmd ] false ] write [false] close [print "Peer closed connection." close port true] ] ] ] ] The first thing to notice is the fact that we use 'use to create a context that returns a function! value. This function (and only this particluar value) has access to its buffer. By doing the handler block in the server part above, every accepted port gets a copy of theis function value with its own "static" buffer space. A very simple but effective trick. Now, on to the events. 'Connect does nothing, as we are already connected. This happened when the awake function of our TCP server port was called. We chose 'write and 'close also to do nothing. Why? We only want to receive a command and then do it. The reading is done in 'read, we append what we have read to the buffer. If we see a newline, we execute it and clear the buffer until the newline. And then we return false. The fact that we return false implies that we may read more than one connection. Also note that 'close does nothing, but *if* a port is closed, it also closes. Of course you can use 'close for all sorts of clean-up. Now if you this code do-cmd: func [cmd] [ other/color: attempt [load cmd] show other ] view layout [ across me: box 100x100 random 255.255.255 0:00:01 feel [ engage: func [f a e] [ if a = 'time [ me/color: random 255.255.255 if port [insert port join me/color newline] show me ] ] ] other: box 100x100 random 255.255.255 Return Area 208x100 ] halt to the above, you have a simple GUI with a command server. You are now ready to play with async:// and enhance the code adding substates to 'read or 'write to implement advanced protocols. Its just more of the same. I hope you keep the use [ ] [ ] trick in mind for server handlers, I found it a very easy way to add extra substates this way. Now.... start coding!