
A protocol (a basic abstraction for communication) provides a communication service for exchanging messages through a network. A protocol typically works by using the services provided by a lower level protocol, down to the hardwired communication interface. This defines a layered organization such as a protocol stack or acyclic graph. In Jonathan, the basic protocol layer is that provided by the operating system, in the form of Java sockets, and we are not concerned about the lower levels.
The main communication abstraction provided by Jonathan is a session. A session is an object that represents a communication channel. It provides an interface for sending and receiving messages; actually two different interfaces (Session_Low and Session_High) are respectively provided for incoming and outgoing messages. A protocol is essentially a session manager: it creates sessions, acts as a naming and binding context for these sessions, and provides them with communication resources. Like protocols, sessions are organized in a hierarchy. At the lowest level, a session relies on a basic communication mechanism called a connection, which provides an interface to send and to receive elementary messages (sequences of bytes). For instance, in the TCP-IP protocol suite, a connection provides the IpConnection interface and encapsulates a socket.
The main communication primitives are message sending and receiving. They operate in different ways, due to the asynchronous nature of receiving. A read operation (implemented by a receive() method on a connection) blocks the executing thread until data is available on the input channel associated with the connection. When data becomes available (a message has arrived), the thread is unblocked, causing the message to be passed up the protocol stack by calling the ``lower'' interfaces of the sessions, in ascending order. On the other hand, an application process sends an outgoing message by calling the ``higher'' interface provided by a session. The message is then sent down the protocol stack by calling ``higher'' interfaces in descending order, down to the call of an emit method on the connection. Figure 3.1 gives an overview of this mechanism, which is described in further detail in Section 3.2.
|
|
Sessions are set up according to the Jonathan binding framework. On the server side, a protocol graph is first constructed by assembling elementary protocols. The protocol graph is a naming context, which provides the export method. The exported interface (srv_itf) is the ``lower'' interface of a session (of type Session_Low), which provides the functionality of the server. The export method returns a session identifier (a name for the exported interface), which contains all the information needed to set up a communication with the server (e.g.,for TCP/IP, the IP address of the server and a port number). This information may be transmitted over the network and decoded by a client.
In order to be able to access the interface exported by a server, a client must call the bind method provided by a session identifier that designates the server, passing the client application's ``lower'' interface (clt_itf) as a parameter. The session identifier may be obtained from the network (e.g. through a name service), or it may be constructed locally using the server address and port number if these are known. The bind method returns an interface session of type Session_High, which may be used by the client to call the server. Messages from the server are directed to the client application, through the interface clt_itf provided as a parameter of the call to bind.
A general picture of the export-bind mechanism is outlined on Figure 3.2. Many details are omitted; these are provided in Section 3.2.
|
|
Actual communication relies on two services: chunks and (un)marshallers, that are provided, respectively, by the Jonathan resource library and the Jeremie presentation library. We describe these services briefly. A full description may be found in the resource framework tutorial.
Typically, marshallers and unmarshallers are used as follows (this is a simplified example).
Sending a message composed of an integer i followed by a 8-byte string str followed by an object obj.Marshallers and unmarshallers are created by marshaller factories. A marshaller factory is usually provided in the bootstrap configuration of Jonathan (see the configuration framework tutorial).
Session_High session ... StdMarshallerFactory marshaller_factory ... ... Marshaller m = marshaller\_factory.newMarshaller(); marshaller.writeInt(i); marshaller.writeString8(str); marshaller.writeValue(obj); session.send(marshaller); ...Receiving the message sent by the above program sequence; the following sequence is supposed to be part of a method having Unmarshaller unmarshaller as a parameter.
i=unmarshaller.readInt();} str=unmarshaller.writeString8();} obj=unmarshaller.readValue();} unmarshaller.close();} ...
The first example (using Java sockets) does not involve Jonathan at all. It illustrates, at a fairly low level, the export-bind pattern of interaction that is further expanded in the following use cases. Consider a server that provides a service to a single client at a time (multiple clients are considered later on). The server selects a port (port 3456 in this example) and creates a server socket associated with that port. It then waits for client connections by calling accept() on the socket. When a client connects to port 3456 (this is done in the Socket constructor), accept() returns a new socket dedicated to exchanges with the client. The original socket remains available for new connections (if we do not create a new thread per client, only one client connection may be opened at a time).
In effect, the accept() call in the server program is equivalent
to our export primitive, while the connect() implicitly
called in the Socket constructor in the client program is
equivalent to our bind primitive.
Note that the binding process always relies on an information shared
by the client and the server (here, the hostname and the port number).
In the present case, this shared information is hardwired in the code.
More elaborate methods are introduced in further examples.
The complete code of an example using this pattern (a simple echo server)
may be found in the Sun Java tutorial
(http://java.sun.com/docs/books/tutorial/networking/sockets/)
The TCP-IP framework is composed of the following interfaces and classes
(actually the interfaces in the apis.protocols package are
common to all communication protocols).
package org.objectweb.jonathan.apis.protocols
In addition, the TCP-IP framework uses the following interfaces and
classes.
package org.objectweb.jonathan.apis.presentation
The libs.protocols.tcpip package implements the session level,
together with the ``chunk provider'' which allows a session to get input
data from a connection. The libs.resources.tcpip package implements
the connection level. The session and connection levels are described in the following sections.
Since sessions play a central part in the communication framework, it is
important to understand the interplay between sessions at different levels.
We illustrate this by the example of TCP-IP (Figure
3.3). The general pattern
outlined on this figure applies both on the client and on the server side.
The main difference is that the server-side sessions are typically created
by export, while the client-side sessions are created by bind.
Server
// create a new server socket associated with a specified port
server_socket = new ServerSocket(3456);
// wait for client connections:~a ``pseudo-export'' operation
Socket socket = server_socket.accept();
// socket is now available for communication with client
Client
// connecting to server: a ``pseudo-bind'' operation
Socket socket = new Socket(hostname, 3456);
// socket is now available for communication with server
3.2 The TCP-IP Protocol
3.2.1 Overview of the TCP-IP Framework
public interface Protocol
public interface ProtocolGraph
public interface ReplyInterface
public interface
ReplySession
public interface
RequestSession
public class
ServerException
extends
JonathanException
public interface
SessionIdentifier
public interface
Session_High
public interface
Session_Low
package org.objectweb.jonathan.libs.protocols.tcpip
final public class
TcpIpProtocol
implements
Protocol
final class TcpIpProtocolGraph implements
ProtocolGraph
final class CltSessionIdentifier extends
IpSessionIdentifier
final class SrvSessionId extends
IpSessionIdentifier
abstract class Session implements
IpSession, Runnable
final class CltSession extends Session
final class SrvSession extends Session
final class SrvSessionFactory implements Runnable
final class TcpIpChunkProvider extends Chunk implements
ChunkProvider
package org.objectweb.jonathan.apis.protocols.ip
public interface
TcpIpSrvConnectionFactory
public interface
IpConnection
public interface
IpSession
extends
Session_High
abstract public class
IpSessionIdentifier
implements
SessionIdentifier
public interface
TcpIpConnectionMgr
public interface
UdpConnectionMgr
package org.objectweb.jonathan.libs.resources.tcpip
public class
IPv4ConnectionFactory implements
TcpIpConnectionMgr
class SrvConnectionFactory
implements
TcpIpSrvConnectionFactory
public class
JConnectionMgr implements
TcpIpConnectionMgr
class Connection implements
IpConnection
class SrvConnectionFactory implements
TcpIpSrvConnectionFactory
public class EndOfMessageException extends
JonathanException
public class MarshalException extends
JonathanException
public interface
Marshaller
public interface
MarshallerFactory
public class
UnMarshalException
extends
JonathanException
public interface
UnMarshaller
package org.objectweb.jeremie.libs.presentation.std
public class
StdMarshallerFactory
implements
MarshallerFactory
public class
StdMarshallerFactoryFactory
extends
GenericFactory
3.2.2
The Session Level
|
|
At the lower level, we have a TcpIp session, which essentially encapsulates a connection to the network. It has two functions:
At the upper level, we have an application session, which provides the client or server functionality. The application session
It is important to emphasize the difference between the Session_High and Session_Low interfaces (especially since both interfaces include a method called send, which may seem confusing at first sight).
The classes ServerSession and ClientSession that implement the server and client application sessions of the Echo application have the following general outline.
The actual programs must include, in addition, provision for exception
handling and for nice termination of client sessions. The complete
programs
may be found in
Client.java,
Server.java
(the programs actually contain provision for multiple clients, to be
explained later on (cf. Section 3.2.6)).
The mechanism for session setup uses the binding
framework based on the export-bind pattern.
Both the server and the client start by an initial configuration
phase (see the configuration
tutorial),
and create an instance of
TcpIpProtocol.
Then each side instantiates a session as follows.
From this point on, the core of the program runs in the application
programs, i.e. the ClientSession and ServerSession classes,
as described above.
The interfaces provided by the session level abstract away (in the
send
methods) the low-level message transmission mechanism. This mechanism is
defined at the connection level and (in the current implementation) relies
on two classes: JConnectionMgr defines generic mechanisms for
using socket-based connections, and IPv4ConnectionFactory provides
a specific implementation of these mechanisms. The main abstraction at
this level is the connection (instance of IpConnection), which
encapsulates a socket.
A full description of the connection level is given in the resource framework tutorial.
For completeness, we now give a summary explanation of the mechanisms for message input.
Recall that CltSession and SrvSession are the client
and server incarnations, respectively, of the generic TcpIp session described
above (in the code, both classes derive from a common abstract class,
TcpIpProtocol.Session).
This class extends Runnable, i.e. its instances are executed as
independent threads activated by a run() method, which is called
when a message is received. This is done through the
TcpIpProtocol.TcpChunkProvider
class, which encapsulates a socket input stream (through an
IpConnection),
and delivers messages as ``chunks'' (a
Chunk
is the abstraction provided by Jonathan to efficiently use data of variable
length). This class has two main methods, prepare() and close(),
which are respectively called as a prelude and postlude of all input
operationsperformed through an
Unmarshaller
on the input stream. A TcpChunkProvider contains a data cache,which is used as follows.
Thus the chunk provider effectively acts as a data pump that injects incoming messages
into the TcpIp session, which in turn sends them to the upper level application session.
We now describe in detail the internal workings of the export-bind
operations. An overview of these operations is given in Figure 3.4 which
gives a more detailed picture of the process outlined on Figure
3.2.
class ServerSession implements Session_Low{
ServerSession();
static MarshallerFactory marshaller_factory;
private int counter; //internal state of session
// the server method for accepting requests:
// - unmarshaller: the request message
// - sender: the local interface to the client
public void send(UnMarshaller unmarshaller, Session_High sender){
String theOutput = null;
String theInput = unmarshaller.readString8();
theOutput = counter + ":" + theInput;
unmarshaller.close();
Marshaller marshaller = marshaller_factory.newMarshaller();
sender.prepare(marshaller);
marshaller.writeString8(theOutput);
sender.send(marshaller);
}
}
class ClientSession implements Session_Low {
static MarshallerFactory marshaller_factory;
BufferedReader reader; // for terminal input by client
ClientSession(BufferedReader reader);
this.reader = reader;}
// the client method for accepting messages from server
// - unmarshaller: the message
// - session: the local interface to the server
public void send(UnMarshaller unmarshaller, Session_High session){
String fromServer,input;
System.out.print("Client: "); // prompting client
System.out.flush();
input = reader.readLine();
Marshaller marshaller = marshaller_factory.newMarshaller();
session.prepare(marshaller);
marshaller.writeString8(input);
session.send(marshaller);
fromServer = unmarshaller.readString8();
unmarshaller.close();
}
}
3.2.3
Setting up sessions
Server:
// configuring the system: creating factories
// (described in the configuration tutorial)
// creating a protocol instance (a naming context for sessions)
TcpIpProtocol protocol =
new TcpIpProtocol(<parameters, to be described later>);
// creating and exporting a new session
SessionIdentifier session_id =
protocol.newProtocolGraph(port).export (new ServerSession());
// if no port specified, selects an unused port
Client:
// configuring the system: creating factories
// (described in the configuration tutorial)
// creating reader, getting server hostname and port
// creating a protocol instance (a naming context for sessions)
TcpIpProtocol protocol =
new TcpIpProtocol(<parameters, to be described later>);
// preparing for connection to server
IpSessionIdentifier participant =
protocol.newSessionIdentifier(hostname,port) ;
// creating client-side session and connecting to server
Session_High session = participant.bind (new ClientSession(reader)) ;
// session is now available for communication with server}
3.2.4
The Connection Level
3.2.5
Putting it all together
|
|
Calling the export method on ProtocolGraph has the following effect (s1, c1, etc. refer to the tags that designate the server and client operations on Figure 3.4).
Calling the bind method on CltSessionIdentifier has the following effect.
Two patterns may be used for serving multiple clients, according to whether the server maintains a common state shared by all clients or a distinct state for each client.
The mechanism described above allows several clients to connect to a single server, through the connection factory mechanism. If a new client binds to the server, a new connection is created (using the socket accept mechanism), as well as a new SrvSession instance encapsulating this connection, together with a new thread. However, there is still a unique application session (ServerSession) , whose state is shared between all clients (Figure 3.5). This is illustrated in the example programs by adding state to the application session, in the form of an integer variable counter that is incremented after each client call. Multiple clients see a single instance on this variable. The text of the programs may be found in Client.java, Server.java..
|
|
If the application needs a per-client session state, then it is necessary to explicitly manage multiple sessions at the application level. This is done in the following example.
In this example, each client is associated with a distinct application-level session that maintains the client's own version of the state (in this case, the counter variable). This is achieved, on the server side by an instance of SrvProtocol, which has two functions: it acts as a factory that creates a new instance of the client session, OwnSession, when a new client connects; it acts as a demultiplexer that forwards the incoming messages to the appropriate OwnSession according to the identity of the sender. The factory is implemented as a hashtable that contains an entry per session. The body of the application (in this case, the incrementation of the counter) is implemented by OwnSession.
|
|
The client side is identical to that of the previous application. The text of the programs may be found in Client.java, Server.java.
In this section, we examine the implementation in Jonathan of two other protocols: the IP Multicast Protocol and the Real Time Protocol (RTP). These implementations conform to the export-bind pattern as decribed in Section 3.1.1, with local variations. Then we present the Event Channel use case, which combines both protocols. In addition, a presentation of the GIOP protocol may be found in the Jonathan binding tutorial.
As defined in IETF RFC 1112, ``IP multicasting is the transmission of an IP datagram to a ``host group'', a set of zero or more hosts identified by a single IP destination address''. IP addresses starting with 1110 (i.e. 224.0.0.1 to 239.255.255.255) are reserved for multicast. Groups are dynamic, i.e. a host may join or leave a group at any time; a host may be a member of several groups. A host need not be a member of a group to send a message to that group.
A particular class of Java sockets, java.net.MulticastSocket, is used in Jonathan as the base layer for implementing IP Multicast. A multicast socket s may subscribe to a host group g by executing s.joinGroup(g) and unsubscribe by executing s.leaveGroup(g). In order to send a message msg to a group g, a datagram must first be created by DatagramPacket d = new DatagramPacket(msg, msg.length, g, port), where port is the port to which socket s is bound. The datagram is then sent by executing s.send(d).
The Jonathan MulticastIpProtocol class manages MulticastIpSession sessions. Each session is dedicated to a (IP multicast address, port) network endpoint. A session may optionally be associated with an upper level session. In that case, it may send and receive messages, and a per session thread is used to wait for incoming messages. Otherwise, the session is only used to send messages.
In the IP Multicast protocol, there are no separate client and server roles; therefore there is no need to separate protocol graphs (which export servers) from session identifiers (which are used by clients to bind to servers). A single data structure, MulticastIpSessionIdentifier, is used for both functions (it implements SessionIdentifier and ProtocolGraph and thus provides both export and bind).
The sessions managed by this protocols are instances of class MulticastIpSession, which essentially provides two methods, send and run. In the current implementation, a single thread is created with each instance and waits on the multicast socket. The constructor is as follows:
The reader threads executes the run() method which is a loop with the following overall structure (exceptions not shown):
The send(message) method does essentially this:
A MulticastIpSessionIdentifier contains three fields: address, port, and session. As explained above, the export and bind methods are very similar. Both create a new socket with an associated session. They only differ by the type of the returned value (an identifier for export, a session for bind); in addition, export needs to supply an upper level interface.
As shown above, this creates a multicast socket using the port associated with the identifier, and spawns the reader thread if an upper (receiving) interface (hls) is provided (if not, the socket is only used for sending and does not need a waiting thread).
RTP (Real-time Transport Protocol) is the Internet standard protocol for the transport of real-time data, including audio and video.
RTP works on top of an existing transport protocol (usually UDP) and is designed to be independent of that protocol. RTP is composed of a real-time transport protocol (RTP proper), and of an RTP control protocol, RTCP, which monitors the quality of service.
Jonathan provides a partial implementation, RTPProtocol, of the RTP protocol (not including RTCP). RTP packets have a 12 byte header (defined by the RTPHeader class), which includes such information as sequence number and timestamp.
RTPProtocol provides the usual export and bind operations to its users:
An event channel is a communication channel on which two types of entities may be connected: event sources and event consumers. When a source produces an event, in the form of a message, this message is delivered to all the consumers connected to the channel. The channel itself may be regarded as both an event source and consumer: it consumes events from the sources and delivers them to the consumers.
Two communication patterns may be used:
In this example, we use the push pattern. The interface provided by the channel to event sources is that of a representative (a proxy) of the interface provided by the event consumers (Figure 3.7). Particular implementations of the event channel may provide specific guarantees for message delivery, in terms of reliability, ordering, or timeliness.
MulticastIpSession(MulticastIpSessionIdentifier sid,
Session_Low prev_protocol) throws IOException {
socket=new MulticastSocket(sid.port());
socket.joinGroup(sid.InetAddress());
this.sid=sid;
this.prev_protocol=prev_protocol;
if(prev_protocol!=null) {
reader =new Thread(this);
cont = true;
reader.start();
}
}
while (true) {
socket.receive(packet); \\ a DatagramPacket
extract message from packet
send message to upper session, i.e. prev_protocol;
}
encapsulate message into a DatagramPacket packet;
socket.send(packet);
118 public SessionIdentifier export(Session_Low hls) throws JonathanException {
119 if (hls != null) {
120 try {
121 session = new MulticastIpSession(this,hls);
122 return this;
123 } catch (IOException e) {
124 throw new ExportException(e);
125 }
126 } else {
127 throw new ExportException("MulticastIpSessionIdentifier: no protocol low interface specified.");
128 }
129 }
135 public Session_High bind(Session_Low hls) throws JonathanException {
136 try {
137 return new MulticastIpSession(this,hls);
138 } catch (IOException e) {
139 throw new org.objectweb.jonathan.apis.binding.BindException(e);
140 }
141 }
3.3.2 The RTP Protocol
3.3.3 Use Case: Event Channel
Introduction
|
|
Jonathan provides two implementations of a simple event channel, using David (CORBA) and Jeremie (Java RMI), respectively. Both rely on the same binder and event model; they essentially differ by the communication protocol. This presentation is based on the Jeremie version of the event channel.
The event channel provides the following interface, which defines the methods needed by event sources and consumers to connect to the channel using the ``push'' pattern:
Event channels are built by event channel factories. Class EventChannelFactory provides, among others, the following method:
which creates a new event channel built on the supplied host address and port. It also provides a class that implements the EventChannel interface defined above.
Like in the ``Hello World'' case described in the binding tutorial, a name server (in Jeremie, a registry) is used to register event channels. In this example, an event source creates the channel, registers it under a symbolic name of the form <// < registry host > < channel name > , and starts producing events. A prospective consumer retrieves a channel from the name server using its symbolic name, and subscribes to the channel; it then starts receiving messages transmitted on this channel.
The core of the main method of NewsSource, the program for event sources, is:
The core of the main method of NewsConsumer, the program for event consumers, is:
The NewsTicker interface shared by source and consumer classes consists of the
method latestNews(String msgs[]) activated when an event is produced. This method is
implemented in the NewsConsumer class; it simply displays the incoming messages
on the screen:
The implementation of the event channel relies on two components that closely interact: the event channel factory, EventChannelFactory, and the event binder, EBinder. The role of the factory is to deliver implementations of the EventChannel interface,
both in the form of actual instances and in the form of stubs. The role of the binder is to provide an interface allowing both sources and consumers to connect to an event channel. The current implementation is hardwired to work with the RTP protocol on top of the IP Multicast protocol.
EBinder provides a specific class of identifiers, EIds. An EId designates an event channel built on a particular IP address and port number used by the underlying IP Multicast protocol. The two main methods are getProtocolGraph() and bind(), which are used as follows:
The usual export-bind pattern is again used here; getProtocolGraph() returns the protocol graph of the underlying RTP protocol, itself relying on IP Multicast; bind() returns a stub, created by a stub factory using the underlying RTP session.
An EventChannelFactory is created by an EventChannelFactoryFactory, which associates it with
an EBinder. It implements special instances of stubs and skeletons targeted towards one-way invocation,
and provides a specific implementation of EventChannel, that works as follows.
An instance of EventChannel is created by the constructor:
On line 291, a new EId is created by the EBinder, with the given address and port. On line 292, a new session is associated with this EId, using the underlying RTP and IP Multicast protocols. Finally, a proxy (stub) is created using this session. This proxy is ready to be delivered to any source willing to send events to the channel, through the following method:
A consumer connects to the event channel by calling the following method:
The key operation here is on line 322: the consumer builds a skeleton that will act as an event receiver interface for it, then binds that skeleton to the event channel using the bindConsumer method of the EBinder, as described above. Lines 302 to 321 illustrate another (classical) situation in the binding process: if the factory associated with the channel is not known (e.g. because the event channel reference has been sent over the network), the method tries to retrieve it using one of the identifiers associated with the stub, by iteratively resolving the identifier chain until the factory (binder) is found, or until the search fails.
Acknowledgments. Thanks to Marc Lacoste (France Telecom R&D) for providing useful material on the Jonathan TCP-IP communication framework .
public interface EventChannel extends java.rmi.Remote { // for use by RMI
void addConsumer(Object consumer)
throws JonathanException; // adds a new consumer to the event channel
void removeConsumer(Object consumer)
throws JonathanException; // removes a consumer (will no longer receive events)
Object getConsumerProxy()
throws JonathanException; // returns a consumer proxy to be used by a source
}
public EventChannel newEventChannel(String address, int port, String type)
throws JonathanException
Using an Event Channel
EventChannel channel;
EventChannelFactory channelFactory =
EventChannelFactoryFactory.newEventChannelFactory(NewsSource.class);
channel=channelFactory.newEventChannel(address,port,"NewsChannel");
Naming.rebind("//"+args[3]+"/"+args[2], channel); // //<registry host><channel name>
System.out.println("Ready...");
NewsTicker ticker=(NewsTicker)channel.getConsumerProxy();
NewsSource producer=new NewsSource(ticker);
producer.produce();// produce is the event generator method of NewSource class
// it includes a call to the NewsTicker method: ticker.latestNews
System.setSecurityManager(new RMISecurityManager());
channelName=args[0];
EventChannel channel=
(EventChannel) Naming.lookup("//" + args[1] + "/" + channelName);
if(channel==null) {
System.err.println("Channel "+ channelName + " not found");
System.exit(1);
}
channel.addConsumer(new NewsConsumer());
public void latestNews(String msgs[]) {
for(int i=0;i<msgs.length;i++)
System.out.println(msgs[i]);
}
Event Channel Implementation
ProtocolGraph protocol_graph = channel_id.getProtocolGraph();
protocol_graph.export(consumer);
289 EventChannelImpl(String address, int port, String type,
290 EventChannelFactory binder) throws JonathanException {
291 id=(EBinder.EId)binder.getEBinder().newId(address,port,type);
292 SessionIdentifier ep=id.getSessionIdentifier();
293 Context hints = JContextFactory.instance.newContext();
294 hints.addElement("interface_type",String.class,type,(char) 0);
295 proxy=binder.newStub(ep,new Identifier[] {id}, hints);
296 hints.release();
297 this.binder=binder;
298 }
328 public Object getConsumerProxy() throws JonathanException {
329 return proxy;
330 }
301 public void addConsumer(Object consumer) throws JonathanException {
302 a: if(binder==null) {
303 if (proxy instanceof StdStub) {
304 Identifier[] ids =
305 ((JRMIRef) ((StdStub) proxy).getRef()).getIdentifiers();
306 Object cid;
307 for (int i = 0; i < ids.length; i++) {
308 cid = ids[i];
309 while (cid instanceof Identifier) {
310 if (cid instanceof EBinder.EId) {
311 id = (EBinder.EId) cid;
312 binder = (EventChannelFactory) id.getContext();
313 break a;
314 } else {
315 cid = ((Identifier) cid).resolve();
316 }
317 }
318 }
319 }
320 throw new BindException("Unbound channel");
321 }
322 binder.getEBinder().bindConsumer(new OneWaySkeleton(consumer),id);
323 }