您的位置:首页 > 编程语言 > C#

使用C#+socket实现用移动设备控制的虚拟手柄

2017-05-09 11:52 465 查看
最近在和同学玩死神vs火影。以怀念小时候,突然认为用键盘玩的不够畅快,因此萌生了写一个虚拟手柄的念头。

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

有关socket的服务端,建立在一个server上,让移动设备和电脑分别连接,建立信道,在server上使用python建立socketclient与在移动设备上使用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()
{
//设定serverIP地址
IPAddress ip = IPAddress.Parse("<ip地址>");
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
clientSocket.Connect(new IPEndPoint(ip, <port号>)); //配置serverIP与port
Console.WriteLine("连接server成功");
}
catch
{
Console.WriteLine("连接server失败,请按回车键退出!

");
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()
{
//设定serverIP地址
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)); //配置serverIP与端口
Console.WriteLine("连接server成功");
}
catch
{
Console.WriteLine("连接server失败,请按回车键退出!");
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;

}

}

}
}


查了无数资料才实现了这么几个功能。实属不易,希望对须要的各位有所帮助。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: