IPC System

Inter Process Communication (IPC) is a must have feature for all operating systems. NeuroKernel has various IPC ports to play with including sockets and pipes. However the IPC mechanisms introduced with NeuroKernel can only be used between NeuroKernel Tasks. It is possible to implement a port that communicate with other systems using web sockets. In IPC system, event pipeline is used for almost all communications. Task container processes and packs the events accordingly and redirects them to their intendant destinations.

1. Ports

Ports are used as bidirectional communication channel between tasks. It is possible to implement custom ports that can make use of other external connections. Developers are free to implement their own. Unregistered remote applications can only listen to named pipes. Only trusted remote applications can craete and listen to any port. Port authority automatically closes open ports when an application terminates. Port number will be between 0-65535 for sockets, and larger than 65535 for pipes. It will return -1 for web sockets.

1.1 Port Manager

Port manager is responsible with opening, listening and closing ports. There are two legs in the port management, first happens at the task container level. Second happens in the kernel level, and If all is good the request is dispatched to kernel from task container where port authority module decides whether the request is granted or not. IPortManager interface object is used for all port related operations.

   import com.neurokernel.client.*;
   import com.neurokernel.client.io.*;
   import com.neurokernel.client.adapter.NPortListener;
 
   public class DataListeners extends NApplication {
       @Override
       public void main(int argc, String[] argv) {
           getSystem().getPortManager().listenSocket(300, new NPortListener() {
               @Override
               public void onData(IPort port) {
                   getConsole().println(port.read());
               }
 
               @Override
               public void onConnect(IPort port) {
                   port.write(this.toString());
               }
 
               @Override
               public void onError(IPort port) {
                   getConsole().println("Failed to open port:"+port.getPortNumber());
               }
          });
       }
   }

1.2 Sockets

Sockets are ports which are identified by a number. Maximum open ports allowed is 65536. There is always a server port and client ports connecting to it. Server socket is opened with createSocket method of IPortManager interface object. Another task may make a request to listen to the opened port using the same interface. Unregistered remote applications cannot open sockets. IPort interface passed to the port listener can be cast to ISocket.

   import com.neurokernel.client.*;
   import com.neurokernel.client.system.*;
   import com.neurokernel.client.adapter.NPortListener;
 
   public class SocketServer extends NApplication {
       @Override
       public void main(int argc, String[] argv) {
          getSystem().getPortManager().createSocket(300,new ServerSocketListener());
       }
 
       public class ServerSocketListener extends NPortListener {
           @Override
           public void onData(IPort port) {
               getConsole().println(port.readString());
               port.writeString("How are you?");
           }
       }
   }
   import com.neurokernel.client.*;
   import com.neurokernel.client.system.*;
 
   public class SocketClient extends NApplication {
       @Override
       public void main(int argc, String[] argv) {
          getSystem().getPortManager().listenSocket(300,new ClientSocketListener());
       }
 
       public class ClientSocketListener implements IPortListener {
           @Override
           public void onConnect(IPort port) {
                port.writeString("Hello");
           }
 
           @Override
           public void onData(IPort port) {
                getConsole().println(port.readString());
           }
 
           @Override
           public void onError(IPort port) {
              getConsole().println("Socket Error");
           }
 
           @Override
           public void onOpen(IPort port) {
              getConsole().println("Socket Opened");
           }
 
           @Override
           public void onClose(IPort port) {
              getConsole().println("Socket Closed");
           }
       }
   }

1.3 Pipes

