📄 j2ssh-clientserver-part-three.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Untitled Document</title>
</head>
<body>
<h2><font face="Verdana, Arial, Helvetica, sans-serif"><b>Part Three - SSH Channels</b> </font><font face="Verdana, Arial, Helvetica, sans-serif">
</font></h2>
<font face="Verdana, Arial, Helvetica, sans-serif">
<p><font size="2">We need to understand the SSH channel process in order to continue,
so what are channels? All terminal sessions, forwarded connections, etc. are
channels. Either side may open a channel and multiple channels are multiplexed
into a single connection. Channels are flow-controlled, that is no data may
be sent to a channel until a message is received to indicate that window space
is available.</font></p>
<p><font size="2">Channels can be whatever you want them to be, there are many
channel types already defined within the SSH protocol specification, such as
the 'session' or 'tcpip-forward' channels. These are the mechanism that provide
the transport for your session and port forwarding data.</font></p>
<p><font size="2">When you want to open a channel you send a request to the SSH
server, the server will then either respond to that request with a confirmation
or failure message. Your channel is identified by a name, for instance lets
say we want to create a channel to echo information back to the client, we would
call the channel "echo@3sp.com";. You should always name your channel
using the name@domain syntax.</font></p>
<p><font size="2">J2SSH provides a mechanism for developing custom channels, the
com.sshtools.j2ssh.connection package contains a number of classes to help you.
First the Channel class is the abstract base for all channels. Lets take a look
at its abstract methods, the first method is self explainitory, it returns the
name/type of the channel.</font></p>
</font>
<blockquote>
<pre>public String getChannelType() {
return "echo@3sp.com";
}
</pre>
</blockquote>
<font size="2" face="Verdana, Arial, Helvetica, sans-serif"><p>The next two methods
provide the settings for the flow control. The values returned by the following
mehtods set the boundary for the window space. The channel will never provide
more window space than the maximum and will increase window space automatically
(back up to the maximum) when the minimum is reached. </p>
</font>
<blockquote>
<pre>protected int getMinimumWindowSpace() {
return 1024;
}
protected int getMaximumWindowSpace() {
return 65535;
}
</pre>
</blockquote>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">The maximum packet
size setting is the maximum amount of data that the remote side can send in
one single packet. This value should not exceed the maximum window space.</font></p>
<blockquote>
<pre>protected int getMaximumPacketSize() {
return 32768;
}</pre>
</blockquote>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">When a request is made to
open a channel, the client can send some data with the open request so that
the server may process the request based on additional information. Your channel
should return this information (if any) in the getChannelOpenData method, if
there is no data simply return null.</font></p>
<blockquote>
<pre>public byte[] getChannelOpenData() {
return null;
}</pre>
</blockquote>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">When the server confirms
that the channel is open it can also provide data in response to the channel
open information. It does so by returning this information in the getChannelConfirmationData
method. Again if no data is required to be sent to the remote side simply return
null.</font></p>
<blockquote>
<pre>public byte[] getChannelConfirmationData() {
return null;
}</pre>
</blockquote>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">Once the channel
has been opened, the channel mechanism calls the channels onChannelOpen method.
You should not perform any expensive processing in this method since it will
lock up the protocol and you will not be able to send data. </font></p>
<blockquote>
<pre>protected void onChannelOpen() throws java.io.IOException {
/** Your channel is open so do stuff if you want **/
}</pre>
</blockquote>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">Once the channel
is open you can send data using the channels sendChannelData method, this type
of data is received by the channel with the following method.</font></p>
<blockquote>
<pre>protected void onChannelData(SshMsgChannelData msg) throws java.io.IOException {
// Channel data has arrived, process it!
}</pre>
</blockquote>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">There is also an
extended data channel which can be used, the extended data has a type field
which can be used to identify different application defined data types. This
is received with the onChannelExtData method and sent with sendChannelExtData.</font></p>
<blockquote>
<pre>protected void onChannelExtData(SshMsgChannelExtendedData msg) throws java.io.IOException {<br> /**@todo Implement this com.sshtools.j2ssh.connection.Channel abstract method*/<br>}
</pre>
</blockquote>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">There is also a
request mechanism seperate to the channel data which can be used. This provides
named based requests. For example in our echo channel we could have a request
to turn echo on and off. Requests are recieved with the onChannelRequet method
and sent with sendChannelRequest.</font></p>
<blockquote>
<pre>protected void onChannelRequest(String requestname, boolean wantreply, byte[] requestdata)
throws java.io.IOException {
/**@todo Implement this com.sshtools.j2ssh.connection.Channel abstract method*/
}</pre>
</blockquote>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">When you no longer
wish to send data you can set the local side to EOF by using setLocalEOF. When
the remote server sends EOF the onChannelEOF method is called.</font></p>
<blockquote>
<pre>protected void onChannelEOF() throws java.io.IOException {
/**@todo Implement this com.sshtools.j2ssh.connection.Channel abstract method*/
}</pre>
</blockquote>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">Finally, to close the channel
you can use the close method. When the channel is closed by either side the
onChannelClose method is called.</font></p>
<blockquote>
<pre>protected void onChannelClose() throws java.io.IOException {
/**@todo Implement this com.sshtools.j2ssh.connection.Channel abstract method*/
}</pre>
</blockquote>
<!--pagebreak-->
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">Ok so now we know the basic
structure how do we implement our channel and use it from either side of the
connection? Heres our server side channel implelentation. This simply returns
any data sent to it if echo is on.</font></p>
<blockquote>
<pre>public class EchoChannel extends Channel {
boolean echo = true;
public EchoChannel() {
}
public String getChannelType() {
return "echo@3sp.com";;
}
protected void onChannelRequest(String requestname, boolean wantreply, byte[]
requestdata) throws java.io.IOException {
if(requestname.equals("echo-off@3sp.com";)) {
echo = false;
if(wantreply)
connection.sendChannelRequestSuccess(this);
return;
} else if(requestname.equals("echo-on@3sp.com";)) {
echo = true;
if(wantreply)
connection.sendChannelRequestSuccess(this);
return;
}
if(wantreply)
connection.sendChannelRequestFailure(this);
}
protected void onChannelExtData(SshMsgChannelExtendedData msg) throws java.io.IOException {
}
protected void onChannelData(SshMsgChannelData msg) throws java.io.IOException {
if(echo)
sendChannelData(msg.getChannelData());
}
protected int getMaximumPacketSize() {
return 32768;
}
protected void onChannelEOF() throws java.io.IOException {
}
protected void onChannelClose() throws java.io.IOException {
}
public byte[] getChannelOpenData() {
return null;
}
protected int getMinimumWindowSpace() {
return 1024;
}
protected int getMaximumWindowSpace() {
return 65535;
}
protected void onChannelOpen() throws java.io.IOException {
}
public byte[] getChannelConfirmationData() {
return null;
}
}</pre>
</blockquote>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">Now we need to
configure the server to accept the channel and create an instance of our EchoChannel
when a request is made. To do this we will need to create a ChannelFactory that
can create the channel, this is a simple interface</font></p>
<blockquote>
<pre>public interface ChannelFactory {
public Channel createChannel(String channelType, byte[] requestData)
throws InvalidChannelException;
}
</pre>
</blockquote>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">So lets create an EchoChannelFactory
implementation</font></p>
<blockquote>
<pre>public class EchoChannelFactory implements ChannelFactory {
public Channel createChannel(String channelType, byte[] requestData)
throws InvalidChannelException {
if(channelType.equals("echo@3sp.com";)) {
return new EchoChannel();
}
throw new InvalidChannelException("Only echo channels allowed by this factory");
}
}</pre>
</blockquote>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">So now were ready
to configure the server. In my previous articles you will remember we implemented
the configureServices method of the SshServer? To allow this channel to be opened,
we simply add the following line to the configureServices implementation</font></p>
<blockquote><pre>connection.addChannelFactory("echo@3sp.com";, new EchoChannelFactory());</pre></blockquote>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">Your server should now be
configured to support your channel. So lets look at how to invoke the channel
from the client.</font></p>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">First we need to create
a client side implementation of the channel, but we need it to be simpler with
a set of IOStreams perhaps??? Well the hard work is already done for you, take
a look at the IOChannel class. This provides an InputStream and OutputStream
for the channels data. Heres the implementation, it has less methods to implement
since the channel data is now handled by the parent class.</font></p>
<blockquote>
<pre>public class EchoChannelClient extends IOChannel {
public EchoChannelClient() {
}
public String getChannelType() {
return "echo@3sp.com";;
}
protected void onChannelRequest(String requestname,
boolean wantreply,
byte[] data) throws java.io.IOException {
if(wantreply)
connection.sendChannelRequestFailure(this);
}
protected int getMaximumPacketSize() {
return 32768;
}
public byte[] getChannelOpenData() {
return null;
}
protected int getMinimumWindowSpace() {
return 1024;
}
protected void onChannelOpen() throws java.io.IOException {
}
protected int getMaximumWindowSpace() {
return 65535;
}
public byte[] getChannelConfirmationData() {
return null;
}
}</pre>
</blockquote>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">We need to add
a method for turning the echo on and off.</font></p>
<blockquote>
<pre>public void setEcho(boolean echo) throws java.io.IOException {
if(echo)
connection.sendChannelRequest(this, "echo-on@3sp.com";, false, null);
else
connection.sendChannelRequest(this, "echo-off@3sp.com";, false, null);
}</pre>
</blockquote>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif">Now were ready
to go since any data we write to the channels outputstream, which we obtain
by using getOutputStream will be returned to the InputStream if echo is on.</font></p>
<p> <font size="2" face="Verdana, Arial, Helvetica, sans-serif">To use the channel using
an SshClient instance simply</font></p>
<blockquote>
<pre>EchoChannelClient echo = new EchoChannelClient();
if(ssh.openChannel(echo)) {
// Channel is open
echo.getOutputStream().write("hello world!".getBytes());
// Read it back from the inputstream
byte[] buf = new byte[32];
echo.getInputStream().read(buf);
}</pre>
</blockquote>
<p><font size="2" face="Verdana, Arial, Helvetica, sans-serif"> So of course your
requirements are probably much more complex, if you want the server to handle
IOStreams you can use the IOChannel instead of the Channel, in the end its up
to you....... with a little imagination ;-) </font></p>
</body>
</html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -