Navigation: Programming Cookbook > Sockets Connectivity >
Monitoring Blocking Calls
So far we have been using blocking Socket calls executed from a workspace and therefore relying on the fact that another user interface process will be automatically started when the original is blocked. If you were to use these calls from within your application classes you would normally want to start-up a background process specifically for this purpose. Maintaining one or more background processes can be tedious, especially in situations where the requirements are fairly straightforward. For this reason, we have introduced a class, BlockingCallMonitor, to help with the task of maintaining a background process in which a blocking call can be easily made and monitored.
Once instantiated, a BlockingCallMonitor instance can be configured with a Block that, when evaluated, will run the proposed blocking call. It can also be configured with Blocks to be executed when the call completes or when an error occurs. The default completion block merely triggers a #completedWith: event that will be supplied with the result of the call as its argument. The default error block re-signals the error, but you might often replace this with another Block that triggers an appropriate event.
First, a simple example demonstrating how a BlockingCallMonitor can be used to indicate when an STB streamed object has been received and fully assembled by a Socket. In the Server workspace:
monitor := BlockingCallMonitor callBlock: [socketA receive].
monitor when: #completedWith: send: #notify: to: MessageBox.
Tip: note that the parameter passed when #completedWith: is triggered will also be forwarded as the parameter to MessageBox>>notify:. For this reason, this example only works when socketA receives String objects.
Now, in the Client workspace:
socketB send: 'Hello Sockets'.
socketB send: 'Hello Again'.
For each send of a String from socketB you should see the monitor bring up a MessageBox displaying the string's value. Since the monitor is repeatedly evaluating the call block on its background process this can continue ad-infinitum if required.
You should always aim to terminate a BlockingCallMonitor's monitoring process when you finished using it. Otherwise, the monitor object will not be garbage collected. In the Server workspace:
As a more complicated example let's see how multiple BlockingCallMonitors can be used to easily implement a complete "Smalltalk Server" capable of servicing multiple client connections. Because of the complexity, you should probably copy the code below into a new workspace via the clipboard to avoid any obvious typos that might prevent it from working first time.
"Here is the code to set up the Smalltalk Server"
serverSocket := ServerSocket port: 2048.
acceptanceMonitor := BlockingCallMonitor new.
acceptanceMonitor callBlock: [serverSocket accept].
acceptanceMonitor completionBlock: [:socket |
serverMonitor := BlockingCallMonitor new.
serverMonitor callBlock: [Compiler evaluate: socket receive logged: false].
serverMonitor completionBlock: [:answer | socket send: answer].
serverMonitor errorBlock: [:error | Sound beep. serverMonitor terminate].
serverMonitor monitor ].
A new ServerSocket is set up on port 2048. A BlockingCallMonitor is created (the acceptanceMonitor) to accept connection requests from clients and, as each arrives, another one is instantiated (the serverMonitor) to receive and handle the Smalltalk evaluations. Note that we are using the error block of the serverMonitor to detect any errors and shut down the monitor process. This will also detect the situation when the client socket is closed and clean up appropriately.
Now, let's connect some clients and make some requests of the server.
socketA := Socket port: 2048 address: (InternetAddress ipAddress: #[192 168 0 1]).
socketA send: '5 factorial'; receive. "Display it"
socketB := Socket port: 2048 address: (InternetAddress ipAddress: #[192 168 0 1]).
socketB send: '3+4'; receive. "Display it"
socketB send: '((1 to: 10) collect: [:i | i ]) reverse'; receive. "Display it"
"Close each client"
As each client socket is closed you should hear a beep as the appropriate error block is executed and the corresponding serverMonitor is terminated. When you're finished, don't forget to clean up the acceptanceMonitor before closing the workspace.