远程鼠标控制-服务端
以前不懂网络编程的时候,感觉远程桌面是一个好神奇的技术,很神秘,现在了解这方面的知识后才觉得也没什么,前几天,用C#写了一个远程鼠标控制的东东,给感兴趣的朋友参考一下。转贴请注明出处。
我先引用一篇别人的文章:
Socket编程基础
本章以Berkeley Socket为主,主要介绍网络编程时常用的调用和程序使用它们的方法及基本结构。网络编程有两种主要的编程接口,一种是Berkeley UNIX(BSD UNIX)的socket编程接口,另一种是AT&T的TLI接口(用于UNIXSYSV)。
1 、TCP/IP 基础知识
这里先假定读者对ISO的OSI七层模型已有了一定的了解,下面我们来看看TCP/IP模型。ISO的OSI对服务、接口和协议的概念区别十分明了,但它却没有真正的用户群。TCP/IP模型对服务、接口和协议的概念区别不象OSI模型那样明晰,但很实用。TCP/IP模型分为四层,对应于OSI七层模型如下图所示:图6-1 TCP/IP参考模型与OSI模型的近似对应关系在TCP/IP模型中,互联网层是基于无连接互联网络层的分组交换网络。在这一层中主机可以把报文(Packet)发往任何网络,报文独立地传向目标。互联网层定义了报文的格式和协议,这就是IP协议族(Internet Protocol)。互联网层的功能是将报文发送到目的地,主要的设计问题是报文路由和避免阻塞。互联网层上面是传输层,该层的主要功能和OSI模型的该层一样,主要使源和目的主机之间可以进行会话。该层定义了两个端到端的协议,一个是面向连接的传输控制协议TCP,另一个是无连接的用户数据报协议UDP。TCP/IP协议模型中没有会话层和表示层。传输层之上是应用层,它包含所有的高层协议,如远程虚拟终端协议TELNET、文件传输协议FTP、简单邮件传输协议SMTP等。这些高层协议中常见的如TELNET协议,用来允许用户远程登录到另一台UNIX机器;FTP协议用来传输文件,常见的有WU-FTP(Washington University的FTP服务器端程序,是一个免费程序);SMTP协议用来传送email,常见的服务器端程序有netscape等公司制作的程序,也有免费使用的sendmail程序;还有域名系统服务DNS协议,新闻组传送协议NNTP,用于WWW的超文本传输协议HTTP等。主机到网络这一层,在TCP/IP模型中没有详细定义,这里不作介绍。
2、 Socket一般描述
由于越来越多的计算机厂商,特别是工作站制造商如Sun等公司采用了Berkeley UNIX,socket接口被广泛采用,以至于现在,socket接口被广泛认可并成为了事实上的工业标准。目前的SYSV、BSD、OSF都将socket接口作为系统的一部分。当时设计如何支持TCP/IP协议时,有两种加入函数的方法,一种是直接加入支持TCP/IP协议的调用,另一种是加入支持一般网络协议的函数,而用参数来指定支持TCP/IP协议。Berkeley采用了后者,这样可以支持多协议族,TCP/IP是协议族之一(PF_INET)。
2.1 socket 描述符
前面已经提到过,在UNIX中,进程要对文件进行操作,一般使用open调用打开一个文件进行访问,每个进程都有一个文件描述符表,该表中存放打开的文件描述符。用户使用open等调用得到的文件描述符其实是文件描述符在该表中的索引号,该表项的内容是一个指向文件表的指针。应用程序只要使用该描述符就可以对指定文件进行操作。同样,socket接口增加了网络通信操作的抽象定义,与文件操作一样,每个打开的socket都对应一个整数,我们称它为socket描述符,该整数也是socket描述符在文件描述符表中的索引值。但socket描述符在描述符表中的表项并不指向文件表,而是指向一个与该socket有关的数据结构。BSD UNIX中新增加了一个socket调用,应用程序可以调用它来新建一个socket描述符,注意进程用open只能产生文件描述符,而不能产生socket描述符。socket调用只能完成建立通信的部分工作,一旦建立了一个socket,应用程序可以使用其他特定的调用来为它添加其他详细信息,以完成建立通信的过程。
2.2 从概念上理解socket的使用网络编程中最常见的是客户/服务器模式。
以该模式编程时,服务端有一个进程(或多个进程)在指定的端口等待客户来连接,服务程序等待客户的连接信息,一旦连接上之后,就可以按设计的数据交换方法和格式进行数据传输。客户端在需要的时刻发出向服务端的连接请求。
这里为了便于理解,提到了这些调用及其大致的功能。使用socket调用后,仅产生了一个可以使用的socket描述符,这时还不能进行通信,还要使用其他的调用,以使得socket所指的结构中使用的信息被填写完。在使用TCP协议时,一般服务端进程先使用socket调用得到一个描述符,然后使用bind调用将一个名字与socket描述符连接起来,对于Internet域就是将Internet地址联编到socket。之后,服务端使用listen调用指出等待服务请求队列的长度。然后就可以使用accept调用等待客户端发起连接(一般是阻塞等待连接,后面章节会讲到非阻塞的方式),一旦有客户端发出连接,accept返回客户的地址信息,并返回一个新的socket描述符,该描述符与原先的socket有相同的特性,这时服务端就可以使用这个新的socket进行读写操作了。一般服务端可能在accept返回后创建一个新的进程进行与客户的通信,父进程则再到accept调用处等待另一个连接。客户端进程一般先使用socket调用得到一个socket描述符,然后使用connect向指定的服务器上的指定端口发起连接,一旦连接成功返回,就说明已经建立了与服务器的连接,这时就可以通过socket描述符进行读写操作了。下面是在客户和服务端使用TCP时,客户进程和服务进程使用系统调用的该程。
使用TCP的客户和服务端使用系统调用的图示使用无连接的UDP协议时,服务端进程创建一个socket,之后调用recvfrom接收客户端的数据报,然后调用sendto将要返回客户端的消息发送给客户进程。客户端也要先创建一个socket,再使用sendto向服务端进程发出请求,使用recvfrom得到返回的消息。
相信大家对socket编程也有一定了解,如果不是很明白可以参阅其他资料,也可以E-mail我。
鼠标控制,如何控制?我是这样实现的,在客户端中,我把当前鼠标的坐标通过API,GetCursorPos获取,并保存到一个叫MousePosition结构中,该结构很简单,只要2个字段,一个是x坐标,一个是y坐标,转换为2进制数组byte[],然后发送到服务端,其中当然要指定客户端IP,这里用的是10.0.0.9,我寝室局域网用的A类网。然后服务端接受到以后,在解析2进制数组byte[],把坐标保存,用API, SetCursorPos设置当前坐标。就那么简单,这里没有模拟鼠标按键,如果需要可以通过mouse_event 来实现,具体用法请参见ms-help://MS.MSDNQTR.2003FEB.2052/winui/winui/windowsuserinterface/userinput/mouseinput/mouseinputreference/mouseinputfunctions/mouse_event.htm。
或E-mail我。
现在我们来看看客户端的代码。
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.IO;
using System.Threading;
namespace 鼠标控制_客户端
{
//我们定义一个保存鼠标坐标的结构
public struct MousePosition
{
private int x;
private int y;
public int X
{
get
{
return x;
}
}
public int Y
{
get
{
return y;
}
}
}
class Class1
{
//声明GetCursorPos函数,该函数只有一个参数,这是一个out类型的参数,获取的坐标就保存//在该输出参数里面,成功返回真,反正为假。
[System.Runtime.InteropServices.DllImport( "user32.dll" , EntryPoint="GetCursorPos" )]
public extern static bool GetCursorPos( out MousePosition lpPoint );
[STAThread]
static void Main(string[] args)
{
byte[] data = new byte[100]; //初始化一个byte数组,用来传递信息
string strData;
MousePosition mp;
// IPEndPoint为一个指定IP以及端口的类,构造函数中要求一个IPAddress类型的IP,我//们用IPAddress.Parse把一个string类型的转换为IPAddress,第2个参数为端口号,这里//用的是60000,当然只要不和当前已用端口冲突,随便指定什么都可以,需要说明的是,//如果用TCP协议,客户端与服务端使用的端口必须同意,而用UDP就没关系。
//10.0.0.9是我用的,这个根据自己的网络情况改
IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("10.0.0.9"),60000);
// 构造一个套节字,第一个参数指定为 InterNetwork及为IP版本4的地址。第二个参数为
// 套接字类型,Stream为支持可靠、双向、基于连接的字节流,而不重复数据,无边界,若
// ProtocolType参数为tcp,需指定套接字类型为stream。具体使用请参见
//ms-help://MS.MSDNQTR.2003FEB.2052/cpref/html/frlrfsystemnetsocketssockettypeclasstopic.htm
Socket client = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
try
{
//连接到指定的主机既服务端,参数ipep指定的IP为10.0.0.9,端口为60000的IPEndPoint
client.Connect(ipep);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadLine();
return;
}
//声明一个数据流,把他绑定到刚才的连接,这个用法比Send更方便,在将NetworkStream
//绑定到一个StreamWriter上,这样把数据写到数据流中就仅需要调用Write或WriteLine
//方法,而读数据则调用Read或ReadLine方法,比起用send和Receive方法比要简单得多。
//下面的注释项给出了不用数据流的方法,需要用到我们刚才声明的byte数组。
NetworkStream ns = new NetworkStream(client);
StreamWriter sw = new StreamWriter(ns);
//在循环中不断的向服务端发送鼠标坐标的信息
while(true)
{
GetCursorPos(out mp);
//我们将鼠标的坐标保存到一个string中,并用‘,’号分割。
strData = mp.X.ToString() + "," + mp.Y.ToString();
// Socket实例的Send方法只接受byte的参数,所以需要把strData转换为用ASCII编码
//的byte[],这里用的ASCII编码,服务端也要用ASCII解码,如果传递中文信息,比如
//聊天工具,则需要用utf-8或其他的。
// data = Encoding.ASCII.GetBytes(strData);
// client.Send(data);
sw.WriteLine(strData);//将信息写入数据流,发到服务端
sw.Flush();
Thread.Sleep(10);
}
//释放
sw.Close();
ns.Close();
client.Shutdown(SocketShutdown.Both);
client.Close();
}
}
}
这就是客户端的全部实现,使用 数据流还是用Socket实例的Send方法,可以又具体情况而定,不过大家一定都看出,数据流的方法简单一些。
下面来看一下服务端是怎么接受到客户端发送来的信息,并重置鼠标坐标的:
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
namespace 鼠标控制_服务端
{
class Class1
{
//这个API 用于指定鼠标坐标,一看就明白
[System.Runtime.InteropServices.DllImport("user32.dll" , EntryPoint="SetCursorPos")]
public extern static bool SetCursorPos ( int x , int y ) ;
[STAThread]
static void Main(string[] args)
{
//和前面一样
byte[] data = new byte[100];
string strData;
//这个地方我们没有指定具体的ip,IPAddress.Any的意思是指示服务器应侦听所有网络
//接口上的客户端活动。
IPEndPoint ipep = new IPEndPoint(IPAddress.Any,60000);
Socket server = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
//将套接字绑定到指定IPEndPoint,并开始监听60000端口
server.Bind(ipep);
server.Listen(10);
Console.WriteLine("等待连接");
Socket control = server.Accept();
// Accept方法为以同步方式从侦听套接字的连接请求队列中提取第一个挂起的连接请求,
//然后创建并返回新的 Socket。不能使用返回的这个 Socket 接受连接队列中的任何附加连//接。然而,可以调用返回的 Socket 的 RemoteEndPoint 法来标识远程主机的网络地址//和端口号。在阻塞模式中,Accept 将会持续处于阻塞状态,直到传入的连接尝试进入队//列。在接受//连接之后,原来的Socket 将会继续把传入的连接请求放入队列,直到将其关//闭。这是msdn上的解释,可能看起来有点模糊,意思可以简单理解为,若有连接接入则//继续向下执行,若没有,则挂起等待,直到有连接接入。
Console.WriteLine("连接到");
NetworkStream ns = new NetworkStream(control);
StreamReader sr = new StreamReader(ns);
while(true)
{
try
{
//从数据流中读出数据,既读取客户端发来的信息
strData = sr.ReadLine();
}
catch(Exception)
{
return;
}
//以下的注释用Receive方法来读出数据而非数据流,该方法返回一个int数据,极为数据的长度,如果不
//为0,则用ASCII解码到string中,刚才我们用‘,’号分割x坐标和y坐标,现在我们在把他分开,保存
//到2个int中
// int recv = control.Receive(data);
// if(recv == 0)
// break;
// strData = Encoding.ASCII.GetString(data,0,recv);
int index = strData.IndexOf(',');
string strx = strData.Substring(0,index);
string stry = strData.Substring(index + 1);
int x = Convert.ToInt32(strx);
int y = Convert.ToInt32(stry);
//用这个api可以设置鼠标的坐标,而这个坐标是与客户端同步的
SetCursorPos(x,y);
}
sr.Close();
ns.Close();
control.Close();
server.Close();
}
}
}
我们用一个很简单的方法实现了鼠标的远程控制,这篇文章没有并没有讲太多的技术上的问题,不过通过这个思路可以实现很多有趣的东西。