Analyzing the data stream created by function calls, the following structure has to be implemented:

RTMP header:

first byte :

    0x03 in case of a 12 byte length header
    or
    0x43 in case of a 8 byte length header
    or
    0xC3 in case of a 0 byte length header

next three bytes:

    unknown, can be 0x00

next three bytes:

    body length, without inter-chunk headers
    maximum body size is FFFFFF = 16777215 bytes
   
next byte:

    body type
   
    0x14 : invoke with AMF data
  
next four bytes:

    unknown, can be 0x00
   
Body bytes in case of invoke

    invoke identifier encoded as an AMF string
    followed by a number in double precision floating point number ( maybe result request? )
    followed by the AMF-encoded arguments

    at connection, an AMF encoded object with compulsory properties needed:
    - app : the application identifier to connect to
    - swfURL : referrer of the swf
    - flashVer : agent
    - audioCodecs
    - videoCodes
    - pageURL

    body have to be split up into 128-byte length chunks  inserting a  0-byte rtmp header ( 0xC3 ) between chunks
   
AMF encoding

    String:
   
        0x02 followed by the size of the string on two bytes, max length: 65535
   
    null:
   
        0x05
       
    Object
   
        0x03 closed by 0x00 0x00 0x09
        key - value pairs are defined inside them, keys as amf strings
        values as amf encoded data

Fortunately we don't need to know how to encode AMF data, because ByteArray has a built-in AMF encoder function, and it is also faster than a simple implementation.

And that's all. Let's see how to realize it:

