Googolflex!!
  • Home
  • About
  • Contracting

Recent Posts

  • Sprint’s new “Simply ‘Almost’ Everything®” Plans
  • CSS Changes in Flex 4
  • Dotted Underline LinkButton (Flex)

About The Author : jwd

This is John Dusbabek's tech blog. John is a software engineer and Flex developer in Provo, UT, where he lives with his lovely wife and four sons.

Recent Comments

  • nodmonkey on PHP Warning: mysql_connect(): Can’t connect to MySQL server on… (13)
  • Can't connect to mysql with php/apache but can with cli | That-Matt on PHP Warning: mysql_connect(): Can’t connect to MySQL server on… (13)

Archive for Scalability

Feb
09

MySQL Master-Master Replication

Posted by: jwd | Comments (0)

In my opinion, having dual-writable master MySQL databases (in a replication configuration) is not worth the hassle. There are a host of problems, enough that you should seriously consider what you’re trying to gain when attempting it. However, the master-master replication scheme still has some very good uses when used in an active-passive way. The two most compelling reasons for me are:

1. Keeping a “hot spare”. You have an additional database already configured as a master. This might not seem like much, since you “almost” gain the same thing in a master-slave setup. However, if your master-master includes slave servers, this topology provides a very high degree of fault tolerance. Especially when each master-server pair is geographically separated.

2. Making changes to the database. Certain changes made to the database may require a long time to complete, particularly if you have a very large database. In the master-master setup, you can take one of the servers “offline” (by telling the other master not to replicate its changes), and make the changes necessary. Then bring it back online, after the changes have been made, make it the active master, and let the other master perform the changes.

This could be seen as an added benefit of keeping a “hot spare”.

The good news is that setting this up is identical to setting up master-slave replication, you simply do it twice. Each master is essentially the master of and a slave to the other database. To keep it active-passive, one of the databases will need to be made read-only. Here are the changes you will need to the my.cnf configuration file:

Active Master my.cnf

log_bin = mysql-bin
server_id = 1001
relay_log = mysql-relay-bin
log_slave_updates = 1

Passive Master my.cnf

log_bin = mysql-bin
server_id = 1002
relay_log = mysql-relay-bin
log_slave_updates = 1
read_only = 1  # Notice this line

Then set up the replication user accounts, as described in this post: Simple MySQL Master-Slave Replication

Finally you issue the slave directives, and start the slave process:

Active Master ‘change master’

# Active master is slave to passive host
CHANGE MASTER TO MASTER_HOST='passive.mysql.host',
MASTER_USER='rep_user',
MASTER_PASSWORD='reppassword',
MASTER_LOG_FILE='mysql-bin-000001',
MASTER_LOG_POS=0;

start slave;

Passive Master ‘change master’

# Passive master is slave to active host
CHANGE MASTER TO MASTER_HOST='active.mysql.host',
MASTER_USER='rep_user',
MASTER_PASSWORD='reppassword',
MASTER_LOG_FILE='mysql-bin-000001',
MASTER_LOG_POS=0;

start slave;
Categories : Architecture, Databases, MySQL, Scalability
Comments (0)
Feb
06

Simple MySQL Master-Slave Replication

Posted by: jwd | Comments (0)

I’ve been doing a lot of research (not cutting edge type stuff) into MySQL scalability, and the first exercise I went through was configuring a simple master-slave replication setup. It was much simpler than I thought it would be. Here are the steps.


Editing the my.cnf Files

Because of the way replication works in MySQL, you will need to turn on binary logging. Essentially, the slave is going to connect to the master and request the log. After it gets the log (or the parts that it needs) it will replay it, executing the queries to bring it up to date. You also need to give the server an ID. So add these lines to the my.cnf (usually /etc/my.cnf) on the master database:

<mysqld>
log_bin = mysql-bin
server_id = 1001

You can give it any ID you want, you just want it to have a different ID than the slave. In the slave we will add a few additional lines, as well:

<mysqld>
log_bin = mysql-bin
server_id = 1002
relay_log = mysql-relay-bin
log_slave_updates = 1
read_only = 1

Note: Replace the angle brackets above with square brackets. I haven’t figured out how to make my code plugin not treat [mysqld] as another code block definition.

According to Schwartz, et al. in High Performance MySQL (review forthcoming), the only parameter required on a slave is the server_id. The other parameters make it easy to switch the server between being a master or a slave. They also mention the read_only parameter is a good safety precaution but might not be applicable in all cases.


User Account Setup

Replication privileges need to be granted to the replication user on the master. If you want to be able to easily switch your server between master or slave, you can grant these privileges on both servers.

GRANT REPLICATION SLAVE
ON *.*
TO 'rep_user'@'10.0.0.%'
IDENTIFIED BY 'reppassword';

