基于Libevent转发的内网端口暴露(二):优化重构

  上面一篇文章发了之后,做了一些稍微的改进:添加了会话起始时候帐户信息加密传输,以及服务端线程池初始化的时候等待完成。然后现在清醒的思考了一下(建议效率低的时候不要写BUG),发现最初的设计过于的简单化,将所有的控制包和数据转发都通过客户端和服务端所建立的唯一连接传输,这样的设计不可避免的会带来一些问题:
  (1) 每次传输需要把包头读出来进行解析,找到目标地址、数据包长度以及本地客户端的映射表然后转发,这个过程中数据会被多次的拷贝,所以效率会很低;
  (2) 所有的数据都走一条链路,那么总体的吞吐量是有限的,尤其当数据量大了的时候对交互软件响应极为不利;
  (3) 对于服务端来说,一个客户会话只有一对bufferevent,那么采用的Libevent/epoll的长处根本没发挥出来,可能这样做的效率还不如长连接;
  (4) 所有鸡蛋都放在一个篮子里面,万一这条线路挂了,那么所有的端口都无法工作,对整个系统的可靠性也不利;
  (5) 最要命的是,原先的设计每个侦听端口映射只能建立一条连接,硬伤啊!

sshinner

  对于没有经验的“狗驾驶”来说,重构是再所难免的。

日了狗了

  为此,在原先的仓库中建立了一个新devel分支,打算对整个项目进行相应的重构,稳定后可能会rebase过去。新的设计思路如下:
  (1) 初始流程还是相同的,首先CLT_DAEMON启动后向SRV加密传输账户信息,通过认证(尚未实现)后建立account和activity的数据结构,然后CLT_USR端启动,进行会话信息的校验,这样CLT和SRV的连接就正式建立了,这个过程中为连接的套接字建立bufferevent,分别记作be_main_daemon和be_main_usr,主要作为控制信息发送的通道;
  (2) CLT_USR同样对本地感兴趣的每个端口创建侦听套接字,等待应用程序连接;
  (3) 一旦有应用程序连接CLT_USR之后,首先为这个连接的套接字创建bufferevent,记作be_usr_local(同时记录连接的端口号l_sock作为标识,这样就可以有多个同类型的连接),然后向SRV发起一个连接请求,将新建立的请求创建bufferevent,记作be_usr_srv,然后通过这个连接向服务器发送HD_CMD_CONN消息;
  (4) 服务器收到HD_CMD_CONN的控制包后,得知这是一个数据通信连接,会取消这个套接字原本在connect事件中建立的bufferevent,然后添加到指定线程待处理队列中;同时将这个包再转发到CLT_DAEMON端,促发CLT_DAEMON端做通信端的准备;
  (5) 线程处理这个请求,建立数据通信的bufferevent,记作be_srv_usr,用l_sock进行标识;
  (6) CLT_DAEMON收到HD_CMD_CONN消息后,会根据端口连接本地的服务,建立bufferevent记为be_daemon_local,然后再类似地向服务器请求连接,建立be_daemon_srv,并通过be_daemon_srv发送HD_CMD_CONN消息;
  (7) SRV处理HD_CMD_CONN消息,建立be_srv_daemon,然后根据l_sock将be_srv_daemon和be_srv_usr进行关联;
通过上面的步骤,就为每个链接建立了

be_usr_local:be_usr_srv <-> be_srv_usr:be_srv_daemon <-> be_daemon_srv:be_daemon_local

  这样的一个转发通道。在这个通道中,不必读取解析包头,他们都是要转发的净负荷数据,同时转发采用bufferevent_write_buffer,尽可能的避免了数据的不必要复制。
  (8) 还需要注意的是,上面CLT端的bufferevent(be_usr_srv/local, be_daemon_srv/local)起初都是只建立不使能的,当SRV处理了CLT_DAEMON的HD_CMD_CONN后,整个链路就打通了,然后线程会通过be_main_daemon和be_main_usr分别向两段发送HD_CMD_CONN_ACT包,两个cLT端收到这个包后,启用bufferevent_enable使能EV_READ|EV_WRITE,然后才开始真正的数据传输。

  测试发现,通信的速度比之前的设计快的多,而且ssh也工作的非常正常!欢迎大家体验,clone的时候注意选择devel分支哦!

本文完!