zoukankan      html  css  js  c++  java
  • 使用C#+socket实现用移动设备控制的虚拟手柄

    近期在和同学玩死神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;
    
                }
    
            }
    
        }
    }
    

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






  • 相关阅读:
    .net测试篇之Moq行为配置
    .net测试篇之Moq框架简单使用
    .net测试篇之测试神器Autofixture在几个复杂场景下的使用示例以及与Moq结合
    .net测试篇之测试神器Autofixture Generator使用与自定义builder
    .net测试篇之测试神器Autofixture基本配置一
    .net测试篇之单元测试/集成测试神器Autofixture
    .netcore持续集成测试篇之web项目验收测试
    .netcore持续集成测试篇之 .net core 2.1项目集成测试
    .netcore持续集成测试篇之MVC层单元测试
    .netcore持续集成测试篇之测试方法改造
  • 原文地址:https://www.cnblogs.com/aiwz/p/6154139.html
Copyright © 2011-2022 走看看