Proper error handling is essential for robust SSH applications. QtSsh provides multiple mechanisms for detecting and handling errors at both the connection and channel levels.
The primary method for asynchronous error detection.
connect(client, &SshClient::sshError, [=]() {
qDebug() << "SSH connection error occurred";
});
connect(client, &SshClient::sshDisconnected, [=]() {
qDebug() << "Disconnected from server";
});connect(proc, &SshProcess::failed, [=]() {
qDebug() << "Command execution failed";
qDebug() << "Errors:" << proc->errMsg();
});connect(scp, &SshScpSend::failed, [=]() {
qDebug() << "File transfer failed";
});Monitor connection and channel states.
SshClient::SshState state = client->sshState();
switch(state) {
case SshClient::Error:
qDebug() << "Client in error state";
break;
case SshClient::Ready:
qDebug() << "Client ready";
break;
case SshClient::Unconnected:
qDebug() << "Not connected";
break;
// ... other states
}SshChannel::ChannelState state = channel->channelState();
if (state == SshChannel::Error) {
qDebug() << "Channel error";
}Some methods return values indicating success or failure.
// SFTP operations
SshSFtp *sftp = client->getChannel<SshSFtp>("upload");
QString error = sftp->send(local, remote);
if (!error.isEmpty()) {
qDebug() << "Upload failed:" << error;
}
// Check error flag
if (sftp->isError()) {
qDebug() << "Errors:" << sftp->errMsg();
}Timeout-based error detection.
bool connected = client->waitForState(SshClient::Ready);
if (!connected) {
qDebug() << "Connection timeout";
}Symptoms:
sshErrorsignal emitted- State becomes
SshClient::Error
Possible Causes:
- Wrong hostname or IP address
- Port blocked by firewall
- SSH service not running
- Network connectivity issues
Solutions:
client->setConnectTimeout(30000); // Increase timeout
// Test connectivity first
QTcpSocket testSocket;
testSocket.connectToHost("hostname", 22);
if (!testSocket.waitForConnected(5000)) {
qDebug() << "Cannot reach host";
}Symptoms:
sshErrorsignal after connection attempt- State changes to
ErrorduringAuthenticationphase
Possible Causes:
- Wrong username or password
- Invalid SSH key
- Key permissions too open (should be 600)
- Server doesn't allow password authentication
- Server requires different authentication method
Solutions:
// Try password authentication
client->setPassphrase("correct-password");
// Try key authentication
client->setKeys("~/.ssh/id_rsa.pub", "~/.ssh/id_rsa");
// Check key permissions on Linux/macOS:
// chmod 600 ~/.ssh/id_rsa
// chmod 644 ~/.ssh/id_rsa.pubSymptoms:
- Connection fails during handshake
- libssh2 reports host key mismatch
Solutions:
// Set known hosts file
client->setKownHostFile("/home/user/.ssh/known_hosts");
// Or manually add host
SshKey hostKey = /* get from first connection */;
client->addKnownHost("hostname", hostKey);
client->saveKnownHosts("/home/user/.ssh/known_hosts");Symptoms:
- Channel remains in
Openningstate - Operations don't execute
Possible Causes:
- Client not in
Readystate - Too many channels open
- Server resource limits
Solutions:
// Ensure client is ready
if (client->sshState() != SshClient::Ready) {
qDebug() << "Wait for ready state first";
return;
}
// Close unused channels
channel->close();Symptoms:
failedsignal emittedisError()returns true
Possible Causes:
- Command not found on remote system
- Permission denied
- Command syntax error
- Connection lost during execution
Solutions:
SshProcess *proc = client->getChannel<SshProcess>("test");
connect(proc, &SshProcess::failed, [=]() {
qDebug() << "Command failed";
QStringList errors = proc->errMsg();
for (const QString &err : errors) {
qDebug() << " " << err;
}
});
// Use full paths for commands
proc->runCommand("/usr/bin/ls -la");
// Check command exists first
proc->runCommand("which ls");Symptoms:
send()returns non-empty error stringisError()returns true
Possible Causes:
- Remote path doesn't exist
- No write permissions
- Disk full
- Local file doesn't exist or can't be read
Solutions:
SshSFtp *sftp = client->getChannel<SshSFtp>("upload");
// Check local file exists
if (!QFile::exists(localPath)) {
qDebug() << "Local file not found";
return;
}
// Create remote directory first
QFileInfo remoteInfo(remotePath);
sftp->mkpath(remoteInfo.path());
// Upload
QString error = sftp->send(localPath, remotePath);
if (!error.isEmpty()) {
qDebug() << "Upload error:" << error;
if (sftp->isError()) {
for (const QString &err : sftp->errMsg()) {
qDebug() << " " << err;
}
}
}Symptoms:
get()returns false- File not created locally
Possible Causes:
- Remote file doesn't exist
- No read permissions on remote
- No write permissions locally
- Insufficient local disk space
Solutions:
SshSFtp *sftp = client->getChannel<SshSFtp>("download");
// Check remote file exists
if (!sftp->isFile(remotePath)) {
qDebug() << "Remote file doesn't exist";
return;
}
// Check remote file size
quint64 size = sftp->filesize(remotePath);
qDebug() << "File size:" << size << "bytes";
// Ensure local directory exists
QFileInfo localInfo(localPath);
QDir().mkpath(localInfo.path());
// Download
bool success = sftp->get(remotePath, localPath, true);
if (!success) {
qDebug() << "Download failed:" << sftp->errMsg();
}Symptoms:
failedsignal emitted- Transfer stops mid-way
Solutions:
SshScpSend *scp = client->getChannel<SshScpSend>("upload");
connect(scp, &SshScpSend::failed, [=]() {
qDebug() << "SCP upload failed";
// Implement retry logic
});
connect(scp, &SshScpSend::progress, [=](qint64 sent, qint64 total) {
// Monitor for stalls
static qint64 lastSent = 0;
if (sent == lastSent) {
qDebug() << "Transfer stalled?";
}
lastSent = sent;
});
scp->send(localPath, remotePath);Symptoms:
listen()doesn't establish tunnel- No
connectionChangedsignals
Possible Causes:
- Port already in use
- Permission denied (ports < 1024)
- Server doesn't allow port forwarding
- Firewall blocking
Solutions:
SshTunnelOut *tunnel = client->getChannel<SshTunnelOut>("forward");
// Use auto-assigned port
tunnel->listen(0, "remote-host"); // Port 0 = auto
quint16 actualPort = tunnel->localPort();
qDebug() << "Tunnel on port:" << actualPort;
// Check port availability first
QTcpServer testServer;
if (!testServer.listen(QHostAddress::LocalHost, 8080)) {
qDebug() << "Port 8080 already in use";
}
testServer.close();class SshErrorHandler : public QObject {
Q_OBJECT
public:
SshErrorHandler(SshClient *client) : m_client(client) {
connect(client, &SshClient::sshStateChanged,
this, &SshErrorHandler::onStateChanged);
connect(client, &SshClient::sshError,
this, &SshErrorHandler::onError);
}
private slots:
void onStateChanged(SshClient::SshState state) {
qDebug() << "State:" << state;
if (state == SshClient::Error) {
qDebug() << "Client entered error state";
emit errorOccurred("Client error");
}
}
void onError() {
qDebug() << "SSH Error signal received";
SshClient::SshState state = m_client->sshState();
QString errorMsg;
switch(state) {
case SshClient::Authentication:
errorMsg = "Authentication failed";
break;
case SshClient::HandShake:
errorMsg = "Handshake failed";
break;
default:
errorMsg = "Unknown error";
}
emit errorOccurred(errorMsg);
}
signals:
void errorOccurred(QString message);
private:
SshClient *m_client;
};class RetryConnector : public QObject {
Q_OBJECT
public:
RetryConnector(SshClient *client, int maxRetries = 3)
: m_client(client), m_maxRetries(maxRetries), m_retries(0), m_backoff(1000) {
connect(m_client, &SshClient::sshReady, this, &RetryConnector::onConnected);
connect(m_client, &SshClient::sshError, this, &RetryConnector::onError);
}
void connect(const QString &user, const QString &host) {
m_user = user;
m_host = host;
tryConnect();
}
private slots:
void onConnected() {
qDebug() << "Connected successfully";
m_retries = 0;
emit connected();
}
void onError() {
m_retries++;
if (m_retries >= m_maxRetries) {
qDebug() << "Max retries reached, giving up";
emit failed();
return;
}
int delay = m_backoff * (1 << (m_retries - 1)); // Exponential backoff
qDebug() << "Retry" << m_retries << "in" << delay << "ms";
QTimer::singleShot(delay, this, &RetryConnector::tryConnect);
}
void tryConnect() {
qDebug() << "Attempting connection...";
m_client->connectToHost(m_user, m_host);
}
signals:
void connected();
void failed();
private:
SshClient *m_client;
int m_maxRetries;
int m_retries;
int m_backoff;
QString m_user;
QString m_host;
};void robustFileTransfer(SshClient *client, const QString &local, const QString &remote) {
// Try SFTP first (more reliable)
SshSFtp *sftp = client->getChannel<SshSFtp>("transfer");
QString error = sftp->send(local, remote);
if (error.isEmpty()) {
qDebug() << "SFTP transfer successful";
return;
}
// Fall back to SCP
qDebug() << "SFTP failed, trying SCP...";
SshScpSend *scp = client->getChannel<SshScpSend>("transfer-scp");
connect(scp, &SshScpSend::finished, []() {
qDebug() << "SCP transfer successful";
});
connect(scp, &SshScpSend::failed, []() {
qDebug() << "Both SFTP and SCP failed";
});
scp->send(local, remote);
}class SshLogger : public QObject {
Q_OBJECT
public:
SshLogger(SshClient *client, const QString &logFile) : m_logFile(logFile) {
m_log.setFileName(logFile);
m_log.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text);
m_stream.setDevice(&m_log);
connect(client, &SshClient::sshStateChanged,
this, &SshLogger::logState);
connect(client, &SshClient::sshError,
this, &SshLogger::logError);
}
~SshLogger() {
m_log.close();
}
private slots:
void logState(SshClient::SshState state) {
QString timestamp = QDateTime::currentDateTime().toString(Qt::ISODate);
m_stream << timestamp << " - State: " << state << "\n";
m_stream.flush();
}
void logError() {
QString timestamp = QDateTime::currentDateTime().toString(Qt::ISODate);
m_stream << timestamp << " - ERROR occurred\n";
m_stream.flush();
}
private:
QString m_logFile;
QFile m_log;
QTextStream m_stream;
};
// Usage
SshClient *client = new SshClient("logged");
SshLogger *logger = new SshLogger(client, "/var/log/ssh-client.log");QtSsh uses Qt logging categories:
// Enable all SSH logging
QLoggingCategory::setFilterRules("sshclient=true\n"
"sshchannel=true\n"
"logsshprocess=true\n"
"logsshsftp=true\n"
"logsshtunnelin=true\n"
"logsshtunnelout=true");connect(client, &SshClient::sshStateChanged, [](SshClient::SshState state) {
qDebug() << "Client state:" << state;
});
connect(channel, &SshChannel::stateChanged, [](SshChannel::ChannelState state) {
qDebug() << "Channel state:" << state;
});// Helper function is provided
const char *errorStr = sshErrorToString(errorCode);
qDebug() << "libssh2 error:" << errorStr;class OperationTracer {
public:
static void trace(const QString &operation) {
qDebug() << QTime::currentTime().toString()
<< "[" << operation << "]";
}
};
// Usage
OperationTracer::trace("Connecting to server");
client->connectToHost(user, host);
OperationTracer::trace("Executing command");
proc->runCommand(cmd);- Always connect error signals before starting operations
- Check return values from synchronous methods
- Implement timeouts for all operations
- Log errors for production debugging
- Provide user feedback for long operations
- Handle disconnections gracefully with cleanup
- Validate inputs before SSH operations
- Test error paths during development
- Use try-catch for Qt exceptions in error handlers
- Document error conditions in your code
// Use wrong port
client->connectToHost("user", "host", 2222); // Wrong portclient->setPassphrase("wrong-password");
client->connectToHost("user", "host");proc->runCommand("/nonexistent/command");// Try to upload non-existent file
sftp->send("/nonexistent/file.txt", "/remote/file.txt");
// Try to download from read-protected location
sftp->get("/root/protected.txt", "/local/file.txt");- SshClient API - Connection management
- Quick Start Guide - Basic usage examples
- Installation Guide - Setup and building