There are certain features that are accessible by granting the REPLICATION CLIENT privilege. You can also limit the replication privileges to only certain databases or tables if so desired. Though it is not recommended, you could also grant access to all hosts, just as you might in any other GRANT statement.


Starting the Replication Procedure

Now that both the master and slave are configured, and correct permissions are granted to the replication user, the slave needs to be “started”. This is done by declaring the master host, and indicating necessary credentials and log file information, and then issuing the ’start slave’ command.

The host and credential information can be declared in the my.cnf file. However there are some advantages to making the declarations as MySQL commands. Specifically you will be able to make changes to these without having to restart the daemon. Here is the command you should issue to configure the slave:

CHANGE MASTER TO MASTER_HOST='master.mysql.host',
MASTER_USER='rep_user',
MASTER_PASSWORD='reppassword',
MASTER_LOG_FILE='mysql-bin-000001',
MASTER_LOG_POS=0;

And then you issue the command to start the slave:

start slave

If you were previously using binary logs on the master, and then cloned the server to create the slave, you might have trouble getting replication started without first deleting the binary log from the slave.

Sources
The MySQL documentation
High Performance MySQL, by Baron Schwartz et al.

Categories : Architecture, Databases, MySQL, Scalability
Comments (0)
Feb
04

Apache mod_proxy_balancer Self Registration : Part 3

Posted by: jwd | Comments (0)

I’ll start off by going over the basic high level architecture for my self registration procedure:

There is a register.php script residing on the load balancer, accessible via HTTP.
There is a deregister.php script residing on the load balancer, accessible via HTTP.
There is a register_with_lb.pl script residing on the web server, in /usr/local/bin/.
There is a deregister_with_lb.pl script residing on the web server, in /usr/local/bin/.
There is a MySQL database that stores the current configuration state, on it are two stored procedures register_lb and deregister_lb.


register.php

No changes were made to register.php as described in this post , though I’m considering some alterations to increase its security.


deregister.php

The biggest difference between register.php and deregister.php (aside from their purpose) is where the insert/delete database code is called from and why. When register.php is called by the web server, it will have already inserted information about itself into the database, including its hash. I made the decision that I did not want the load balancer responsible for inserting servers into the database. It would merely check that the requesting server inserted itself, and then regenerate the balancer_members.conf.

In the case of deregister.php I decided I wanted the server making the call to still be in the database so the script could verify the identity before removing it and regeneration the balancer_members. And since the deregistration SQL is contained within a stored procedure, I needed to make some changes to the script (as compared to register.php) regarding the database.

Specifically, the standard mysql library cannot call stored procedures. So I had to convert it to using mysqli, which is a similar, though more OO approach. The portion of the code that regenerates the balancer_members.conf is similar enough that I won’t re-list it here, but I will show how to connect using mysqli, and how to call a stored procedure.

$mysqli = new mysqli($dbhost, $dbuser, $dbpass, $dbname);
if (mysqli_connect_errno()) {
  printf("Connect failed: %s\n", mysqli_connect_error());
}

$query = "SELECT count(*) as count FROM " . $dbtable . " WHERE ip='" . $_SERVER['REMOTE_ADDR'] . "';";
$result = $mysqli->query($query);
$row = $result->fetch_row();
echo $row[0];

if ($row[0] >= 1) {
  $del_query = "call deregister_lb('" . $_SERVER['REMOTE_ADDR'] . "');";
  $del_result = $mysqli->query($del_query);

  //<code for regenerating the conf file removed here>

  echo exec('echo "' . $file . '" > /etc/httpd/conf.d/balancer_members.conf');
  echo exec("sudo /usr/local/bin/reload_httpd");
}

As you can see, I’m using the actual REMOTE_ADDR to determine the validity of the request.


(de)register_lb.sql Stored Procedure

Here is the code for the deregister_lb stored procedure:

DROP PROCEDURE IF EXISTS deregister_lb $$

CREATE PROCEDURE deregister_lb ( ip VARCHAR(100) )
  BEGIN
    DELETE FROM lb2_members
	WHERE ip=_ip;
  END $$

and also for the register_lb stored procedure:

DROP PROCEDURE IF EXISTS register_lb $$
CREATE PROCEDURE register_lb (
  _hostname VARCHAR(100),
  _ip VARCHAR(40),
  _loadfactor INT,
  _hash VARCHAR(100)
  )

  BEGIN
    DECLARE already_exists INT DEFAULT 0;
    SELECT count(*) INTO already_exists FROM lb2_members WHERE hash=_hash;

    IF already_exists=1 THEN
	  UPDATE lb2_members
	  SET hostname=_hostname, ip=_ip, loadfactor=_loadfactor
	  WHERE hash=_hash;
    ELSE
      INSERT INTO lb2_members (ip, hostname, loadfactor, hash)
	  VALUES (_ip, _hostname, _loadfactor, _hash);
    END IF;
  END $$

