基于Tight VNC的远程协助功能的实现

整体布局

  在远程协助中,请求协助的一方称为被协助方,或者(远程协助)服务端;响应的一方称为协助方,或者(远程协助)客户端;这里命名被协助方为Cv,而协助方命名为Cs,中转服务器为Sr。

  TightVNC是免费为个人和商业用途,提供有用的,完整的源代码, 在管理,技术支持,教育和许多其他用途, 跨平台,可用于Windows和Unix,Java客户端,包括 与标准的VNC软件兼容,符合RFB协议规范。

TightVNC 分为Server和 Viewer两部分,Server作为请求协助端,Viewer协助端亦即是Cs和Cv 。

  Repeater为中转服务器Sr,当Server连接Viewer是,通过Sr相互转发数据。对Cs的监控端口为5500,Cv的监控端口为5900,当然5500、5900都是vnc的默认端口,这样会引起其他一些VNC软件的冲突,所以Cs和Cv监控端口都需要修改。

整个设备架设可简化为如图1所示,分为情况

1)     协助用户Cv和请求协助用户Cs1在同一局域网内,这时Cv与Cs1可以直接进行TCP通信;

2)     协助用户Cv和请求协助用户Cs2不在同一局域网内,这时Cv与Cs2只能通过中转服务器相互转发数据进行通信。

 

 

 整体布局

 

流程图

一、被协助用户Cs请求协助用户Cv的协助过程流程图如图2所示;

 Cs请求Cv协助过程流程图

1)     被协助者

  1. 用户界面操作响应,向在线用户发送1301请求(远程协助请求)。
  2. 1301-Confirmation消息处理:

如果对方拒绝接受请求,显示信息窗口,提示“远程请求被对方拒绝”。

如果对方接受请求,则创建远程协助对象;是否允许对方查看,调用远程协助对象的StartServer,参数为对方用户ID。

申请受控请求

2)     协助者

  1. 处理1301-Request消息。

窗口提示用户“xxxx发出远程协助邀请,您是否同意?”;如果“同意”则创建远程协助对象,调用远程协助对象Connect方法,连接对方的IP以及Port,建立TCP连接。调用用远程协助对象的SetHost(),参数为用户自己ID。

  1. 处理申请受控消息。

调用用远程协助对象的SetViewOnly(),参数为false;远程协助建立成功。

双方通过中转服务器或者直接建立TCP连接,完成远程协助以及数据交互。

二、Server两种请求协助方式Connect和Listen

1)     在局域网中Server端和 Viewer端如果能够直接进行TCP通信的时,这时就不需要通过中转服务器,Viewer直接连接Server。流程图如图3所示

 

 

Cv与Cs直接进行TCP通信流程图

2)     当Server端和 Viewer端不能直接的TCP通信时,亦就可能是在两个不同的局域网中,这时就需要通过中转服务器Repeater进行Server端和 Viewer端的消息转发。流程图如图4所示

 

Cv与Cs通过Repeater通信流程图

远程协助插件设计方案

将Server和 Viewer封装在两个动态库中,分别为RemoteHelpServer和RemoteHelpViewer,外部调用RemoteHelpServer和RemoteHelpViewer时,应为单实例调用,即是已经作为一个客户端或者是服务器端时,不能再与其他人进行远程协助。

外部接口包括:连接方式、调节画面质量、画面模式和协助方式等

  1. 连接方式:Viewer直连Server和通过Repeater与Server通信;
  2. 调节画面质量:1-9级别的lpeg图像压缩;
  3. 画面模式:8位和24位画面质量;
  4. 协助方式:仅屏幕查看和邀请方请求受控;
  5. 远程协助窗体模式:最大化,还原和最小化;
  6. 快捷键:Shift+Ctrl+Alt+F 全屏、ESC退出全屏。

RemoteHelpServer和RemoteHelpViewer动态库接口函数说明

Server端接口函数:

//参数:无

//返回值:Server端实例指针

//作用:创建远程协助Server端实例

