Communicating with Vic/Vat via the Conference Bus

As described in McCanne, S., and Jacobson, V., vic: A Flexible Framework for Packet Video , ACM Multimedia '95, a multicast channel, the Conference Bus, can be used to exchange messages between processes on a single host.

confcntlr uses the conference bus so that:

  1. vic/vat can tell confcntlr that vic/vat was terminated (e.g.,the user clicked the "quit" button in the vic/vat main window).
  2. vic/vat can tell confcntlr that a setting was changed (e.g., the user manipulated a control in the vic or vat window).
  3. confcntlr can tell vic or vat that to change a setting (e.g., bandwidth, framerate, format, device) or turn on/off transmission.

To implement the conference bus for confcntlr, I followed these general steps:

  1. At startup, confcntlr creates UDP sending and receiving sockets for local multicast with the following routines (for brevity, error-checking is omitted from this example):
    /* USE THE SAME CONSTANTS FOR ALL PROGRAMS ON THE CONFERENCE BUS. */
    #define PORT                0xdeaf
    #define GROUP_ADDR 0xe0ffdeef

    /* global variables */
    int rsock, ssock;
    struct sockaddr_in peer;
    struct ip_mreq mr;

    int make_receiver(int channel)
    {
       int s, on=1;

       s = socket(AF_INET, SOCK_DGRAM, 0);
       setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));
       memset((char *)&peer, 0, sizeof(peer));
       peer.sin_family = AF_INET;
       peer.sin_port = htons(PORT + channel);
       peer.sin_addr.s_addr = htonl(GROUP_ADDR);
        if (bind(s, (struct sockaddr *)&peer, sizeof(peer)) < 0)
       {
           peer.sin_addr.s_addr = htonl(INADDR_ANY);
           bind(s, (struct sockaddr *)&peer, sizeof(peer));
       }
        /* Here, make socket s nonblocking with fcntl(). */
        mr.imr_multiaddr.s_adddr = htonl(GROUP_ADDR);
        mr.imr_interface.s_adddr = htonl(INADDR_ANY);
        setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mr, sizeof(mr));
        /* Here, create an event handler for s with a READABLE flag.
            Using Tcl/Tk under UNIX, I called Tk_CreateFileHandler() with
            flag = TK_READABLE; callback = dispatch() */
        return s;
    }
    int make_sender(int channel)
    {
       int s, ttl=0;
       long addr;

        s = socket(AF_INET, SOCK_DGRAM, 0);
        peer.sin_port = 0;
        peer.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
        bind(s, (struct sockaddr *)&peer, sizeof(peer));
        peer.sin_port = htons(PORT + channel);
        peer.sin_addr.s_addr = htonl(GROUP_ADDR);
        connect(s, (struct sockaddr *)*peer, sizeof(peer));
        setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&addr, 4);
        return s;
    }
    I modeled my source code after vic (referring to confbus.cc, group-ipc.cc, and iohandler.cc in the vic or vat source code.) The confcntlr routines are in confcntlr's confbus.h and confbus.c files.

    The "channel" argument to make_sender() and make_receiver() have the same value used with vic or vat's "-I channel" command line option. This channel is the conference bus connection that vic and/or vat will be connected to and should be any integer > 0. (See the vic and vat man pages.)

  2. When sending or receiving conference bus messages over the sockets, confcntlr uses the same data struct, header, and constants as vic and vat. If "loopback" is on, the sender will get a copy of its own message. So, the header contains the sender's process identifier and, upon receipt of a conference bus message, confcntlr checks to see if the header value is the same as its own process identifier (in which case the message is discarded). A code sample is as follows:
    #define GIPC_MAGIC 0x0ef5ee0a
    struct ipchdr {
       unsigned int magic;
       u_short type;
       u_short pid;
    };

    char buf[1024];

    /* The actual routine in confcntlr's confbus.c uses a Tcl and C
        interface and includes error-checking */
    int MsgSend(int sock, char *msg)
    {
       int len;
       struct ipchdr *g = (struct ipchdr *)buf;

       g->type = 0;
       g->pid = getpid();
       g->magic = GIPC_MAGIC;
        /* Copy msg into (buf + sizeof(ipchdr)) */
       send(ssock, (char *)buf, len, 0);
       return 0;
    }

    /* The function below is called from a file handler when rsock is
         readable.
         Its arguments are those required by Tk_CreateFileHandler() */
    void dispatch(ClientData fd, int mask)
    {
       int cc;
       struct ipchdr *g = (struct ipchdr *)buf;

       cc = recv((int)fd, buf, sizeof(buf));
        /* Extract header information and call a function to process the
            message if we're going to do so */
       cc -= sizeof(struct ipchdr);
        /* Ignore bogus messages; process messages not our own */
       if (ntohl(g->magic) != GIPC_MAGIC)
           return;
       if (ntohs(g->pid) != getpid())
           /* Call a function to deal with the actual message. */
           ipc_input((char *)(g+1));
    }
  3. To send messages to vic or vat, the sending application must define a message type that vic or vat will recognize and define a procedure to invoke when vic or vat receives a message of that type. At startup, vic and vat will evaluate a user-written Tcl script if the script's path is specified by the "-U pathname" command line option. Confcntlr writes such a script (e.g., /tmp/vic-tcl or /tmp/vat-tcl) to set defaults and uses this script to define conference bus message types and procedures. Vic and vat each use a global Tcl array, "cb_dispatch", with conference bus message types for the indices and Tcl procedures for the values (refer to the confbus.tcl file in the vic and vat source code).

    When a user changes a setting in a confcntlr window, confcntlr sends a message on the conference bus to vic (or vat) so that vic (or vat) can update its value and invoke whatever procedures it would invoke if the user had changed the same setting in the vic (or vat) window. Confcntlr defines this message type as "change-params", writes a "proc changeParameters" in its Tcl script, and tells vic (or vat) to invoke this procedure whenever it receives a change-params message. An example of the conference bus information in this Tcl file is below.

           global cb_dispatch
           set cb_dispatch (change-params) changeParameters
           proc changeParameters {p value pid fmt} {
               if { $pid != [pid] } {
                   return
               }
               if { $p == "bandwidth" } {
                   option add Vic.bandwidth $value 50
                   global bps_slider
                   $bps_slider set $value
                   set cmd [$bps_slider cget -command]
                   eval $cmd $value
                } elseif { $p == "framerate" } {
                    #do something else...
                }
                etc...
           }

    The actual code is in confcntlr's uimain.tcl file (in its writetclfile and writevatfile procedures).

  4. To ask vic/vat to send a message to confcntlr, I write a procedure in the vic (or vat) Tcl script described above and tell vic (or vat) when to call this procedure. Confcntlr reconfigures the vic (or vat) widget it is interested in so that the widget is bound to the command written by confcntlr. (The user_hook procedure is used for this purpose.) This new command calls the same routines that vic (or vat) normally calls and then sends confcntlr a message of the type that is specified. For example, since confcntlr wants to know if the user clicked the "quit" or "transmit" button in the vic window, confcntlr adds the following procedures to the vic Tcl script. (See the writetclfile and writevat file procedures in confcntlr's uimain.tcl for the actual code.)

           proc user_hook { } {
               # Make sure the transmit checkbutton exists.
               build.menu
               global transmitButton
               $transmitButton config -command changeXmit
               .top.bar.quit config -command send_adios
           }

           proc changeXmit { } {
               global V transmitButtonState
               transmit
               $V(cb) send "paramChange transmit $transmitButtonState [pid]"
           }

           proc send_adios { } {
               global V
               $V(cb) send "adios [pid]"
               adios
           }

  5. When confcntlr receives a conference bus message from vic/vat, it extracts the message type and calls a function to process that message type. For example, dispatch() in the code sample for step 2 calls ipc_input, which is below.

    void ipc_input(char *msg)
    {
       char cmd[100];

       /* Parse off field1 of msg and store in cmd. */
        if (!strcmp(cmd, "adios"))
           Tcl_VarEval(myinterp,"cb_adios",(msg+strlen(cmd)+1), 0);
        else if (!strcmp(cmd, "paramChange"))
           Tcl_VarEval(myinterp,"cb_paramChange",(msg+strlen(cmd)+1), 0);
    }


Good luck in getting on the conference bus!
--Marcia