Note that I’ve omitted the code that changes the delimiter to $$ instead of a semicolon.


register_with_lb.pl

This perl script uses perl DBI for accessing the database. I had to get that installed on my web server since it wasn’t already. Normally you can install perl packages using the cpan command. In which case you would issue the following commands to install DBI and a MySQL driver for it:

cpan DBI
cpan DBD::mysql

If it’s the first time you’ve run cpan, you will need to go through some configuration. It’s pretty much self explanatory, and I just accepted all of the defaults. Everything installed correctly except for the MySQL driver, which I ended up having to install from source. If I had executed the command:

yum install mysql-devel.i386

first, then my cpan install of DBD::mysql might have worked, but I didn’t realize that until installing from source. In case you ever need to install a perl module from source, particularly the DBD::mysql driver, enter these commands (which I think is basically what cpan does):

yum install mysql-devel.i386 #(only requred in this particular instance)
wget http://www.cpan.org/modules/by-module/DBD/DBD-mysql-4.011.tar.gz
gzip -cd DBD-mysql-4.011.tar.gz | tar xf -
cd DBD-mysql-4.011 #(or whatever version you downloaded)
perl Makefile.PL
make

Here is how you connect to the database and call a stored procedure:

my $dsn = "DBI:mysql:host=mysql.host;database=lb_register";
my $dbh = DBI->connect ($dsn, "lbuser", "lbpasswd")
  or die "Cannot connect to MySQL server\n";

my $sql = "call register_lb('" . $localhost . "', '" . $localip . "', " . $loadfactor . ", '" .  $hash . "')";
$dbh->do($sql);

$dbh->disconnect();

After that, register_with_lb.pl opens a socket to the load balancer and makes an HTTP request over the socket. There are probably easier ways to do this, I just happened to have the socket code lying around and was glad to be able to reuse it. Here’s the gist of it, in case you’re interested:

# Parse the URI.
my $url = URI->new("http://load.balancer.com/register/register.php?hash=" . $hash);

# Parse these in from the command line
$host = $url->host;
$port = $url->port;
$resource = $url->path;
$query = $url->query;

# Initialize the socket
$socket = IO::Socket::INET->new ( Proto => "tcp", PeerAddr => $host, PeerPort => $port,);
unless ($socket) { die "Error connecting to $host" }
$socket->autoflush(1);

# Format the request
my $request = "GET " . $resource . (($query)?"?" . $query : "") . " HTTP/1.1" . $EOL . "Host: " . $host . $EOL . "User-agent: register_script" . $EOR;

# Use send() to make the request, and output the response.
# Not necessary in this example, but informational.
if ( $socket->send($request) ) {
  while ( <$socket> ) { print }
}

# Close the socket
close $socket;

The above code pretty much sums up deregister_from_lb.pl, since no database calls are made, a call is simply made to the deregister script. The line you would change is as follows:

my $url = URI->new("http://my.balancer.com/register/deregister.php");

Then make the files executable, and copy them to be used by the startup script described in the previous post:

chmod a+x register_with_lb.pl
chmod a+x deregister_with_lb.pl
cp register_with_lb.pl /usr/local/bin/
cp deregister_with_lb.pl /usr/local/bin

I don’t show it here, but right now my IP addresses are hard coded. There are a number of ways you can find out your actual IP address from within perl, I’m just not doing that right now.


Securing the register scripts

As an additional security measure, I’ve restricted access to the /register/ location on the load balancer to the IP address range I expect my web servers to be from, like this:

<Location /register>
  Order Deny,Allow
  Deny from all
  Allow from 10.0.0.
</Location>

And now you have a web server that can register automatically (if you’ve gone through the previous two posts as well) with a mod_proxy_balancer load balancer.


Update

I did some searching around to find a way to determine your IP address from inside the perl script. This is a simple way if your server has a public IP address and reverse DNS set up correctly for that IP address:

use Socket;
use Sys::Hostname;
my $host = hostname();
my $addr = inet_ntoa(scalar(gethostbyname($host)) || 'localhost');

If your slave web servers are on a private network, the above command will return the loopback IP address (127.0.0.1) which isn’t useful for the load balancer (I wonder if it would start an infinite loop and crash the load balancer?). I found a function that prints out the IP address by parsing it out from the results of the ifconfig command.

It seemed a little long to just rip off and copy verbatim. So here’s a link to that code (which is what I’m using now) in case you’d like to use it. Perl script to get IP address.

Categories : Apache Web Server, Architecture, MySQL, Perl, Scalability, Shell Scripting, client/server
Comments (0)
Feb
03

Apache mod_proxy_balancer Self Registration : Part 1

Posted by: jwd | Comments (0)

