1

I'm creating a C++ class for executing SSH commands, using libssh2.

The life cycle of a libssh2 SSH session goes through these stages:

  1. Initialization (acquires local resources)
  2. Handshake/Authentication (establishes an SSH session on the remote host)
  3. Disconnect (terminates SSH session on the remote host)
  4. Free (releases local resources; if necessary, also performs step 3).

Before step 1, we have to open a socket, which we pass to libssh2 in step 2. From then on, we don't need to pass the socket anymore, as libssh2 will store a reference to it. After step 4, we can close the socket.

I'm exposing this through a class (SSHSession), and I'd like setup (steps 1 and 2) to happen on ctor and teardown (steps 3 and 4) to happen on dtor (since step 2 is time-consuming, this will allow me to keep a pool of sessions open and reuse it to execute commands).

My first attempt concentrated all the code on SSHSession, and its ctor quickly became a mess, with the "if this step fails, then we must see what has already been done and undo it" routine; the dtor was not as complex, but I still found it too "busy".

Then, I divided the work across several classes, implementing RAII for each acquire/release step, namely:

  • Steps 1 and 4.
  • Steps 2 and 3.

I created a class SessionConnection that implemented steps 2 and 3, and had a member of type SessionHandle that implemented steps 1 and 4; it also had the socket as a data member, creating the first order dependency on ctor/dtor - the socket could not be destroyed before the SessionHandle member.

As I was considering my design, I figured I could arrange steps 2 and 3 like this:

2.1. Handshake (establishes an SSH session on the remote host)

2.2. Authentication

3. Disconnect (terminates SSH session on the remote host)

Which means I could further simplify my SessionConnection class, implementing another class to perform RAII on steps 2.1 and 3, and ending up with something like this:

  • Class SSHSession has a SessionConnection data member.
  • Class SessionConnection implements step 2.2.
  • SessionConnection has a socket data member.
  • SessionConnection has a SessionHandle data member, that implements steps 1 and 4. This must be destroyed before the socket.
  • SessionConnection has a RemoteSessionHandle data member, that implements steps 2.1 and 3. This must be created after, and destroyed before, the SessionHandle data member.

This greatly simplifies my ctors/dtors, courtesy of RAII. And I find conceptually sound; if, on the one hand, we can look at these as states the SSH session goes through, on the other hand, we can also see them as resources (local, remote) we're managing.

However, I now have a strict construction/destruction order in SessionConnection, and while I believe it's an improvement (and I found nothing in my research that stated "this is evil", only "this should be clearly documented"), I'm interested in other opinions, and I'll happily accept info/pointers about possible alternatives to this design.

What I've looked at, so far:

  • ScopeGuard, ScopeExit. It's a similar mechanism, I didn't find anything I could use as an improvement on my design.
  • State Machine. I couldn't find a way to simplify the design with it, and it didn't solve one problem RAII does, namely, cleanup if something fails.

Thanks for your time.

4

1 回答 1

1

这种设计似乎没有根本性的错误。

如果您想取消对 的成员的构造/销毁顺序的限制SessionConnection,您可以执行以下操作:

  • 班级SSHSession
    • 执行步骤 2.2
    • SSHConnection会员
  • 班级SSHConnection
    • 实施步骤 2.1 和 3
    • LocalSessionHandle会员
  • 班级LocalSessionHandle
    • 执行步骤 1 和 4
    • Socket会员

定义的成员及其包含的类的构造/销毁顺序可确保以正确的顺序执行这些步骤。

于 2012-11-08T12:56:26.327 回答