近期在和同学玩死神vs火影,以怀念小时候,突然觉得用键盘玩的不够畅快,因此萌生了写一个虚拟手柄的念头。

我的思路是在移动设备(iOS、Android)上实现手柄,在电脑上监听,利用socket建立持久连接,通过移动设备向电脑上的监听软件发送操作码,通过操作码来处理事件。

有关socket的服务端,建立在一个服务器上,让移动设备和电脑分别连接,建立信道,在服务器上使用python建立socket客户端与在移动设备上使用socket十分便利,这里不讲述,本文的重点是实现电脑上根据键值实现的按键事件,包括组合键的处理。

我们假设虚拟手柄有4+6个键,分别是上下左右,1-6功能键,发送的操作码码分别为0~9,当所有按键松开,发送的操作码为-1。

为了实现按键操作,需要借助USER32.DLL的keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo)函数,第一个参数是键码,第二个和第四个填0即可,第三个代表是按下按键还是松开,0表示按下,2表示松开。

由于C#无法直接调用那些宏,因此键码输入数字来实现,键码对应表如下:

虚拟键码   对应值  对应键
VK_LBUTTON 1 鼠标左键
VK_RBUTTON 2 鼠标右键
VK_CANCEL 3 Cancel
VK_MBUTTON 4 鼠标中键
VK_XBUTTON1 5
VK_XBUTTON2 6
VK_BACK 8 Backspace
VK_TAB 9 Tab
VK_CLEAR 12 Clear
VK_RETURN 13 Enter
VK_SHIFT 16 Shift
VK_CONTROL 17 Ctrl
VK_MENU 18 Alt
VK_PAUSE 19 Pause
VK_CAPITAL 20 Caps Lock
VK_KANA 21
VK_HANGUL 21
VK_JUNJA 23
VK_FINAL 24
VK_HANJA 25
VK_KANJI 25*
VK_ESCAPE 27 Esc
VK_CONVERT 28
VK_NONCONVERT 29
VK_ACCEPT 30
VK_MODECHANGE 31
VK_SPACE 32 Space
VK_PRIOR 33 Page Up
VK_NEXT 34 Page Down
VK_END 35 End
VK_HOME 36 Home
VK_LEFT 37 Left Arrow
VK_UP 38 Up Arrow
VK_RIGHT 39 Right Arrow
VK_DOWN 40 Down Arrow
VK_SELECT 41 Select
VK_PRINT 42 Print
VK_EXECUTE 43 Execute
VK_SNAPSHOT 44 Snapshot
VK_INSERT 45 Insert
VK_DELETE 46 Delete
VK_HELP 47 Help
48 0
49 1
50 2
51 3
52 4
53 5
54 6
55 7
56 8
57 9
65 A
66 B
67 C
68 D
69 E
70 F
71 G
72 H
73 I
74 J
75 K
76 L
77 M
78 N
79 O
80 P
81 Q
82 R
83 S
84 T
85 U
86 V
87 W
88 X
89 Y
90 Z
VK_LWIN 91
VK_RWIN 92
VK_APPS 93
VK_SLEEP 95
VK_NUMPAD0 96 小键盘 0
VK_NUMPAD1 97 小键盘 1
VK_NUMPAD2 98 小键盘 2
VK_NUMPAD3 99 小键盘 3
VK_NUMPAD4 100 小键盘 4
VK_NUMPAD5 101 小键盘 5
VK_NUMPAD6 102 小键盘 6
VK_NUMPAD7 103 小键盘 7
VK_NUMPAD8 104 小键盘 8
VK_NUMPAD9 105 小键盘 9
VK_MULTIPLY 106 小键盘 *
VK_ADD 107 小键盘 +
VK_SEPARATOR 108 小键盘 Enter
VK_SUBTRACT 109 小键盘 -
VK_DECIMAL 110 小键盘 .
VK_DIVIDE 111 小键盘 /
VK_F1 112 F1
VK_F2 113 F2
VK_F3 114 F3
VK_F4 115 F4
VK_F5 116 F5
VK_F6 117 F6
VK_F7 118 F7
VK_F8 119 F8
VK_F9 120 F9
VK_F10 121 F10
VK_F11 122 F11
VK_F12 123 F12
VK_F13 124
VK_F14 125
VK_F15 126
VK_F16 127
VK_F17 128
VK_F18 129
VK_F19 130
VK_F20 131
VK_F21 132
VK_F22 133
VK_F23 134
VK_F24 135
VK_NUMLOCK 144 Num Lock
VK_SCROLL 145 Scroll
VK_LSHIFT 160
VK_RSHIFT 161
VK_LCONTROL 162
VK_RCONTROL 163
VK_LMENU 164
VK_RMENU 165
VK_BROWSER_BACK 166
VK_BROWSER_FORWARD 167
VK_BROWSER_REFRESH 168
VK_BROWSER_STOP 169
VK_BROWSER_SEARCH 170
VK_BROWSER_FAVORITES 171
VK_BROWSER_HOME 172
VK_VOLUME_MUTE 173 VolumeMute
VK_VOLUME_DOWN 174 VolumeDown
VK_VOLUME_UP 175 VolumeUp
VK_MEDIA_NEXT_TRACK 176
VK_MEDIA_PREV_TRACK 177
VK_MEDIA_STOP 178
VK_MEDIA_PLAY_PAUSE 179
VK_LAUNCH_MAIL 180
VK_LAUNCH_MEDIA_SELECT 181
VK_LAUNCH_APP1 182
VK_LAUNCH_APP2 183
VK_OEM_1 186 ; :
VK_OEM_PLUS 187 = +
VK_OEM_COMMA 188
VK_OEM_MINUS 189 - _
VK_OEM_PERIOD 190
VK_OEM_2 191 / ?
VK_OEM_3 192 ` ~
VK_OEM_4 219 [ {
VK_OEM_5 220 \ |
VK_OEM_6 221 ] }
VK_OEM_7 222 ' "
VK_OEM_8 223
VK_OEM_102 226
VK_PACKET 231
VK_PROCESSKEY 229
VK_ATTN 246
VK_CRSEL 247
VK_EXSEL 248
VK_EREOF 249
VK_PLAY 250
VK_ZOOM 251
VK_NONAME 252
VK_PA1 253
VK_OEM_CLEAR 254

为了实现组合键,对于每一个要处理的按键,都应该调用函数实现该键的按下,并且注意已经按下的键不能重复按下,当所有键松开始,要清空所有键的按下情况,为了实现这个目的,使用动态数组ArrayList来记录已经按下的键。

要使用ArrayList,要引用:

using System.Collections;

ArrayList主要的方法是Contains判断元素是否在数组内,Clear删除所有元素,Add添加元素。

我们在每个键按下时先判断ArrayList是否包含该元素,不包含则调用函数让该键按下,并且把键码添加到数组内,否则不动作。

当按键全部释放时,应当遍历ArrayList数组,让所有按下的键释放,然后清空ArrayList。

通过这样的逻辑,我们就可以实现任何按键事件了。

下面具体讲解各个模块的实现方法:

【按键的按下与释放】

因为要引入DLL,因此添加引用:

using System.Runtime.InteropServices;

然后引用一个DLL来处理键盘:

[DllImport("USER32.DLL")]
public static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);</span>

这个函数就是上面介绍过的按键处理函数,直接调用即可模拟键盘操作,为了实现上面的业务逻辑,在按键按下时先判断是否已经被按下,然后处理,封装一个函数处理按键:

其中al是一个动态数组,定义如下:

static ArrayList al = new ArrayList(0);
static void pressKey(byte keycode)
{
if (!al.Contains(keycode))
{
al.Add(keycode);
keybd_event(keycode, 0, 0, 0);
}
}

这样就实现了按键的按下,并且避免了重复按下。

当按键全部释放时,要释放所有已经按下的按键,并且清空al:

foreach (byte key in al)
{
keybd_event(key, 0, 2, 0);
}
al.Clear();

【socket的实现】

首先引用:

using System.Net;
using System.Net.Sockets;

然后使用一个函数实现socket的监听

static void runSocket()
{
//设定服务器IP地址
IPAddress ip = IPAddress.Parse("<ip地址>");
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
clientSocket.Connect(new IPEndPoint(ip, <端口号>)); //配置服务器IP与端口
Console.WriteLine("连接服务器成功");
}
catch
{
Console.WriteLine("连接服务器失败,请按回车键退出!");
return;
} //通过clientSocket接收数据
while (true)
{
int receiveLength = clientSocket.Receive(result);
string str = Encoding.ASCII.GetString(result, 0, receiveLength);
// Console.WriteLine("<" + str + ">");
int option = -1;
try
{
option = int.Parse(str);
}
catch
{ }
// Console.WriteLine("option = " + option);
state = option; }
}

之所以定义一个函数,是为了在子线程中监听socket,socket收到的操作码进行解析,解析成功后则赋值为state,state就是主线程要处理的操作码,代表着按键的按下。

开启socket线程的代码,写在main函数中:

Thread t = new Thread(runSocket);
t.Start();

接下来的部分就是针对不同的按键进行处理了,下面贴出完整的源码,这是一个C#控制台程序:

为了安全,我把自己的ip和port都去掉了,如果要使用这个源码,需要注意以下事项:

①socket服务端能够根据手柄的动作发送字符0~9、-1。

②电脑端的程序保持开启状态,调试与运行状态皆可。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Runtime.InteropServices;
using System.Collections; namespace SocketClient
{ class Program
{ static int state = 0;
static ArrayList al = new ArrayList(0); private static byte[] result = new byte[1024]; [DllImport("USER32.DLL")]
public static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo); static void pressKey(byte keycode)
{
if (!al.Contains(keycode))
{
al.Add(keycode);
keybd_event(keycode, 0, 0, 0);
}
} static void Main(string[] args)
{
Thread t = new Thread(runSocket);
t.Start();
al.Clear();
state = -1;
while (true)
{
if (state != -1) { switch (state)
{
case 0: // W = 87
pressKey(87);
break;
case 1: // S = 83
pressKey(83);
break;
case 2: // A = 65
pressKey(65);
break;
case 3: // D = 68
pressKey(68);
break;
case 4: // J = 74
pressKey(74);
break;
case 5: // K = 75
pressKey(75);
break;
case 6: // L = 76
pressKey(76);
break;
case 7: // U = 85
pressKey(85);
break;
case 8: // I = 73
pressKey(73);
break;
case 9: // O = 79
pressKey(79);
break;
}
}
else
{
foreach (byte key in al)
{
keybd_event(key, 0, 2, 0);
}
al.Clear();
}
}
} static void runSocket()
{
//设定服务器IP地址
IPAddress ip = IPAddress.Parse("42.96.168.162");
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
clientSocket.Connect(new IPEndPoint(ip, 12345)); //配置服务器IP与端口
Console.WriteLine("连接服务器成功");
}
catch
{
Console.WriteLine("连接服务器失败,请按回车键退出!");
return;
} //通过clientSocket接收数据
while (true)
{
int receiveLength = clientSocket.Receive(result);
string str = Encoding.ASCII.GetString(result, 0, receiveLength);
// Console.WriteLine("<" + str + ">");
int option = -1;
try
{
option = int.Parse(str);
}
catch
{ }
// Console.WriteLine("option = " + option);
state = option; } } }
}

查了无数资料才实现了这么几个功能,实属不易,希望对需要的各位有所帮助。

使用C#+socket实现用移动设备控制的虚拟手柄的更多相关文章

  1. Linux+postfix+extmail+dovecot打造基于web页面的邮件系统

    原文地址:http://blog.csdn.net/deansrk/article/details/6717720 最终效果图: 准备阶段:需要手动下载的软件包: postfix-2.6.5.tar. ...

  2. webform处理过程

    一.post/get传值注意几点 post提交的时候,只有写了name属性且没有写disable=true表单元素(input,select,textarea)才会被提交. 如果不确定是get还是po ...

  3. Java 网络编程---分布式文件协同编辑器设计与实现

    目录: 第一部分:Java网络编程知识 (一)简单的Http请求 一般浏览网页时,使用的时Ip地址,而IP(Internet Protocol,互联网协议)目前主要是IPv4和IPv6. IP地址是一 ...

  4. DPDK support for vhost-user

    转载:http://blog.csdn.net/quqi99/article/details/47321023 X86体系早期没有在硬件设计上对虚拟化提供支持,因此虚拟化完全通过软件实现.一个典型的做 ...

  5. TCP网络编程

    TCP网络编程  与UDP不同的是TCP是通过客服端和服务端的方式来传输数据的.客服端:public class TCPClient { /**     * @param args     * @th ...

  6. 关于UDP-读这篇就够了(疑难杂症和使用)

    本文为转载文章 原文链接:https://www.qcloud.com/community/article/848077001486437077 版权归原文所有 关于UDP 面向报文的传输方式决定了U ...

  7. rsync的配置文件模板及简单介绍,命令及参数

    必须知道推送有一个限速参数.--bwlimit=100 工作总必须要加.有三种模式,1.本地的模拟cp命令,在一个服务器2.远程的两个服务器之间,模拟scp3.以socket进程监听的方式启动rsyn ...

  8. Linux性能优化从入门到实战:09 内存篇:Buffer和Cache

      Buffer 是缓冲区,而 Cache 是缓存,两者都是数据在内存中的临时存储.   避免跟文中的"缓存"一词混淆,而文中的"缓存",则通指内存中的临时存储 ...

  9. 用一个文件,实现迷你 Web 框架

    当下网络就如同空气一样在我们的周围,它以无数种方式改变着我们的生活,但要说网络的核心技术变化甚微. 随着开源文化的蓬勃发展,诞生了诸多优秀的开源 Web 框架,让我们的开发变得轻松.但同时也让我们不敢 ...

随机推荐

  1. 深入以太坊智能合约 ABI

    开发 DApp 时要调用在区块链上的以太坊智能合约,就需要智能合约的 ABI.本文希望更多了解 ABI,如为什么需要 ABI?如何解读 Ethereum 的智能合约 ABI?以及如何取得合约的 ABI ...

  2. 用js来实现那些数据结构12(散列表)

    上一篇写了如何实现简单的Map结构,因为东西太少了不让上首页.好吧... 这一篇文章说一下散列表hashMap的实现.那么为什么要使用hashMap?hashMap又有什么优势呢?hashMap是如何 ...

  3. 开源Spring解决方案--lm.solution

    Github 项目地址: https://github.com/liumeng0403/lm.solution 一.说明 1.本项目未按java项目传统命名方式命名项目名,包名 如:org.xxxx. ...

  4. 如何joomla修改版权信息

    1.在language\zh_CN目录下有一个zh-CN.mod_footer.ini文件,修改里面的内容: 2.具体模板的html\mod_footer目录下的default.php文件内(具体文件 ...

  5. Python笔记十一(迭代器)

    这里我们要学会Iterable和Iterator. 一类是集合数据类型,如list.tuple.dict.set.str等: 一类是generator,包括生成器和带yield的generator f ...

  6. RDO Stack: No valid host was found. There are not enough hosts available.

    Issue: When you launch an instance in Newton, you may find that the instance cannot be started due t ...

  7. Switch控件详解

    Switch控件详解 原生效果 5.x 4.x 布局 <Switch android:id="@+id/setting_switch" android:layout_widt ...

  8. RxJava操作符(03-变换操作)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51649975 本文出自:[openXu的博客] 目录: Buffer FlatMap fla ...

  9. Struts 2 之 OGNL

    OGNL概述 Object-Graph Navigation Language,对象图导航语言 1.能够访问对象的方法,如list.size() 2.能够访问静态属性与静态方法,需要在类名前加上@,如 ...

  10. UNIX网络编程——揭开网络编程常见API的面纱【下】

    Linux网络编程数据收发的API流程分析        只要把数据在协议栈中的流动线路和脉络弄清楚了,关于协议栈的实现部分,理解起来就轻松多了.在网络编程章节的数据接收过程中,我们主要介绍过read ...