Load balancers are great, but they become even more powerful when servers have the ability to self-register when they come online, and deregister when they go offline. This is especially true with services such as EC2, when the size of the server group might grow or shrink in response to need. This is a tutorial describing my particular (partially insecure at the moment) solution for allowing self-registration with Apache’s mod_proxy_balancer. Specifically this covers the load balancer side of the equation. Tomorrow I hope to get a post out describing the server side.

Here is my flowchart for how self registration will work:
1. Server comes online.
2. A startup script will register itself with the MySQL database (including hostname, ip, loadfactor, and a hash that it will generate in some way).
3. The server will then call a PHP script on the load balancer: “register/register.php”.
4. The PHP script will verify that a server sent the request.
5. The PHP script will query the database to get the current list of balancer members, and regenerate the balancer_members.conf file.
6. The PHP script will then issue a command to reload Apache’s configuration files.

Deregistration, which my PHP script as presented doesn’t display, will work as follows:
1. Server sends its hash to the PHP script, and shuts down.
2. The PHP script will check the hash against the database.
3. The PHP script will remove the server from the database.
4. The PHP script will repeat steps 5 and 6 above.

First, set up the database and created a user with sufficient privileges.

CREATE DATABASE lb_register;
GRANT ALL ON lb_register.* TO 'lbuser'@'%' IDENTIFIED BY 'password';

CREATE TABLE lb2_members(
ip VARCHAR(20) NOT NULL PRIMARY KEY,
hostname VARCHAR(100) NOT NULL,
loadfactor INT NOT NULL DEFAULT 0,
hash VARCHAR(40) );

Second, create the PHP script.

$dbhost = "mysql.host.com";
$dbuser = "lbuser";
$dbpass = "password";
$dbname = "lb_register";
$dbtable = "lb2_members";

$conn = mysql_connect($dbhost, $dbuser, $dbpass) or die (mysql_error());
mysql_select_db($dbname);

$query = "SELECT count(*) as count FROM " . $dbtable . " WHERE hash='" . $_GET['hash'] . "';";
$result = mysql_query($query);

$row = mysql_fetch_assoc(mysql_query($query));
if ($row['count'] >= 1) {

  $file = "<Proxy balancer://mycluster>" . "\n";
  $member_query = "SELECT hostname, loadfactor FROM " . $dbtable . ";";
  $member_result = mysql_query($member_query);

  while ($row = mysql_fetch_array($member_result, MYSQL_BOTH)) {
    $file .= "   BalancerMember http://" . $row['hostname'] . " ";
    $file .= ($row['loadfactor'] > 1) ? ("loadfactor=" . $row['loadfactor'] . "\n") : "\n";
  }
  $file .= "</Proxy>";

  exec('echo "' . $file . '" > /etc/httpd/conf.d/balancer_members.conf');
  exec("sudo /usr/local/bin/reload_httpd");
}

mysql_close($conn);

You can tell a few things about the server configuration by looking at the script:
1. User apache will need to be able to write to the “/etc/httpd/conf.d/balancer_members.conf” file.
2. User apache will need to be able to execute the script “/usr/local/bin/reload_httpd”.
3. User apache will need sudoer rights.
4. This script was used for debugging, and not by a server that is actually registering… tyou can see that deregistration is not handled yet.

To grant write privileges to apache, I changed the owner of the balancer_members.conf to apache.

 chown apache /etc/httpd/conf.d/balancer_members.conf

This is probably the least secure aspect of my solution, as if the apache user were compromised, then any directives could be written to this file. I’m not sure how big a threat this is, but it’s something that concerns me at least enough to think about this some more (and invite suggestions).

Next is to grant apache privileges to execute “/usr/local/bin/reload_httpd”. We could accomplish this the same as we did above, but then it wouldn’t allow apache to execute what’s inside of the script, which is this:

#!/bin/bash
service httpd reload

unless we give execution rights to apache on service, which we don’t want. What we also don’t want is for apache to be able to write to the file reload_httpd. So what I ended up doing was, as you see in the script, to make root the owner of reload_httpd and remove write privileges for all (so apache couldn’t change it) and then add apache to the sudoers file, granting rights to execute this script without a password.

visudo

is the generally accepted way to edit the sudoers file. And I added this line:

apache ALL=(ALL) NOPASSWD: /usr/local/bin/reload_httpd

I’m open to more secure ways of implementing this aspect as well, as I don’t consider myself a sudo configuration expert. I think this gives apache rights to execute everything from anywhere if he knows the password; but he can also execute the /usr/local/bin/reload_httpd script without a password.

I also had to comment out the line:

#Defaults   requiretty

to allow sudo to function properly from a script not executed in a terminal.

Finally I had to disable proxying for the register script in my balancer.conf file:

ProxyPass /register/ !