RemoteHelpHandle *CreateRemoteHelpServer();

 

//参数:Server端实例指针

//返回值:无

//作用:销毁远程协助Server端实例

void DestoryRemoteHelpServer(RemoteHelpHandle *handle);

 

//参数:Server端实例指针,中转服务器IP及端口号

//返回值:成功时返回Socket指针

//作用:Server连接中转服务器

SocketIPv4 *ServerConnect(RemoteHelpHandle *handle,TCHAR *host,unsigned short port);

 

//参数:Server端实例指针,监听端口号

//返回值:成功时返回Socket指针

//作用:Server监听

SocketIPv4 *ServerListen(RemoteHelpHandle *handle,unsigned short port);

 

//参数:Server端实例指针,Socket指针,是否作为服务监听状态

//返回值:0,1

//作用:Server确认Viewer可以查看

int AddNewConnection(RemoteHelpHandle *handle,SocketIPv4 *socket,bool bIsListenMode);

 

//参数:Server端实例指针,连接凭证

//返回值:无

//作用:Server连接凭证

void SetServerConnectionTag(RemoteHelpHandle *handle,char *pszConnectionTag);

 

//参数:Server端实例指针

//返回值:无

//作用:Server端断开连接

void ServerDisconnect(RemoteHelpHandle *handle);

Viewer端接口函数:

//参数:无

//返回值:Viewer端实例指针

//作用:创建远程协助Viewer端实例

RemoteHelpHandle *CreateRemoteHelpViewer();

 

//参数:Viewer端实例指针

//返回值:无

//作用:销毁远程协助Viewer端实例

void  DestoryRemoteHelpViewer(RemoteHelpHandle *handle);

 

//参数:Viewer端实例指针,中转服务器IP及端口号

//返回值:关闭窗体时返回true,其它为false

//作用:Viewer连接中转服务器

bool  ViewerConnect(RemoteHelpHandle *handle,char *host,int port);

 

//参数:Viewer端实例指针,中转服务器IP及端口号

//返回值:关闭窗体时返回true,其它为false

//作用:Viewer连接中转服务器

void  SetViewerConnectionTag(RemoteHelpHandle *handle,char *pszConnectionTag);

 

//参数:Viewer端实例指针,是否使用8Bit

//返回值:无

//作用:远程协助图像质量,两种情况8Bit,24Bit

void  Set8BitColor(RemoteHelpHandle *handle,bool use8Bit);

 

//参数:Viewer端实例指针,压缩率级数

//返回值:无

//作用:远程协助图像压缩率

void setJpegCompressionLevel(RemoteHelpHandle *handle,int level);

 

//参数:Viewer端实例指针, 是否为只查看

//返回值:无

//作用:设置远程协助只查看或可控

void setViewOnly(RemoteHelpHandle *handle,bool viewOnly);

 

//参数:Viewer端实例指针, 是否为全屏

//返回值:无

//作用:设置远程协助窗体为全屏或退出全屏

void setFullScreenMode(RemoteHelpHandle *handle,bool fullScreenMode);

 

//参数:Viewer端实例指针

//返回值:无

//作用:Viewer端断开连接

void ViewerDisconnect(RemoteHelpHandle *handle);

程序界面

一、远程协助握手过程窗体界面显示:

  请求远程协助 

Viewer接受远程协助,等待确认Server确认查看

Server确认查看

 Server已申请受控 

二、客户端Cv:如图5所示,连接成功后分别有属性设置、最大化、断开等按钮;左下方为显示图像面板。

客户端Cv

三、属性设置:如图6所示,调节画面质量:1-9级别的lpeg图像压缩;画面模式:8位和24位画面质量

属性设置

四、客户端Cs:如图7所示,连接成功后,可以断开远程控制或者停止受控等操作。

客户端Cs

