1. 程式人生 > >使用C#+socket實現用移動設備控制的虛擬手柄

使用C#+socket實現用移動設備控制的虛擬手柄

sock endpoint network 回車 byte arraylist program 行處理 favor

最近在和同學玩死神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;

            }

        }

    }
}

查了無數資料才實現了這麽幾個功能。實屬不易,希望對須要的各位有所幫助。






使用C#+socket實現用移動設備控制的虛擬手柄