And then your server is configured to dynamically update its list of balance members, you can check by going to the balancer-manager if you’ve got that enabled. Next I will discuss how to handle the web server side of things.

Categories : Amazon Web Services, Apache Web Server, Architecture, EC2, Linux, MySQL, Scalability, client/server
Comments (0)
Feb
02

Apache mod_proxy_balancer: No Protocol handler was valid

Posted by: jwd | Comments (0)

Just this afternoon, one of my colleagues and I were discussing our feelings about the “semicolon-class” of bugs that developers will inevitably spin their wheels on from time to time. I’ve had just such an experience the past two evenings with what started out as a simple recipe from the Apache Cookbook, entitled “Load Balancing with mod_proxy_balancer”.

The recipe itself is straightforward, taking up just over a page. I was able to get a basic load balancer working within a few minutes by following the recipe, but for some reason I couldn’t get the balancer-manager site to load correctly. The following directives should configure the balancer-manager:

<Location /balancer-manager>
  SetHandler balancer-manager
  Order Deny,Allow
  Allow from all
</Location>

When I would start the server, however, I got an HTTP 500 error response, with this message in the log:

[Tue Feb 02 12:49:09 2010] [warn] proxy: No protocol handler was valid for the URL /balancer-manager. If you are using a DSO version of mod_proxy, make sure the proxy submodules are included in the configuration using LoadModule.

My searches on Google were fruitless: most people were able to solve the problem by ensuring these lines were added to their httpd.conf:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_connect_module modules/mod_proxy_connect.so

But they were already included in my conf file, so no dice. Other people solved their problem by loading the mod_proxy_html.so, which doesn’t come on a standard install of Apache. I ended up compiling it from source (learned a lot about compiling Apache shared modules) but that didn’t work for me either.

Eventually I realized that the only time my requests were being proxied to the BalancerMembers was when I was at the URL root, for example tacking on an ‘index.php’ to the URL would generate the same error as trying to access ‘balancer-manager’. And about that time I stumbled upon a user who was able to solve their problem by adding a few rewrite rules… which caused me to finally understand part of my problem.

The reason ‘balancer-manager’ wasn’t loading correctly was because that request was being proxied to the BalancerMember, which didn’t have ‘balancer-manager’ enabled (hence the ‘no protocol handler error’). This still didn’t explain why ‘index.php’ wasn’t working, either, since I knew index.php was on the BalancerMembers.

These discoveries quickly culminated into leading me to my solution, which was a missing trailing-slash in my ProxyPass directive. That’s what was causing my ‘index.php’ to not be proxied. Once I realized the problem was in my ProxyPass– reading the documentation also taught me that there’s a way to keep certain requests from being proxied (which solved my ‘balancer-manager’ issue).

So I present to you, my completed (and functional) balancer.conf. And remember, the trailing slash at the end of the ProxyPass directive IS CRUCIAL!

ProxyPass /balancer-manager !  # Prevents balancer-manager from being proxied.
ProxyPass / balancer://mycluster/
ProxyPassReverse / balancer://mycluster/

<Proxy balancer://mycluster>
  BalancerMember http://10.0.0.10 loadfactor=2
  BalancerMember http://10.0.0.11
  BalancerMember http://10.0.0.12
</Proxy>

<Location /balancer-manager>
  SetHandler balancer-manager
  Order Allow,Deny
  Allow from all
</Location>

I hope this saves someone some time when searching on the particular error message I got. Now I’m off to figure out how to get my web servers to auto register with the balancer-manager. I will blog about it when I figure it out.

Categories : Apache Web Server, Architecture, HTTP Servers, Scalability, client/server
Comments (0)
Dec
22

JUICE chat (Stratus Peer to Peer)

Posted by: jwd | Comments (0)

One of my ongoing weekend projects is an AIR chat application, recently rechristened “JUICE” a.k.a. “John’s Ultimate Internet Chat Experience”. I realize that may only be the case for myself, but the project has been very educational and entertaining for me.

The initial release used AIR/Flex as the front-end, ColdFusion and MySQL for the backend, and BlazeDS messaging for message transmission and “peer discovery”.  I worked on it over a couple weeks adding features like encryption, toast-style notifications, as well as a few other less notable things.

I knew the architecture was awful and would never scale beyond a couple hundred users (if that many). Here’s the basics of how it worked:

  1. The user would login, ColdFusion/MySQL would send back a list of chat buddies.
  2. Each logged in user would ping the messaging server every 10-30 seconds to announce “I’m still here!”
  3. The pings would be broadcast to everyone logged in (and connected to the messaging server). The application would filter out the ones it was interested in based on user id.
  4. Sending messages worked similarly, the user would send the message (which included information about the intended recipient), which would then be broadcast to EVERYONE.
  5. Everyone would filter this information, and the recipient would know it was for him, and would display it.