五、远程协助窗体:窗体模式分别有最大化,还原和最小化;快捷键分别有Shift+Ctrl+Alt+F 全屏、ESC退出全屏。当窗体最大化即全屏时,顶部出现工具条,可以控制窗体最大化,还原和最小化等操作,如图8所示,

 远程协助全屏模式

 注: 

  在这个功能刚开始做的时候,google 、百度了很多这方面的资料,无奈资料还是比较少的。其中有一篇论文《基于P2P的远程协助系统》,写得比较详细的方案,但最终没有实现P2P的方法,可能是一开始就走了弯路和现成的系统限制了,时间方面不允许和稳定性。

  《基于P2P的远程协助系统》里面写是通过本地代理,中转服务器建立桥梁实现远程协助通信。其中本地代理可以不管VNC里面代码的逻辑具体实现,只是负责通信,对远程协助与VNC版本轻松解耦,没有依赖VNC版本,可以不断地使VNC更新换代有重要意义。还有一个作用是可以TCP打洞实现P2P《TCP实现P2P通信、TCP穿越NAT的方法、TCP打洞》,不依赖VNC内部发送规则。

  《TCP实现P2P通信、TCP穿越NAT的方法、TCP打洞》一文中这位前辈写TCP实现P2P的方法,我想这对一些刚开始做TCP实现P2P通信有很大的帮助,感谢他的分享的精神,至少我对TCP实现P2P通信有所了解了。他所说的实现过程如下

  1、 S启动两个网络侦听,一个叫【主连接】侦听,一个叫【协助打洞】的侦听。
  2、 A和B分别与S的【主连接】保持联系。
  3、 当A需要和B建立直接的TCP连接时,首先连接S的【协助打洞】端口,并发送协助连接申请。同时在该端口号上启动侦听。注意由于要在相同的网络终端上绑定到不同的套接字上,所以必须为这些套接字设置 SO_REUSEADDR 属性(即允许重用),否则侦听会失败。
  4、 S的【协助打洞】连接收到A的申请后通过【主连接】通知B,并将A经过NAT-A转换后的公网IP地址和端口等信息告诉B。
  5、 B收到S的连接通知后首先与S的【协助打洞】端口连接,随便发送一些数据后立即断开,这样做的目的是让S能知道B经过NAT-B转换后的公网IP和端口号。
  6、 B尝试与A的经过NAT-A转换后的公网IP地址和端口进行connect,根据不同的路由器会有不同的结果,有些路由器在这个操作就能建立连接(例如我用的TPLink R402),大多数路由器对于不请自到的SYN请求包直接丢弃而导致connect失败,但NAT-A会纪录此次连接的源地址和端口号,为接下来真正的连接做好了准备,这就是所谓的打洞,即B向A打了一个洞,下次A就能直接连接到B刚才使用的端口号了。
  7、 客户端B打洞的同时在相同的端口上启动侦听。B在一切准备就绪以后通过与S的【主连接】回复消息“我已经准备好”,S在收到以后将B经过NAT-B转换后的公网IP和端口号告诉给A。
  8、 A收到S回复的B的公网IP和端口号等信息以后,开始连接到B公网IP和端口号,由于在步骤6中B曾经尝试连接过A的公网IP地址和端口,NAT-A纪录了此次连接的信息,所以当A主动连接B时,NAT-B会认为是合法的SYN数据,并允许通过,从而直接的TCP连接建立起来了。

  整个过程跟UDP打洞很类似,我认为他文中写可能是没有注重的写出一个重点,就是“Sock.SetSockOpt ( SO_REUSEADDR, &nOptValue , sizeof(UINT) )”其中的SO_REUSEADDR,因为这样以上的过程才能够实现。

  还有在OSChina里的一个讨论贴“C++实现TCP打洞的思想”,有些人说的tcp打洞无法实现,那些人我认为是根本没有去了解过吧,而且QQ的远程协助是TCP连接的,难道QQ的远程协助都是经服务器转发的?

  知识有限,基本上想说的都说了!


  

 

posted on 2012-03-29 19:28  刀光建影  阅读(4635)  评论(9编辑  收藏  举报

导航