package
{
   
    import flash.net.Socket;
    import flash.net.ObjectEncoding;
    import flash.utils.ByteArray;
   
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.SecurityErrorEvent;
   
    import flash.display.Sprite;    
   
   
    public class OpenRTMPConnection extends Sprite
    {
       
        // connection status : inactive, pending, handshake, active
       
        public var status : String;
        public var socket : Socket;
       
       
        public function OpenRTMPConnection ( )
        {
           
            // reset status
           
            status = "inactive";
           
            // create new socket
           
            socket = new Socket( );
           
            // initializing events
           
            socket.addEventListener( Event.CLOSE , onClose );
            socket.addEventListener( Event.CONNECT , onConnect );
            socket.addEventListener( IOErrorEvent.IO_ERROR , onIOError );
            socket.addEventListener( ProgressEvent.SOCKET_DATA , onData );
            socket.addEventListener( SecurityErrorEvent.SECURITY_ERROR , onSecurityError );
           
            // connect
           
            socket.connect( "localhost" , 1935 );
           
        }

       
        public function onClose ( event:Event ):void
        {
           
            trace( "onClose: " + event );
           
        }
       
       
        public function onIOError ( event:IOErrorEvent ):void
        {
           
            trace( "onIOError: " + event );
           
        }
       
       
        public function onSecurityError ( event:SecurityErrorEvent ):void
        {
           
            trace( "onSecurityError " + event );
           
        }
       
       
        public function onData ( event:ProgressEvent ):void
        {
           
            trace( "onData " + status );
           
            switch ( status )
            {
               
                case "active" : break;
                case "pending" : sendConnection( ); break;
                case "inactive" : break;
                case "handshake" : openConnection( ); break;
               
            }            

        }
               
       
        public function onConnect ( event:Event ):void
        {
           
            trace( "onConnect" );
           
            status = "pending";
           
            var count : int = -1;            
            var bytes : ByteArray = new ByteArray( );
           
             // send first handshake : 0x03 followed by 1536 bytes
            // write header byte
           
            bytes.writeByte( 0x03 );
           
            // write 1536 random ( zero in my case ) bytes
           
            while ( ++count < 1536 ) bytes.writeByte( 0x00 );
           
            socket.writeBytes( bytes );
           
            // send data
           
            socket.flush( );
           
        }
       
       
       
        public function sendConnection ( ):void
        {
           
            status = "handshake";
           
            // create instances
           
            var agentInfo : Object = new Object( );            
            var copyBytes : ByteArray = new ByteArray( );
            var rtmpBytes : ByteArray = new ByteArray( );
            var bodyBytes : ByteArray = new ByteArray( );
           
            // CREATE PLAYER SETTINGS

            // application to connect to
            agentInfo["app" ] = "milgra";
           
            // referrer
            agentInfo["swfUrl" ] = "Kilroy was here...";
           
            // page url
            agentInfo["pageUrl" ] = "She sells sea shells...";            
           
            // agent
            agentInfo["flashVer" ] = "DOS 5.00 with Norton Commander";
           
            agentInfo["tcUrl" ] = "rtmp://localhost/milgra";
            agentInfo["audioCodecs" ] = 615;
            agentInfo["videoCodecs" ] = 76;
            agentInfo["videoFunction" ] = 0;
            agentInfo["objectEncoding" ] = 0;
           
            // CREATE BODY
           
            // we use ByteArray's built-in AMF encoder, amf0 is needed
           
            bodyBytes.objectEncoding = ObjectEncoding.AMF0;
           
            // write method id
            // ( AMF String id is 0x03 then string length on 2 bytes, then UTF-encoded string )
           
            bodyBytes.writeObject( "connect" );
           
            // a 64-bit double-precision floating point number is next
            // ( AMF Number is 0x00 then number on 8 bytes as a signed, little - endian encoded
            // double precision floating point number )
           
            bodyBytes.writeObject( 1 );
           
            // encoding agent info
            // ( AMF object starts with the id 0x03 , ends with 0x00 0x00 0x09,
            // keys are AMF strings without starting 0x03, values are standard AMF values
           
            bodyBytes.writeObject( agentInfo );
           
            // CREATE FIRST chunk
           
            // cloning second 1536 bytes from server respose
           
            socket.readByte( );
            socket.readBytes( copyBytes , 0 , 1536 );
            socket.readBytes( copyBytes , 0 , 1536 );
           
            // CREATE RTMP header
           
            // first byte, header size is 12 byte
           
            rtmpBytes.writeByte( 0x03 );
           
            // next three bytes
           
            rtmpBytes.writeByte( 0x00 );
            rtmpBytes.writeByte( 0x00 );
            rtmpBytes.writeByte( 0x00 );
           
            // body size in three bytes
           
            var firstByte : int = bodyBytes.length >> 16;
           
            rtmpBytes.writeByte( firstByte );
            rtmpBytes.writeShort( bodyBytes.length );
           
            // body type in one byte = 0x14 == invoke
           
            rtmpBytes.writeByte( 0x14 );
           
            // last four bytes
           
            rtmpBytes.writeByte( 0x00 );
            rtmpBytes.writeByte( 0x00 );
            rtmpBytes.writeByte( 0x00 );
            rtmpBytes.writeByte( 0x00 );
           
            // split body into 128 byte chunks with zero-byte rtmp headers
           
            var counter : int = -1;
            var newBody : ByteArray = new ByteArray( );
           
            bodyBytes.position = 0;

            while( ++counter < bodyBytes.length )
            {

                // write header
               
                if ( counter % 128 == 0 && counter != 0 ) newBody.writeByte( 0xc3 );
               
                // copy byte
               
                newBody.writeByte( bodyBytes.readByte( ) );
               
            }            
           
            // CREATE MESSAGE
           
            socket.writeBytes( copyBytes );
            socket.writeBytes( rtmpBytes );
            socket.writeBytes( newBody );
           
            // send data
           
            socket.flush( );
           
        }
       
        public function openConnection ( ):void
        {
           
            trace( "openConnection" );
           
            // server log :
            // agent:  DOS 5.00 with Norton Commander
            // referrer: Kilroy was here...
           
        }
       
    }
   
}

   
A few useful links on RTMP and AMF:

http://osflash.org/documentation/rtmp

http://osflash.org/_media/rtmp_spec.jpg
http://osflash.org/documentation/amf/astypes
http://osflash.org/documentation/amf/datatypes
http://osflash.org/documentation/amf3