You can see why this would not scale well, the amount of traffic and pinging going on.  Eventually even a user with only 1 or 2 buddies would notice a significant drop in performance as the number of total users increased.  There are some applications of “chat” for which BlazeDS messaging and polling is well suited for (like a chat room).  A chat application involving “buddies”  is much better suited for P2P.

Enter Stratus.  I discovered Stratus a few months ago but was unwilling to delve into it on account of my hectic schedule.  Fortunately one of my friends went and did all the dirty work figuring out how to get it up and running (turns out it’s pretty easy) and I was able to apply it to my chat client.

I’m very pleased with the results, though it’s still very much a work in progress.  I’ve even been able to add video and voice chat features, which is why I went through the trouble in the first place.  Here is how my peer discovery mechanism operates.  It requires only 3 calls to the server per client (which I could easily condense to 2 if I wanted).

  1. The user logs in.
  2. If the user successfully authenticated, he connects to Stratus and receives his peer ID.
  3. The user updates his peer ID in the database (which was set to 0) and gets a list of his buddies and their peer IDs.  I also update information like whether or not I have a camera/mic.
  4. The user processes his buddy.  If the peer ID is something other than 0, then I know this buddy logged in before me, so I must send him my peer ID.
  5. Any buddies that have a peer ID of 0 are not logged in, I need to listen for them though since they will get my peer ID when they log in.
  6. When I log out or close the application, I call the server and reset my peer ID to 0.  I also notify all of my connected peers I’m logging out.

There are a few more steps in the process, but it decreases the load on the server significantly, transferring the “burden of proof” onto the clients.

Interesting to note is the way I get the peer ID of a client who connects after me (step 4).  I don’t actually have to make a call for this, rather he simply has to start broadcasting to me on the NetStream I set up to listen for him (step 5).  When the connection is made, an event will be dispatched by the NetStream class.  In fact, 3 events will be fired, I just chose to act on one of them.

The peer ID can be obtained from the peerStreams array on the publishing NetStream.  I then use that information to set up my subscribing NetSream.

if (this.peerId == null || this.peerId == &amp;quot;0&amp;quot;) {
    this.peerId = publishNetStream.peerStreams[0].farID;

    this.subscribeNetStream = new NetStream(model.netConnection, this.peerId);
    this.subscribeNetStream.client = new NetStreamClient();
    this.subscribeNetStream.play(streamName);
}

Anyway,I’m planning on doing another post to outline my video and voice handshake protocols.  They’re nothing to write home about, but I’d love some feedback if there’s a better way.  I’m also planning on releasing JUICE after I bring it’s level of functionality back up to where the original version was, so stay tuned and you should see my install badge at the bottom of my blog.

Categories : AIR, Actionscript, Architecture, Cold Fusion, ColdFusion Enterprise Server, Flex 3, Messaging, Scalability
Comments (0)
Aug
19

Contextual Shortcut Manager

Posted by: jwd | Comments (0)

I’m about ready to sleep on my ShortcutManager, I figured I might as well finish off the series. I added context to it, while still maintaining backward compatibility with the non-context enabled version. I don’t think I posted it, so it probably won’t matter so much to my readers. For those who haven’t read the previous posts on my ShortcutManager, the “context” I’m referring to is the context in which a keyboard shortcut might be used; such that a given shortcut might have several functions associated with it depending on its context.

In short I needed four things to implement the context aware shortcut manager.

1. The IKeyboardContext interface

This is a simple interface that a model or class can implement that allows a keyboardContext to be set and retrieved. A keyboardContext is simply a string that identifies the current context. These contexts will be associated with a shortcut in the manager. Here is the entire interface:

package com.googolflex.gflib.interfaces {

	public interface IKeyboardContext {

		[Bindable(event="propertyChange")]
		function get keyboardContext() : String;
		function set keyboardContext(v : String) : void;
	}
}

2. A reference to an IKeyboardContext needed to be added to the ShortcutManager

As the keyboardContext changes in the model, the ShortcutManager needs to be able to access it, so it can retrieve the correct function for a given keycode-flags-context tuple. The IKeyboardContext was left null, which helps with the backward compatibility (the implementer will be forced to set it if she wants to use it). Here is the line I added, which is pretty trivial:

public static var model : IKeyboardContext = null;

3. Add the context to the addShortcut method

I gave it a default value, which incidentally was “default”, so that every function added to the Dictionary would have some kind of context. Here is the new addShortcut() method:

public static function addShortcut(keycode : uint, func : Function, flags : uint, context : String = "default") : void {
	if (flags > 0 && flags < 8)
		functionMap[keycode + "-" + flags + "-" + context] = func;
}

4. Finally, the shortcutHandler() needed to incorporate the context