Unlike sockets, pipes are named ports. They require a name to open and listen to. Same IPortManager interface object is also used with the pipes. Similar to socket, there is serving and listening end for each pipe. Server pipe is opened with createPipe method of IPortManager interface object.IPort interface passed to the port listener can be cast to IPipe.

 import com.neurokernel.client.*;
 import com.neurokernel.client.system.*;
 import com.neurokernel.client.adapter.NPortListener;
 
 public class PipeServer extends NApplication {
     @Override
     public void main(int argc, String[] argv) {
         IPortManager portManager=getSystem().getPortManager();
         portManager.createPipe("MyPipe",new NPortListener() {
                @Override
                public void onOpen(IPort port) {
                     getSystem().getPortManager().createPublicPort(port, true);
                }
          });
 }
 import com.neurokernel.client.*;
 import com.neurokernel.client.system.*;
 import com.neurokernel.client.adapter.NPortListener;
 
 public class PipeClient extends NApplication {
     @Override
     public void main(int argc, String[] argv) {
        IPortManager portManager=getSystem().getPortManager();
        portMgr.listenPipe("MyPipe", new NPortListener() {
               @Override
                public void onOpen(IPort port) {
                    getConsole().print("Connected to MyPipe...");
               }
        });
 }

1.4 Peer Ports

Peer ports can be opened if there is a working webtorrent tracker. There are some free webtorrent trackers but using a private tracker may be needed for some use cases. NeuroKernel comes preconfigured with open trackers. Please see Bittorrent Tracker to easily create your own Bittorrent tracker. Creating and listening to a peer port is similar to sockets and pipes. Same IPortManager interface is used. It is possible to configure the peer port by passing an NPortConfig object to the corresponding methods. IPort interface passed to the port listener can be cast to IPeerPort.

1.5 Public Ports

Applications can create public ports which can also be used by remote applications. These public ports may also be an access to a public device which may work without a need to plug the public port owner device to any application. The device will be a singleton device, and will not be replicated for new connections. It may make more sense to make it a read only device.

 import com.neurokernel.client.*;
 import com.neurokernel.client.system.*;
 import com.neurokernel.client.adapter.NPortListener;
 
 public class PublicPortServer extends NApplication {
     @Override
     public void main(int argc, String[] argv) {
         IPortManager portManager=getSystem().getPortManager();
         portManager.createPipe("MyPublicPipe",new NPortListener() {
                private static final long serialVersionUID = 1L;
                @Override
                public void onOpen(IPort port) {
                     getSystem().getPortManager().createPublicPort(port, true);
                }
          });
 }
 import com.neurokernel.client.*;
 import com.neurokernel.client.system.*;
 import com.neurokernel.client.adapter.NPortListener;
 
 public class PublicPortClient extends NApplication {
     @Override
     public void main(int argc, String[] argv) {
        IPortManager portManager=getSystem().getPortManager();
        portManager.listenPublicPipes("MyPublicPipe");
        getSystem().addListener(new NServicePortListener() {
            @Override
            public void onConnect(IPublicPort servicePort) {
                NDevice device=servicePort.getDevice();
            }
        },NSystemSignals.LISTEN_PUBLIC_PORT);
 }

1.6 Media Ports

Media ports can be used to establish live video feed over peer to peer networking. Media ports work on peer ports. As seen below user, interface prototyping may be very compact so that the business logic can be seen more in the code. This is one of the more complex examples in this documentation to show the capabilities of NeuroKernel API.

import com.neurokernel.client.*;
import com.neurokernel.client.io.*;
import com.neurokernel.client.system.*;
import com.neurokernel.client.adapter.NPortListener;
 
public class MediaPortDemo extends NApplication implements IEventListener {
    IText sdpData;
    IText answer;
    IMediaPort mediaPort;
    boolean initiator;
 
    @Override
    public void main(int argc, final String[] argv) {
        NArguments args = argc > 0 ? new NArguments(argv) : null;
        if (args != null) {
            if (!args.contains("-listener")) {
                createPeers( getSystem().getPortManager(), args);
            }  
        } else {
            initiator = true;
        }
        createUserInterface();
    }
 
    private void createUserInterface() {
        NFrame toplevel=getMainFrame();
        NGridLayout grid=toplevel.getLayout();
        grid.setSize(2,2).setGaps(5, 5).setRowHeight(1, 40);
        grid.setPadding(5);
        sdpData=new NTextArea(new NScrollView(toplevel)).setPromptText("Offer");
        answer=new NTextArea(new NScrollView(toplevel)).setPromptText("Answer");
        new NButton(toplevel,"Send").addCommand(0,this).
                getForm().addField((NTextArea)sdpData);
        if(initiator)new NButton(toplevel,"Connect").addCommand(1,this).
                getForm().addField((NTextArea)answer);
        toplevel.setBounds(50,450,600,400);
        toplevel.setVisible(true);
    }
 
    private NMediaPlayer createMediaPlayer() {
        NFrame mediaFrame=new NFrame(getMainFrame());
        NMediaPlayer mPLayer=new NMediaPlayer(mediaFrame, MediaType.VIDEO_IN);
        mediaFrame.setBounds(200, 200, 300, 450);
        mediaFrame.setVisible(true);
        return mPLayer;
    }
 
    private void createInitiator() {
        getSystem().getPortManager().createMediaPort(new NPortListener() {
            @Override
            public void onData(IPort port) {
                getConsole().print(port.readString());
            }
 
            @Override
            public void onOpen(IPort port) {
                mediaPort = (IMediaPort) port;
                sdpData.setText(mediaPort.getSDP());
            }
 
            @Override
            public void onConnect(IPort port) {
                port.write("Hello World From Inititor");
            }
        });
    }
 
    private void createListener() {
        getSystem().getPortManager().listenMediaPort(new NPortListener() {
            @Override
            public void onData(IPort port) {
                getConsole().print(port.readString());
            }
 
            @Override
            public void onOpen(IPort port) {
                //create listener
                mediaPort = (IMediaPort) port;
                int operation=port.getOperation();
                if(operation==NPortOperations.OPEN) {
                    mediaPort.startMedia(createMediaPlayer(), sdpData.getText());
                } else if(operation==NPortOperations.LISTEN){ //listen
                    answer.setText(mediaPort.getSDP());
                }
            }
 
            @Override
            public void onConnect(IPort port) {
                port.write("Hello World From Listener");
            }
        });
    }
 
    private void createPeers(IPortManager portManager, NArguments args) {
        if (args.contains("-room")) {
            String room=(String) args.getValue("-room");
            portManager.listenPeerPort(new NPortConfig(room).setRoom(true), new NPortListener() {
                @Override
                public void onConnect(IPort port) {
                    ((IMediaPort) port).startMedia(createMediaPlayer(), "initiator");
                }
            });
        } else if (args.contains("-connect")) {
            portManager.listenPeerPort(new NPortConfig((String) args.getValue("-connect")), new NPortListener() {
                @Override
                public void onConnect(IPort port) {
                    ((IPeerPort)port).startMedia(createMediaPlayer());
                }
            });
        }
    }
 
    @Override
    public void onEvent(NEvent e) {
        if (e.getEventType() == NEventTypes.ACTION) {
            if (((NButton) e.getControl()).getCommand() == 0) {
                if (initiator) {
                    createInitiator();
                } else {
                    createListener();
                }
            } else {
                mediaPort.startMedia(createMediaPlayer(), answer.getText());
            }
        }
    }
}

1.7 Web Sockets

A web socket can be opened using port manager. It is important to note that, in NeuroKernel, HTTPS based kernel can only open web sockets over TLS (WSS). It may require some extra work on the server side to configure the certificates for both HTTPS and WSS.

 import com.neurokernel.client.*;
 import com.neurokernel.client.io.*;
 import com.neurokernel.client.system.*;
 import com.neurokernel.client.adapter.NPortListener;
 
 public class WebSocketListener extends NApplication {
     @Override
     public void main(int argc, String[] argv) {
         IPortManager portManager=getSystem().getPortManager();
         portManager.listenWebSocket("ws://127.0.0.1:9000/locationFeed",
             new NPortListener() {
                  @Override
                  public void onConnect(IPort port) {
                       IWebSocket webSocket=(IWebSocket)port;
                       getConsole().println(webSocket.getWebSocketAddress());
                       port.writeString("Hello");
                  }
 
                  @Override
                  public void onData(IPort port) {
                      getConsole().println("Response:" + port.readString());
                  }
 
                  @Override
                  public void onError(IPort port) {
                      getConsole().println("Connection Error");
                  }
              }
         );
     }
 }

2. RPC

Applications can call methods from a plugged in device application using RPC. Method call processors are implemented by the parent application, and method response processors are implemented by the device. Please see Plugin System for detailed information and examples.

3. Shared Data

Shared data access is available to all trusted applications. It is important not to use easy to guess shared data entries for important data if remote applications are utilized. Shared data entries can be created as ready only which cannot be overwritten by other tasks.

import com.neurokernel.client.*;
import com.neurokernel.client.system.*;
import com.neurokernel.client.system.adapter.*;
 
public class SharedDataExample extends NApplication {
      private ISharedData sharedData;
      @Override
      public void main(int argc, String[] argv) {
          final NFrame mainFrame = getMainFrame();
          sharedData=getSystem().getSharedDataHandler();
          sharedData.write("Where","NeuroKernel in Cambridge",true);
 
          new NButton(mainFrame,"Read").addListener(new NActionListener() {
              @Override
              public void onAction(NEvent e) {
                   sharedData.read("Where",new NDataListener() {
                        @Override
                        public void onSuccess(IResponse response) {
                             NMessageDialog.showInfo(mainFrame, "Data:",response.readString());
                        }
                   });
              }
          });
 
          mainFrame.setTitle("Shared Data Interface");
          mainFrame.setBounds(20,20,200,200);
          mainFrame.setVisible(true);
      }
}

4. Clipboard

Clipboard is a single slot shared data space for all applications. Only trusted applications whether remote or not can access to clipboard data. NeuroKernel clipboard is not same as the system clipboard. System clipboard is also available for copy operation only in NEventAction object from NMenuCell and NPropertyActions classes.

import com.neurokernel.client.*;
import com.neurokernel.client.system.*;
import com.neurokernel.client.system.adapter.*;
 
public class ClipboardExample extends NApplication {
      private IClipboard clipboard;
      @Override
      public void main(int argc, String[] argv) {
          final NFrame mainFrame = getMainFrame();
          clipboard=getSystem().getClipboard();
          clipboard.copy("NeuroKernel in Cambridge");
 
          new NButton(mainFrame,"Paste").addListener(new NActionListener() {
              @Override
              public void onAction(NEvent e) {
                   clipboard.paste(new NDataListener() {
                        @Override
                        public void onSuccess(IResponse response) {
                             NMessageDialog.showInfo(mainFrame, "Clipboard:",response.readString());
                        }
                   });
              }
          });
 
          mainFrame.setTitle("Clipboard Interface");
          mainFrame.setBounds(20,20,200,200);
          mainFrame.setVisible(true);
      }
}