Written java program to establish remote SSH connection and send commands to that SSH device with the help of JSch. And that code need to run infinite loop means code will establish connection with first device, sends commands to that and moves to second device. After completion of sending commands to tenth device code will start again from the first device. For two to three iterations it is working fine. But, next iteration onwards not getting any response from device but connection establishment was successful and code was stuck at that device. Please help to solve this, there is no issue from device side. If it is stuck also, code needs to wait some time and start establish connection with next device.
Code is:
public class SSHManager {
private JSch shell;
private Session session;
private Channel channel;
private static OutputStream out;
private static InputStream in;
public void connect(String username, String password, String host,int port)
throws JSchException, IOException, InterruptedException {
shell = new JSch();
session = shell.getSession(username, host, port);
session.setPassword(password);
session.setConfig("StrictHostKeyChecking", "no");
session.connect();
channel=session.openChannel("shell");
channel.setInputStream(null);
channel.setOutputStream(null);
in=channel.getInputStream();
out =channel.getOutputStream();
((ChannelShell)channel).setPtyType("vt102");
channel.connect();
}
public String send(String command) throws IOException, InterruptedException {
byte[] tmp=new byte[1024];
out.write((command+";echo \"z4a3ce4f3317Z\"").getBytes());
out.write(("\n").getBytes());
out.flush();
String result = "";
while(true){
while(in.available()>0){
int i=in.read(tmp, 0, 1024);
if(i<0)
break;
result = result + (new String(tmp, 0, i));
}
if(result.indexOf("z4a3ce4f3317Z") != -1){
break;
}
try{Thread.sleep(300);}catch(Exception ee){}
}
return result;
}
public boolean isConnected() {
return (channel != null && channel.isConnected());
}
public void disconnect() {
if (isConnected()) {
channel.disconnect();
session.disconnect();
}
}
}
class Test {
final static SSHManager client = new SSHManager();
public static void main(String[] args) throws JSchException, IOException, InterruptedException {
while(true) {
try
{
for (int i=1;i<=10;i++)
{
String ipaddr = "10.35.57."+i;
System.out.println(ipaddr);
client.connect("root", "root", ipaddr, 22);
client.send("cd /\n");
Thread.sleep(3500);
client.send("rm sett*");
// Send five more commands to that device
}
}
catch(Exception e)
{
System.out.println(e);
}
Thread.sleep(150*1000);
}
}
}
Related
I have a task:
to do telnet commands through ssh session.
I have read similar problems:
Running telnet command on remote SSH session using JSch
telnet through SSH
Execution hangs after Running telnet command on remote SSH session using JSch
And I wrote the code (with lib https://mvnrepository.com/artifact/com.jcraft/jsch):
Class Ssh:
public class Ssh {
private final String USER;
private final String PASSWORD;
private final String HOST;
public Ssh(String user, String password, String host) {
this.USER = user;
this.PASSWORD = password;
this.HOST = host;
}
public Session runSsh() {
try {
Session session = new JSch().getSession(USER, HOST, 22);
session.setPassword(PASSWORD);
// It must not be recommended, but if you want to skip host-key check,
session.setConfig("StrictHostKeyChecking", "no");
session.connect(3000);
return session;
} catch (JSchException e) {
System.out.println(e);
}
return null;
}
}
Class Telnet
public class Telnet {
public String runCommand(Session session, String command) throws Exception {
Channel channel = session.openChannel("shell");
channel.connect(3000);
DataOutputStream outputStream = new DataOutputStream(channel.getOutputStream());
outputStream.writeBytes("telnet localhost 5000\r\n");
outputStream.writeBytes(command + "\r\n");
outputStream.writeBytes("exit\r\n");
outputStream.flush();
DataInputStream inputStream = new DataInputStream(channel.getInputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
channel.setInputStream(inputStream, true);
String line = reader.readLine();
String result = line +"\n";
while (!(line= reader.readLine()).equals("exit")){
result += line +"\n";
}
result += "Connection closed by foreign host";
outputStream.close();
inputStream.close();
channel.disconnect();
session.disconnect();
return result;
}
}
Class Main
public class Main {
public static void main(String[] arg) {
Ssh ssh = new Ssh("user","password","ip-server");
Telnet telnet = new Telnet();
try {
Session sessionSsh = ssh.runSsh();
String result = telnet.runCommand(sessionSsh, "H");
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
I get the result:
telnet localhost 5000
Entering character mode
Escape character is '^]'.
Command Shell 1 on intelpc-1 (Port: 5000)
b01_1_1 (5000) >
H
Connection closed by foreign host
Process finished with exit code 0
I don't see the result of executing the command telnet...
I made different commands via telnet:
- H
or
- u s3
But I can't see their result :(
Tell me please. How do I get the results of the telnet command execution?
I debugged your example and in my case, I needed to wait for the program, which you connect via telnet, until it is ready to receive a command.
In the second step, you can send your command and the exit command to stop the telnet, so you are reading from the stream until it has stopped by saying "Connection closed...". Be aware, that exiting from telnet can have different commands, some use quit, some other use exit or only wait for a termination signal.
class Telnet {
public String runCommand(Session session, String command) throws Exception {
Channel channel = session.openChannel("shell");
channel.connect(3000);
DataOutputStream outputStream = new DataOutputStream(channel.getOutputStream());
outputStream.writeBytes("telnet localhost 16379\r\n");
outputStream.flush();
DataInputStream inputStream = new DataInputStream(channel.getInputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
channel.setInputStream(inputStream, true);
// Read until we are ready to write
String line;
while (!(line= reader.readLine()).equals("Escape character is '^]'.")){
System.out.println(line);
}
// write command and exit telnet
outputStream.writeBytes(command + "\r\n");
outputStream.writeBytes("quit\r\n");
outputStream.flush();
// read until telnet has closed
String result = "";
while (!(line= reader.readLine()).equals("Connection closed by foreign host.")){
result += line +"\n";
}
outputStream.close();
inputStream.close();
channel.disconnect();
session.disconnect();
return result;
}
}
In the end, there are alternative ways to communicate without telnet, i.e. local port forwarding.
Thanks to Matthias Wiedemann.
I used the SSH tunneling https://www.ssh.com/academy/ssh/tunneling/example as Matthias Wiedemann advised.
Total:
1 step. Connecting via SSH and create session SSH
SSH ssh = new SSH(user, password, host, port);
Session session = SSHUtil.createSession(ssh);
2 step. Creating a tunnel using SSH
SSHUtil.createChannel(session, outputConsole);
SSHUtil.createSshTunnel(session, port);
3 step. Connection via Telnet and executing Telnet commands using the tunnel port (instead of 5000, the port became 9999).
clientForTelnet = new ClientForTelnet(new Telnet(host, port));
outputConsole.print(TelnetUtil.connect(clientForTelnet));
outputConsole is PrintStream.
I got the following:
1.
public class SSHUtil{
private final static Logger LOGGER = LogManager.getLogger(SSHUtil.class);
public static Session createSession(SSH ssh) {
Session session = null;
try {
session = new JSch().getSession(ssh.getUSER(), ssh.getHOST().getIp(), ssh.getPORT().getPort());
session.setPassword(ssh.getPASSWORD());
session.setConfig("StrictHostKeyChecking", "no"); // It must not be recommended, but if you want to skip host-key check
session.connect(10000);
LOGGER.info("SSH session created");
} catch (JSchException e) {
LOGGER.error("SSH session not created " + e);
}
return session;
}
public static Channel createChannel(Session session, PrintStream output) {
Channel channel = null;
try {
channel = session.openChannel("shell");
channel.setInputStream(System.in);
channel.setOutputStream(output);
// streamOut = channel.getOutputStream();
channel.connect(10000);
LOGGER.info("SSH channel created");
} catch (JSchException e) {
LOGGER.error("SSH channel not created " + e);
}
return channel;
}
public static void createSshTunnel(Session session, Port port) {
// TODO сделать пул портов, чтобы исключить вероятность, что порт 9999 занят
try {
session.setPortForwardingL(9999, session.getHost(), port.getPort());
LOGGER.info("Tunnel created localhost:" + 9999 + " -> " + session.getHost() + ":" + port.getPort());
} catch (JSchException e) {
LOGGER.error("SSH tunnel not created " + e);
}
}
public static void disconnect(Session session) {
if (session != null && session.isConnected()) {
session.disconnect();
}
}
}
public class TelnetUtil {
private final static Logger LOGGER = LogManager.getLogger(TelnetUtil.class);
public static String connect(ClientForTelnet clientForTelnet) {
try {
// Connect to the server
clientForTelnet.getTc().connect(clientForTelnet.getTelnet().getHOST().getIp(), clientForTelnet.getTelnet().getPORT().getPort());
// Get input and output stream references
clientForTelnet.setIn(clientForTelnet.getTc().getInputStream());
clientForTelnet.setOut(new PrintStream(clientForTelnet.getTc().getOutputStream()));
} catch (IOException e) {
LOGGER.error(e);
}
return readUntil(clientForTelnet.getPrompt(), clientForTelnet.getIn());
}
public static String readUntil(String pattern, InputStream in) {
StringBuilder sb = new StringBuilder();
try {
char lastChar = pattern.charAt(pattern.length() - 1);
boolean found = false;
char ch = (char) in.read();
while (true) {
// System.out.print(ch);
sb.append(ch);
if (ch == lastChar) {
if (sb.toString().endsWith(pattern)) {
return sb.toString();
}
}
ch = (char) in.read();
}
} catch (IOException e) {
LOGGER.error(e);
}
return sb.toString();
}
public static void write(String value, PrintStream out) {
out.println(value);
out.flush();
}
public static String sendCommand(ClientForTelnet clientForTelnet, String command) {
write(command, clientForTelnet.getOut());
return readUntil(clientForTelnet.getPrompt(), clientForTelnet.getIn());
}
public static void disconnect(TelnetClient tc) {
try {
tc.disconnect();
} catch (IOException e) {
LOGGER.error(e);
}
}
}
public class SSH {
private final String USER;
private final String PASSWORD;
private final Host HOST;
private final Port PORT;
private final Port DEFAULT_PORT = new Port(22);
public SSH(String user, String password, Host host, Port port) {
this.USER = user;
this.PASSWORD = password;
this.HOST = host;
this.PORT = port;
}
public SSH(String user, String password, Host host) {
this.USER = user;
this.PASSWORD = password;
this.HOST = host;
this.PORT = DEFAULT_PORT;
}
public String getUSER() {
return USER;
}
public String getPASSWORD() {
return PASSWORD;
}
public Host getHOST() {
return HOST;
}
public Port getPORT() {
return PORT;
}
}
public class Telnet {
private final Host HOST;
private final Port PORT;
private final Port DEFAULT_PORT = new Port(5000);
public Telnet(Host host, Port port) {
this.HOST = host;
this.PORT = port;
}
public Telnet(Host host) {
this.HOST = host;
this.PORT = DEFAULT_PORT;
}
public Host getHOST() {
return HOST;
}
public Port getPORT() {
return PORT;
}
}
public class ClientForTelnet {
private final TelnetClient TC = new TelnetClient();
private final String PROMT = ">";
private final Telnet TELNET;
private InputStream in;
private PrintStream out;
public ClientForTelnet(Telnet telnet) {
this.TELNET = telnet;
}
public InputStream getIn() {
return in;
}
public PrintStream getOut() {
return out;
}
public Telnet getTelnet() {
return TELNET;
}
public String getPrompt() {
return PROMT;
}
public TelnetClient getTc() {
return TC;
}
public void setIn(InputStream in) {
this.in = in;
}
public void setOut(PrintStream out) {
this.out = out;
}
}
public class Host {
private final static Logger LOGGER = LogManager.getLogger(Host.class);
private String ip;
public Host(String ip) {
this.ip = ip;
}
public String getIp() {
return ip;
}
}
I am trying to implement a shell terminal in a webapp using websocket in spring. I am able to send a single command to the JSch "exec" channel and send the output back to the websocket.
The problem I am having is:
I am not able to retain the state of the shell like working directory, when I send a second command. How can I retain the previous state? I have tried using the same session but it does not work.
public String sendCommand(String command) {
StringBuilder outputBuffer = new StringBuilder();
try {
Channel channel = sesConnection.openChannel("exec");
((ChannelExec) channel).setCommand(command);
InputStream commandOutput = channel.getInputStream();
channel.connect();
int readByte = commandOutput.read();
while (readByte != 0xffffffff) {
outputBuffer.append((char) readByte);
readByte = commandOutput.read();
}
channel.disconnect();
} catch (IOException ioX) {
logWarning(ioX.getMessage());
return null;
} catch (JSchException jschX) {
logWarning(jschX.getMessage());
return null;
}
return outputBuffer.toString();
}
To send back to the websocket, in the controller I have :
private SSHManager getSSHInstance() {
String errorMessage = null;
if (sshInstance == null) {
sshInstance = new SSHManager(username, password, host, "", port);
errorMessage = sshInstance.connect();
System.out.println("Instance created");
if (errorMessage != null) {
throw new RuntimeException("Could not create an ssh connection");
}
}
System.out.println("Returning created instance");
return sshInstance;
}
#MessageMapping("/user")
#SendTo("/topic/user")
public UserResponse getResponse(String command) {
SSHManager currInstance = getSSHInstance();
String result = currInstance.sendCommand(command);
return new UserResponse(result);
}
I tried using the "shell" channel instead of "exec" which worked for getting the input and output through standard input and output stream but I could not get the real-time input and output from/back to the websocket and UI. I am not sure how to proceed from here. Any direction on where/what to look would be very helpful.
Here is my code for the SSH terminal through standard input/output stream:
import com.jcraft.jsch.*;
public class Terminal{
public static void main(String[] args){
try{
JSch jsch=new JSch();
String host = "127.0.0.1";
String user = "user";
String password = "pass";
Session session=jsch.getSession(user, host, 5679);
session.setPassword(password);
session.setConfig("StrictHostKeyChecking", "no");
session.connect(10000);
Channel channel=session.openChannel("shell");
channel.setInputStream(System.in);
channel.setOutputStream(System.out);
channel.connect(3*1000);
}
catch(Exception e){
System.out.println(e.getMessage());
}
}
}
To send the command from the UI, I have the following:
function sendCommand() {
if (stompClient != null) {
stompClient.send("/app/user", {}, JSON.stringify({'command': $("#command").val()}));
}
}
If you want to implement an interactive shell, you have to use the "shell" channel, not the "exec" channel. The "exec" channel is intended for automating individual commands.
Some references:
What is the difference between the 'shell' channel and the 'exec' channel in JSch
JSch Shell.java example
I am using a JSCH -SSH library to execute command in "shell" channel, but unable to find a way
to do 2 things:-
1) How to find whether the command is completely executed on remote unix box ?
2) How to capture the command output in String , instead of printing it on System.out console ?
Below is my code snippet which works fine to display shell command output on system.out
NOTE :I do NOT want to use ,"exec" channel, as it starts a new process for each command and does not remember "session" variables which were exported. I must use "shell" channel.
Below is my code snippet. Any help is appreciated.Thanks for your time.
try{
String commandToRun = "ls /tmp/*.log \n";
if(channel.isClosed())
channel=session.openChannel("shell");
byte[] bytes = commandToRun.getBytes();
ByteArrayInputStream bais=new ByteArrayInputStream(bytes);
channel.setInputStream(bais);
InputStream ins=channel.getInputStream();
channel.connect();
channel.setOutputStream(System.out);//This prints on console. Need 2 capture in String somehow?
//in-efficient way to allow command to execute completely on remote Unix machine
//DO NOT know a better way, to know when command is executed completely
Thread.sleep(5000L);
}
catch(Exception e){
System.out.println("Exception in executeCommand() --->"+ e.getMessage());
e.printStackTrace();
}
My solution may not be needed anymore for the OP, but anyone else who is searching for a solution to cover both conditions 1) waiting for the commands to finish on remote machine; and 2) capturing output as string; you can try this:
public class SshConnectionManager {
private static Session session;
private static ChannelShell channel;
private static String username = "";
private static String password = "";
private static String hostname = "";
private static Session getSession(){
if(session == null || !session.isConnected()){
session = connect(hostname,username,password);
}
return session;
}
private static Channel getChannel(){
if(channel == null || !channel.isConnected()){
try{
channel = (ChannelShell)getSession().openChannel("shell");
channel.connect();
}catch(Exception e){
System.out.println("Error while opening channel: "+ e);
}
}
return channel;
}
private static Session connect(String hostname, String username, String password){
JSch jSch = new JSch();
try {
session = jSch.getSession(username, hostname, 22);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.setPassword(password);
System.out.println("Connecting SSH to " + hostname + " - Please wait for few seconds... ");
session.connect();
System.out.println("Connected!");
}catch(Exception e){
System.out.println("An error occurred while connecting to "+hostname+": "+e);
}
return session;
}
private static void executeCommands(List<String> commands){
try{
Channel channel=getChannel();
System.out.println("Sending commands...");
sendCommands(channel, commands);
readChannelOutput(channel);
System.out.println("Finished sending commands!");
}catch(Exception e){
System.out.println("An error ocurred during executeCommands: "+e);
}
}
private static void sendCommands(Channel channel, List<String> commands){
try{
PrintStream out = new PrintStream(channel.getOutputStream());
out.println("#!/bin/bash");
for(String command : commands){
out.println(command);
}
out.println("exit");
out.flush();
}catch(Exception e){
System.out.println("Error while sending commands: "+ e);
}
}
private static void readChannelOutput(Channel channel){
byte[] buffer = new byte[1024];
try{
InputStream in = channel.getInputStream();
String line = "";
while (true){
while (in.available() > 0) {
int i = in.read(buffer, 0, 1024);
if (i < 0) {
break;
}
line = new String(buffer, 0, i);
System.out.println(line);
}
if(line.contains("logout")){
break;
}
if (channel.isClosed()){
break;
}
try {
Thread.sleep(1000);
} catch (Exception ee){}
}
}catch(Exception e){
System.out.println("Error while reading channel output: "+ e);
}
}
public static void close(){
channel.disconnect();
session.disconnect();
System.out.println("Disconnected channel and session");
}
public static void main(String[] args){
List<String> commands = new ArrayList<String>();
commands.add("ls -l");
executeCommands(commands);
close();
}
}
This solution is also useful if you need to send multiple commands at a time and keep the channel open to reuse it later.
For 2) u can use ByteArrayOutputStream
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
channel.setOutputStream(baos);
and then create new string from new String(baos.toByteArray())
For 1 have you tried to use 2>&1 at the end of your command?
String commandToRun = "ls /tmp/*.log 2>&1 \n";
Taking the example provided by Mihail, other info on the internets, and the feedback from Martin, here's a reworked solution using exec. Note that opening a session allows multiple commands to be sent, each one opening it's own channel for input/output.
Rant:I really dislike having to get the process' OUTPUT stream to write to. What an annoying paradigm (at least for me). What I wanted is the processes input stream to write my output to, and had an amazingly difficult time working out that it's inverted. Is it just me or does the following (pseudocode) not make way more sense??
channel.getInputStream().write("here's some text to write into my channel.");
String ret = channel.getOutputStream().getOutput();
Anyways, thanks to Mihail and Martin for their comments / input.
public class SSHConnectionManager {
private Session session;
private String username = "user";
private String password = "password";
private String hostname = "myhost";
public SSHConnectionManager() { }
public SSHConnectionManager(String hostname, String username, String password) {
this.hostname = hostname;
this.username = username;
this.password = password;
}
public void open() throws JSchException {
open(this.hostname, this.username, this.password);
}
public void open(String hostname, String username, String password) throws JSchException{
JSch jSch = new JSch();
session = jSch.getSession(username, hostname, 22);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no"); // not recommended
session.setConfig(config);
session.setPassword(password);
System.out.println("Connecting SSH to " + hostname + " - Please wait for few seconds... ");
session.connect();
System.out.println("Connected!");
}
public String runCommand(String command) throws JSchException, IOException {
String ret = "";
if (!session.isConnected())
throw new RuntimeException("Not connected to an open session. Call open() first!");
ChannelExec channel = null;
channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(command);
channel.setInputStream(null);
PrintStream out = new PrintStream(channel.getOutputStream());
InputStream in = channel.getInputStream(); // channel.getInputStream();
channel.connect();
// you can also send input to your running process like so:
// String someInputToProcess = "something";
// out.println(someInputToProcess);
// out.flush();
ret = getChannelOutput(channel, in);
channel.disconnect();
System.out.println("Finished sending commands!");
return ret;
}
private String getChannelOutput(Channel channel, InputStream in) throws IOException{
byte[] buffer = new byte[1024];
StringBuilder strBuilder = new StringBuilder();
String line = "";
while (true){
while (in.available() > 0) {
int i = in.read(buffer, 0, 1024);
if (i < 0) {
break;
}
strBuilder.append(new String(buffer, 0, i));
System.out.println(line);
}
if(line.contains("logout")){
break;
}
if (channel.isClosed()){
break;
}
try {
Thread.sleep(1000);
} catch (Exception ee){}
}
return strBuilder.toString();
}
public void close(){
session.disconnect();
System.out.println("Disconnected channel and session");
}
public static void main(String[] args){
SSHConnectionManager ssh = new SSHConnectionManager();
try {
ssh.open();
String ret = ssh.runCommand("ls -l");
System.out.println(ret);
ssh.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
I am trying to send some commands to a router and trying to read the console output. But the problem is every time i send a new command i get the same output. I tried creating a new session after every command is executed but I still get the same output. I got a sample online and modified it according to my needs. When i run it as a standalone class, it works perfectly. But when deployed on a server, it just doesn't seem to work right. To be honest, I am only its only been a day or two I have used the JSCH and documentation is very sparse.
This is how my code reads:
private void writeCommand(String command) throws IOException, JSchException {
Channel channel = getChannel();
((ChannelExec) channel).setCommand(command.getBytes());
channel.setInputStream(null);
((ChannelExec) channel).setErrStream(System.err);
readChannelOutput(channel);
}
private void readChannelOutput(Channel channel) {
byte[] buffer = new byte[1024];
try {
InputStream inputStream = channel.getInputStream();
channel.connect();
while (true) {
while (inputStream.available() > 0) {
int i = inputStream.read(buffer, 0, 1024);
if (i < 0) {
break;
}
deviceOut.updateDeviceOutput(new String(buffer, 0, i));
}
if (channel.isClosed()) {
break;
}
}
disconnect();
} catch(IOException e) {
e.printstacktrace();
} catch (JSchException e) {
e.printstacktrace();
}
}
public Session getSession () throws Exception {
if (session == null || !session.isConnected()) {
session = connect(getUserid(), getIpaddress(), getPassword(), getPort());
}
return session;
}
protected Session connect(String userName, String hostname, String password, int port) throws Exception {
JSch jSch = new JSch();
try {
session = jSch.getSession(userName, hostname, port);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.setPassword(password);
logger.debug("Connecting SSH to " + getIpaddress() + " - Please wait for few seconds... ");
session.connect();
logger.debug("Connected!");
if (session.isConnected()) {
setConnectionState(DeviceConnection.LOGIN_COMPLETE);
} else {
setConnectionState(DeviceConnection.LOGIN_FAILED);
throw new Exception("Failed to login to device");
}
deviceOut = new DeviceOutput(getLogger());
return session;
} catch (JSchException e) {
disconnect();
e.printstacktrace();
} catch (PalException e) {
disconnect();
e.printstacktrace();
}
}
The commands that I am trying to send are
term len 512
and
show running-config | include hostname
But the output just stays the same. The first command should not even have any ouput to be honest. Is there anything I am doing wrong?
I have ServerHandshakeHandler which extends ChannelInboundHandlerAdapter. Client emulates multiple access to server. After some time of successful communication server stops responding when client tries to connect. It doesn't show any incoming connections. Client restart doesn't help, only restart of server.
I tried to set telnet connection when server stops responding: connection establishes but I can't get any response from server (when server is in normal state, it sends response). Similar situation with nmap -v --packet-trace -sT localhost -p {port} : nmap discovers port as open, but there is no log information about incoming connection on server.
Server:
public class ServerHandshakeHandler extends ChannelInboundHandlerAdapter {
private final ChannelGroup group;
private static final byte HANDSHAKE_SUCCEDED = 1;
private static final byte HANDSHAKE_FAILED = 0;
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
public ServerHandshakeHandler(ChannelGroup group) {
this.group = group;
}
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
LOG.debug("in ServerHandshakeHandler.channelRead");
ByteBuf buf = (ByteBuf) msg;
String someField = getSomeField(buf);
ReferenceCountUtil.release(msg);
if (someField.isEmpty()) {
this.fireHandshakeFailed(ctx);
return;
}
LOG.debug("Removing handshake handler from pipeline.");
ctx.pipeline().remove(this);
this.fireHandshakeSucceeded(ctx);
}
#Override
public void channelActive(final ChannelHandlerContext ctx) {
LOG.debug("in ServerHandshakeHandler.channelActive, group size = " + this.group.size());
this.group.add(ctx.channel());
LOG.debug("Incoming connection from: {}",
ctx.channel().remoteAddress().toString());
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
LOG.error("exception caught ", cause);
if (ctx.channel().isActive()) {
ctx.channel().close();
} else {
this.fireHandshakeFailed(ctx);
}
}
private void fireHandshakeFailed(ChannelHandlerContext ctx) {
LOG.debug("fire handshake failed");
ByteBuf buf = Unpooled.buffer(1);
buf.writeByte(HANDSHAKE_FAILED);
ctx.channel().writeAndFlush(buf);
ctx.channel().close();
ctx.fireUserEventTriggered(HandshakeEvent.handshakeFailed(ctx.channel()));
}
private void fireHandshakeSucceeded(ChannelHandlerContext ctx) {
LOG.debug("fire handshake succeded");
ByteBuf buf = Unpooled.buffer(1);
buf.writeByte(HANDSHAKE_SUCCEDED);
ctx.channel().writeAndFlush(buf);
ctx.fireUserEventTriggered(HandshakeEvent
.handshakeSucceeded(ctx.channel()));
}
}
Client:
public class MyClient {
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private String host;
private int port;
private Socket socket;
public Client(String host, int port) {
this.host = host;
this.port = port;
}
public void send(String id, String message) {
try {
socket = new Socket(host, port);
LOG.debug("connected to server");
if (performHandshake(id)) {
LOG.debug("handshake success");
sendMessage(message);
}
socket.close();;
} catch (IOException ex) {
LOG.error("error while sending data", ex);
}
}
private boolean performHandshake(String id) {
try {
byte[] request = handshakeRequest(id);
writeBytes(request);
byte[] response = readBytes(1);
return (response != null && response.length == 1 && response[0] == 1);
} catch (IOException ex) {
LOG.error("perform handshake error", ex);
return false;
}
}
private byte[] handshakeRequest(String id) throws UnsupportedEncodingException {...}
private void writeBytes(byte[] data) throws IOException {
OutputStream out = socket.getOutputStream();
out.write(data);
}
private byte[] readBytes(int length) throws IOException {
InputStream in = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte buffer[] = new byte[1024];
int currentLength = 0;
while (currentLength < length) {
int size = in.read(buffer); //here client stops waiting server response
if (size == -1) {
throw new IOException("unexpected end of stream");
}
baos.write(buffer, 0, size);
currentLength += size;
}
return baos.toByteArray();
}
}
SOLVED! There was narrow piece of code where I was calling synchronized function with connection to database. This connection cannot be established for some reasons and the function hangs. Thread in this function goes into WAITING state. After a while other treads try to access this function and become BLOCKED. That's why server stops processing incoming connections.
I recommend jvisualvm profiling tool, it helped me to find this bug.