I first check if the IKeyboardContext is null, if it is I know to use “default” as the context. If no context was ever specified, and no IKeyboardContext was ever set, then it still works. Here is the modified code:

public static function shortcutHandler(event : KeyboardEvent) : void {
	var flags : uint = getFlags(event);
	var context : String = (model!=null) ? model.keyboardContext : "default";

	if ( functionMap[event.keyCode + "-" + flags + "-" + context] != null )
		(functionMap[event.keyCode + "-" + flags + "-" + context] as Function).apply();
}

And was pretty much it. I will post the modified code, along with a demonstration application at the end of the post. I do want to make a couple points to be aware of when actually incorporating this into a project.

First, your model/class will need to implement IKeyboardContext. Second, you need to remember to set ShortcutManager.model = myIKeyboardContext somewhere. Third, you’re on your own when it comes to maintaining your context. Maybe the “SimpleContextManager” will be next, who knows? And fourth, you’ll have to specify a context when you call addShortcut(). Remember if you download the demo, it will need to have some kind of reference to the ShortcutManager code.

Code
ShortcutManager.zip
KeyboardContextDemo.zip

Categories : Actionscript, Architecture, Flex 3, Flex 4, Scalability
Comments (0)
Aug
19

Simple Flex ShortcutManager (revisited)

Posted by: jwd | Comments (0)

I’ve put some more thought into my shortcut manager, and have decided on a way to implement the shortcut context, as well as curb the Dictionary explosion that my previous architecture would have had.

The new version uses one Dictionary to store all functions, the keyCode, combo keys, and context is all encoded into the dictionary key. To illustrate here is my new addShortcut() method:

public static function addShortcut(keycode : uint, func : Function, pair : String) : void {
	var flags : int = getFlagsFromString(pair);

	if (flags > 0)
		functionMap[keycode + "-" + flags] = func;
}

It accepts the same parameters, the only difference is that the pair string is now a dash delimited string, containing the key combinations (i.e. “ctrl“, “ctrl-alt“, “shift-ctrl“, etc). Those are parsed by a method getFlagsFromString() which I will probably get rid of in favor of some named constants on the ShortcutManager class. That will eliminate the need for the additional method, and pair (which will be one of said constants) can be added to the key as-is.

The removeShortcut() is very similar. I haven’t implemented “contexts” yet, but you can visualize how it will be done:

functionMap[keycode + "-" + flags + "-" + context] = func;

Now that there is only one Dictionary, the shortcutHandler() method has become almost trivial. Here it is, in all of its 5-lined glory:

public static function shortcutHandler(event : KeyboardEvent) : void {
	var flags : uint = getFlags(event);

	if (functionMap[event.keyCode + "-" + flags] != null)
		(functionMap[event.keyCode + "-" + flags] as Function).apply();
}

Like I said… trivial.

Categories : Actionscript, Architecture, Flex 3, Flex 4, Scalability
Comments (0)
Aug
19

Using Bit-wise Math to Simplify Logic

Posted by: jwd | Comments (0)

Strictly speaking, bit strings are not nearly as easy to understand as named boolean variables; but there are situations when they can simplify or eliminate the need for long boolean expressions.

Consider my recent post about the ShortcutManager. There are three control keys that may or may not be pressed at any given time. This amounts to 8 possible key combinations (7 if you ignore when none of them are pressed). Here’s what this would look like if you used a boolean expression to check for each of the possible combinations:

var ctrl : Boolean = event.ctrlKey;
var alt : Boolean = event.altKey;
var shift : Boolean = event.shiftKey;

if (!shift && !alt && ctrl) {
	// CTRL
}
else if (!shift && alt && !ctrl) {
	// ALT
}
else if (!shift && alt && ctrl) {
	// CTRL-ALT
}
else if (shift && !alt && !ctrl) {
	// SHIFT
}
else if (shift && !alt && ctrl) {
	// CTRL-SHIFT
}
else if (shift && alt && !ctrl) {
	// SHIFT-ALT
}
else if (shift && alt && ctrl) {
	// CTRL-SHIFT-ALT
}

That might not seem too bad, and it isn’t that hard to understand what’s going on (even without the comments). But what happens when you’ve got 4 variables (16 possibilities)? The number of combinations increase exponentially. Even if the combinations you are actually interested in are sparse, who wants to type out 10 lines each with 32 boolean variables?

With a simple method to construct my “bit string”, we can greatly simplify this. ActionScript doesn’t have a binary number type, so you’ll have to use integers. If you can count by powers of two you won’t have any trouble. Here’s a method that constructs my “bit string” integer based on selected control keys:

private static function getFlags(event : KeyboardEvent) : uint {
	var flags : uint = 0;
	flags += (event.ctrlKey) ? 1 : 0;
	flags += (event.altKey) ? 2 : 0;
	flags += (event.shiftKey) ? 4 : 0;

	return flags;
}

Having this integer allows me to simplify the logic as follows (which isn’t harder to understand at all, if you use comments):

switch ( getFlags(event) ) {
	case 1: // CTRL
		break;
	case 2: // ALT
		break;
	case 3: // CTRL-ALT
		break;
	case 4: // SHIFT
		break;
	case 5: // CTRL-SHIFT
		break;
	case 6: // SHIFT-ALT
		break;
	case 7: // CTRL-SHIFT-ALT
		break;
}

This is powerful, but it’s just scratching the surface. I haven’t even discussed the bitwise operators &, | and ^. Or the bitwise shift operators >> and <<. These come in handy when you store a series of options as a bit string and need to test whether a certain option is on or off.

For example, if we needed to test if value returned by getFlags() had the SHIFT bit set, we could use the following boolean expression to test it. Remember SHIFT = 4:

if (getFlags(event) & 4 == 4)

Of course this doesn’t help us out much now in the ShortcutManager, but assume I was to add 3 possible contexts that these shortcuts might be used in (different screens, for example). They would hold the 8, 16, and 32 bit places. I could use bitwise math to split them if I only needed to manipulate one them at a time (a stretch, I know).

var screenOnly: uint = flags & 56;
var keysOnly: uint = flags & 7;

They have a variety of uses and are there when you need them. There’s an excellent explanation of various bitwise operations and their uses here.

Categories : Actionscript, Design Patterns, Flex 3, Flex 4, Scalability
Comments (0)
Jul
21

Changing Default Minimum Height of ScrollThumb

Posted by: jwd | Comments (0)

The default minimum height of the scroll thumb is about 10 pixels, which my client felt was a little too small (their data sets usually run into the several-hundreds, which is enough to push it to the minimum at most resolutions). So I set about trying to fix the problem.

There was no style or property I could set on the VScrollBar to control this, so I went to the ScrollThumb. There was no global style I could set on the class to control this, and if I extended the ScrollThumb and manually set it, there was no way for me to easily tell my VScrollBar to use this version of the ScrollThumb.

Long story short, I had to change the framework code. I changed line 48, where explicitMinHeight was set, to something more agreeable for my client. I was pretty disappointed about this, because it has some unfortunate side effects (which I could probably correct if I had the time). Specifically, if there’s a scroll bar that can’t fit the newly resized ScrollThumb, it doesn’t show one at all. This seems most common in short TextAreas.

Categories : Actionscript, Architecture, Flex 2, Flex 3, Monkey Patching, Scalability
Comments (0)
Next Page »

Search

Feedburner

Subscribe to

Get the latest updates delivered via email

Calendar

July 2010
M T W T F S S
« Jun    
 1234
567891011
12131415161718
19202122232425
262728293031  

Archives

  • July 2010 (1)
  • June 2010 (2)
  • May 2010 (1)
  • February 2010 (11)
  • January 2010 (3)
  • December 2009 (5)
  • November 2009 (1)
  • August 2009 (8)
  • July 2009 (8)
  • May 2009 (4)
  • April 2009 (1)
  • March 2009 (6)
  • January 2009 (1)
  • November 2008 (4)
  • October 2008 (5)
  • September 2008 (1)
  • August 2008 (5)
  • July 2008 (1)
  • June 2008 (2)
  • May 2008 (8)
  • April 2008 (5)
  • March 2008 (2)
  • February 2008 (3)
  • January 2008 (1)
  • December 2007 (6)
  • November 2007 (9)
  • October 2007 (1)
  • September 2007 (2)

Categories

Tag Cloud

adobe apache Architecture book review C++ centos client server architecture Custom Components database Design error message fedora flash catalyst flex Flex 3 Flex 4 fms iis 6 Interaction Design load balancing master-master master-slave mod_proxy_balancer Monkey Patching MySQL no protocol p2p peer to peer Perl PHP Red5 regex replication self registration selinux Shell Scripting shortcut manager skins socket policy file sockets states stored procedures stratus tools workflow

Coworkers

  • Casey Jackman
  • Sean Murphy

Family

  • Emily & CJ
  • Family Blog
  • Gary Dusbabek

Meta

  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org

RSS FlexExamples

  • Setting the header height on a Spark Panel container in Flex 4
  • Adding a hover glow filter to an MX Image control in Flex 4
  • Fading an item renderer background fill on a Spark List control in Flex 4
  • Setting the border color on the MX Accordion container headers in Flex
  • Setting the tab width on an MX TabNavigator container in Flex 3

Spam Blocked

842 spam comments
blocked by
Akismet

Sponsored Links

JUICE Chat

BYU Adobe Users Group


Copyright © 2010 All Rights Reserved
Flexx Theme by iThemes
Powered by WordPress