在.NET中使用USB设备

Working with USB devices in .NET

使用.Net(C#),如何使用USB设备?

如何检测USB事件(连接/断开连接)以及如何与设备通信(读/写)。

是否存在本机.Net解决方案?


我曾尝试使用SharpUSBLib,但它弄坏了我的计算机(需要系统还原)。也发生在同一项目的同事中。

我在LibUSBDotNet中找到了替代方法:http://sourceforge.net/projects/libusbdotnet
哈夫(Hav)并没有使用太多,但看起来不错,并且最近进行了更新(与夏普不同)。

编辑:截至2017年2月中,LibUSBDotNet大约在2周前进行了更新。同时,SharpUSBLib自2004年以来未进行更新。


没有针对此的本地(例如系统库)解决方案。这就是moobaa提到存在SharpUSBLib的原因。

如果您希望使用自己的USB设备处理程序,则可以检出System.IO.Ports的SerialPort类。


我使用以下代码来检测何时从计算机上插入和拔出USB设备:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class USBControl : IDisposable
    {
        // used for monitoring plugging and unplugging of USB devices.
        private ManagementEventWatcher watcherAttach;
        private ManagementEventWatcher watcherRemove;

        public USBControl()
        {
            // Add USB plugged event watching
            watcherAttach = new ManagementEventWatcher();
            //var queryAttach = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
            watcherAttach.EventArrived += new EventArrivedEventHandler(watcher_EventArrived);
            watcherAttach.Query = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
            watcherAttach.Start();

            // Add USB unplugged event watching
            watcherRemove = new ManagementEventWatcher();
            //var queryRemove = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
            watcherRemove.EventArrived += new EventArrivedEventHandler(watcher_EventRemoved);
            watcherRemove.Query = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
            watcherRemove.Start();
        }

        /// <summary>
        /// Used to dispose of the USB device watchers when the USBControl class is disposed of.
        /// </summary>
        public void Dispose()
        {
            watcherAttach.Stop();
            watcherRemove.Stop();
            //Thread.Sleep(1000);
            watcherAttach.Dispose();
            watcherRemove.Dispose();
            //Thread.Sleep(1000);
        }

        void watcher_EventArrived(object sender, EventArrivedEventArgs e)
        {
            Debug.WriteLine("watcher_EventArrived");
        }

        void watcher_EventRemoved(object sender, EventArrivedEventArgs e)
        {
            Debug.WriteLine("watcher_EventRemoved");
        }

        ~USBControl()
        {
            this.Dispose();
        }


    }

您必须确保在关闭应用程序时调用Dispose()方法。否则,关闭时将在运行时收到COM对象错误。


我建议我使用两年的LibUSBDotNet。
如果必须使用USB设备(发送请求,处理响应),则该库是我能找到的最佳解决方案。

优点:

  • 具有在同步或异步模式下工作所需的所有方法。
  • 提供的源代码
  • 足够的样本可以立即开始使用。

缺点:

  • 文档不佳(这是开源项目的常见问题)。基本上,您可以在CHM帮助文件中找到方法的通用说明,仅此而已。
    但是我仍然发现提供的示例和源代码足以进行编码。
    有时候我看到一种奇怪的行为,想知道为什么以这种方式实现它,甚至无法获得提示。
  • 似乎不再受支持。最新版本于2010年10月发布。有时很难获得答案。

USB设备通常分为两类:隐藏和USB。 USB设备可能是也可能不是Hid设备,反之亦然。与直接USB相比,隐藏通常更易于使用。不同的平台具有用于处理USB和Hid的不同API。

以下是UWP的文档:

USB:
https://docs.microsoft.com/zh-cn/windows-hardware/drivers/usbcon/how-to-connect-to-a-usb-device--uwp-app-

隐藏:
https://docs.microsoft.com/zh-cn/uwp/api/windows.devices.humaninterfacedevice

以下是Android的文档:
https://developer.xamarin.com/api/namespace/Android.Hardware.Usb/

这里有两个用于在原始Windows API级别处理USB / Hid的类:

https://github.com/MelbourneDeveloper/Device.Net/blob/master/src/Hid.Net/Windows/HidAPICalls.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
public static class HidAPICalls
{
    #region Constants
    private const int DigcfDeviceinterface = 16;
    private const int DigcfPresent = 2;
    private const uint FileShareRead = 1;
    private const uint FileShareWrite = 2;
    private const uint GenericRead = 2147483648;
    private const uint GenericWrite = 1073741824;
    private const uint OpenExisting = 3;
    private const int HIDP_STATUS_SUCCESS = 0x110000;
    private const int HIDP_STATUS_INVALID_PREPARSED_DATA = -0x3FEF0000;
    #endregion

    #region API Calls

    [DllImport("hid.dll", SetLastError = true)]
    private static extern bool HidD_GetPreparsedData(SafeFileHandle hidDeviceObject, out IntPtr pointerToPreparsedData);

    [DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
    private static extern bool HidD_GetManufacturerString(SafeFileHandle hidDeviceObject, IntPtr pointerToBuffer, uint bufferLength);

    [DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
    private static extern bool HidD_GetProductString(SafeFileHandle hidDeviceObject, IntPtr pointerToBuffer, uint bufferLength);

    [DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
    private static extern bool HidD_GetSerialNumberString(SafeFileHandle hidDeviceObject, IntPtr pointerToBuffer, uint bufferLength);

    [DllImport("hid.dll", SetLastError = true)]
    private static extern int HidP_GetCaps(IntPtr pointerToPreparsedData, out HidCollectionCapabilities hidCollectionCapabilities);

    [DllImport("hid.dll", SetLastError = true)]
    private static extern bool HidD_GetAttributes(SafeFileHandle hidDeviceObject, out HidAttributes attributes);

    [DllImport("hid.dll", SetLastError = true)]
    private static extern bool HidD_FreePreparsedData(ref IntPtr pointerToPreparsedData);

    [DllImport("hid.dll", SetLastError = true)]
    private static extern void HidD_GetHidGuid(ref Guid hidGuid);

    private delegate bool GetString(SafeFileHandle hidDeviceObject, IntPtr pointerToBuffer, uint bufferLength);

    #endregion

    #region Helper Methods

    #region Public Methods
    public static HidAttributes GetHidAttributes(SafeFileHandle safeFileHandle)
    {
        var isSuccess = HidD_GetAttributes(safeFileHandle, out var hidAttributes);
        WindowsDeviceBase.HandleError(isSuccess,"Could not get Hid Attributes");
        return hidAttributes;
    }

    public static HidCollectionCapabilities GetHidCapabilities(SafeFileHandle readSafeFileHandle)
    {
        var isSuccess = HidD_GetPreparsedData(readSafeFileHandle, out var pointerToPreParsedData);
        WindowsDeviceBase.HandleError(isSuccess,"Could not get pre parsed data");

        var result = HidP_GetCaps(pointerToPreParsedData, out var hidCollectionCapabilities);
        if (result != HIDP_STATUS_SUCCESS)
        {
            throw new Exception($"Could not get Hid capabilities. Return code: {result}");
        }

        isSuccess = HidD_FreePreparsedData(ref pointerToPreParsedData);
        WindowsDeviceBase.HandleError(isSuccess,"Could not release handle for getting Hid capabilities");

        return hidCollectionCapabilities;
    }

    public static string GetManufacturer(SafeFileHandle safeFileHandle)
    {
        return GetHidString(safeFileHandle, HidD_GetManufacturerString);
    }

    public static string GetProduct(SafeFileHandle safeFileHandle)
    {
        return GetHidString(safeFileHandle, HidD_GetProductString);
    }

    public static string GetSerialNumber(SafeFileHandle safeFileHandle)
    {
        return GetHidString(safeFileHandle, HidD_GetSerialNumberString);
    }
    #endregion

    #region Private Static Methods
    private static string GetHidString(SafeFileHandle safeFileHandle, GetString getString)
    {
        var pointerToBuffer = Marshal.AllocHGlobal(126);
        var isSuccess = getString(safeFileHandle, pointerToBuffer, 126);
        Marshal.FreeHGlobal(pointerToBuffer);
        WindowsDeviceBase.HandleError(isSuccess,"Could not get Hid string");
        return Marshal.PtrToStringUni(pointerToBuffer);    
    }
    #endregion

    #endregion

} ??

https://github.com/MelbourneDeveloper/Device.Net/blob/master/src/Usb.Net/Windows/WinUsbApiCalls.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public static partial class WinUsbApiCalls
{
    #region Constants
    public const int EnglishLanguageID = 1033;
    public const uint DEVICE_SPEED = 1;
    public const byte USB_ENDPOINT_DIRECTION_MASK = 0X80;
    public const int WritePipeId = 0x80;

    /// <summary>
    /// Not sure where this constant is defined...
    /// </summary>
    public const int DEFAULT_DESCRIPTOR_TYPE = 0x01;
    public const int USB_STRING_DESCRIPTOR_TYPE = 0x03;
    #endregion

    #region API Calls
    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_ControlTransfer(IntPtr InterfaceHandle, WINUSB_SETUP_PACKET SetupPacket, byte[] Buffer, uint BufferLength, ref uint LengthTransferred, IntPtr Overlapped);

    [DllImport("winusb.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern bool WinUsb_GetAssociatedInterface(SafeFileHandle InterfaceHandle, byte AssociatedInterfaceIndex, out SafeFileHandle AssociatedInterfaceHandle);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_GetDescriptor(SafeFileHandle InterfaceHandle, byte DescriptorType, byte Index, ushort LanguageID, out USB_DEVICE_DESCRIPTOR deviceDesc, uint BufferLength, out uint LengthTransfered);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_GetDescriptor(SafeFileHandle InterfaceHandle, byte DescriptorType, byte Index, UInt16 LanguageID, byte[] Buffer, UInt32 BufferLength, out UInt32 LengthTransfered);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_Free(SafeFileHandle InterfaceHandle);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_Initialize(SafeFileHandle DeviceHandle, out SafeFileHandle InterfaceHandle);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_QueryDeviceInformation(IntPtr InterfaceHandle, uint InformationType, ref uint BufferLength, ref byte Buffer);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_QueryInterfaceSettings(SafeFileHandle InterfaceHandle, byte AlternateInterfaceNumber, out USB_INTERFACE_DESCRIPTOR UsbAltInterfaceDescriptor);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_QueryPipe(SafeFileHandle InterfaceHandle, byte AlternateInterfaceNumber, byte PipeIndex, out WINUSB_PIPE_INFORMATION PipeInformation);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_ReadPipe(SafeFileHandle InterfaceHandle, byte PipeID, byte[] Buffer, uint BufferLength, out uint LengthTransferred, IntPtr Overlapped);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_SetPipePolicy(IntPtr InterfaceHandle, byte PipeID, uint PolicyType, uint ValueLength, ref uint Value);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_WritePipe(SafeFileHandle InterfaceHandle, byte PipeID, byte[] Buffer, uint BufferLength, out uint LengthTransferred, IntPtr Overlapped);
    #endregion

    #region Public Methods
    public static string GetDescriptor(SafeFileHandle defaultInterfaceHandle, byte index, string errorMessage)
    {
        var buffer = new byte[256];
        var isSuccess = WinUsb_GetDescriptor(defaultInterfaceHandle, USB_STRING_DESCRIPTOR_TYPE, index, EnglishLanguageID, buffer, (uint)buffer.Length, out var transfered);
        WindowsDeviceBase.HandleError(isSuccess, errorMessage);
        var descriptor = new string(Encoding.Unicode.GetChars(buffer, 2, (int)transfered));
        return descriptor.Substring(0, descriptor.Length - 1);
    }
    #endregion
}

使用这些解决方案中的任何一个,您要么需要按一定的时间间隔轮询设备,要么使用API??的本机设备侦听类之一。但是,该库在所有平台上的Hid和USB上都放置了一层,以便您可以轻松检测连接和断开连接:https://github.com/MelbourneDeveloper/Device.Net/wiki/Device-Listener。这是您将如何使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
internal class TrezorExample : IDisposable
{
    #region Fields
    //Define the types of devices to search for. This particular device can be connected to via USB, or Hid
    private readonly List<FilterDeviceDefinition> _DeviceDefinitions = new List<FilterDeviceDefinition>
    {
        new FilterDeviceDefinition{ DeviceType= DeviceType.Hid, VendorId= 0x534C, ProductId=0x0001, Label="Trezor One Firmware 1.6.x", UsagePage=65280 },
        new FilterDeviceDefinition{ DeviceType= DeviceType.Usb, VendorId= 0x534C, ProductId=0x0001, Label="Trezor One Firmware 1.6.x (Android Only)" },
        new FilterDeviceDefinition{ DeviceType= DeviceType.Usb, VendorId= 0x1209, ProductId=0x53C1, Label="Trezor One Firmware 1.7.x" },
        new FilterDeviceDefinition{ DeviceType= DeviceType.Usb, VendorId= 0x1209, ProductId=0x53C0, Label="Model T" }
    };
    #endregion

    #region Events
    public event EventHandler TrezorInitialized;
    public event EventHandler TrezorDisconnected;
    #endregion

    #region Public Properties
    public IDevice TrezorDevice { get; private set; }
    public DeviceListener DeviceListener { get; private set; }
    #endregion

    #region Event Handlers
    private void DevicePoller_DeviceInitialized(object sender, DeviceEventArgs e)
    {
        TrezorDevice = e.Device;
        TrezorInitialized?.Invoke(this, new EventArgs());
    }

    private void DevicePoller_DeviceDisconnected(object sender, DeviceEventArgs e)
    {
        TrezorDevice = null;
        TrezorDisconnected?.Invoke(this, new EventArgs());
    }
    #endregion

    #region Public Methods
    public void StartListening()
    {
        TrezorDevice?.Dispose();
        DeviceListener = new DeviceListener(_DeviceDefinitions, 3000);
        DeviceListener.DeviceDisconnected += DevicePoller_DeviceDisconnected;
        DeviceListener.DeviceInitialized += DevicePoller_DeviceInitialized;
    }

    public async Task InitializeTrezorAsync()
    {
        //Get the first available device and connect to it
        var devices = await DeviceManager.Current.GetDevices(_DeviceDefinitions);
        TrezorDevice = devices.FirstOrDefault();
        await TrezorDevice.InitializeAsync();
    }

    public async Task<byte[]> WriteAndReadFromDeviceAsync()
    {
        //Create a buffer with 3 bytes (initialize)
        var writeBuffer = new byte[64];
        writeBuffer[0] = 0x3f;
        writeBuffer[1] = 0x23;
        writeBuffer[2] = 0x23;

        //Write the data to the device
        return await TrezorDevice.WriteAndReadAsync(writeBuffer);
    }

    public void Dispose()
    {
        TrezorDevice?.Dispose();
    }
    #endregion
}

这里有一个有关如何使SharpUSBLib库和HID驱动程序与C#一起使用的教程:

http://www.developerfusion.com/article/84338/making-usb-c-friendly/


如果您的PC上装有National Instruments软件,则可以使用其" NI-VISA驱动程序向导"创建USB驱动程序。

创建USB驱动程序的步骤:http://www.ni.com/tutorial/4478/en/

创建驱动程序后,您将可以向任何USB设备写入和读取字节。

确保在设备管理器下的Windows中可以看到驱动程序:

enter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
    using NationalInstruments.VisaNS;

    #region UsbRaw
    /// <summary>
    /// Class to communicate with USB Devices using the UsbRaw Class of National Instruments
    /// </summary>
    public class UsbRaw
    {
        private NationalInstruments.VisaNS.UsbRaw usbRaw;
        private List<byte> DataReceived = new List<byte>();

        /// <summary>
        /// Initialize the USB Device to interact with
        /// </summary>
        /// <param name="ResourseName">In this format:"USB0::0x1448::0x8CA0::NI-VISA-30004::RAW".  Use the NI-VISA Driver Wizard from Start??All Programs??National Instruments??VISA??Driver Wizard to create the USB Driver for the device you need to talk to.</param>
        public UsbRaw(string ResourseName)
        {
            usbRaw = new NationalInstruments.VisaNS.UsbRaw(ResourseName, AccessModes.NoLock, 10000, false);
            usbRaw.UsbInterrupt += new UsbRawInterruptEventHandler(OnUSBInterrupt);
            usbRaw.EnableEvent(UsbRawEventType.UsbInterrupt, EventMechanism.Handler);
        }

        /// <summary>
        /// Clears a USB Device from any previous commands
        /// </summary>
        public void Clear()
        {
            usbRaw.Clear();
        }

        /// <summary>
        /// Writes Bytes to the USB Device
        /// </summary>
        /// <param name="EndPoint">USB Bulk Out Pipe attribute to send the data to.  For example: If you see on the Bus Hound sniffer tool that data is coming out from something like 28.4 (Device column), this means that the USB is using Endpoint 4 (Number after the dot)</param>
        /// <param name="BytesToSend">Data to send to the USB device</param>
        public void Write(short EndPoint, byte[] BytesToSend)
        {
            usbRaw.BulkOutPipe = EndPoint;
            usbRaw.Write(BytesToSend);       // Write to USB
        }

        /// <summary>
        /// Reads bytes from a USB Device
        /// </summary>
        /// <returns>Bytes Read</returns>
        public byte[] Read()
        {
            usbRaw.ReadByteArray();     // This fires the UsbRawInterruptEventHandler                

            byte[] rxBytes = DataReceived.ToArray();      // Collects the data received

            return rxBytes;
        }

        /// <summary>
        /// This is used to get the data received by the USB device
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnUSBInterrupt(object sender, UsbRawInterruptEventArgs e)
        {
            try
            {
                DataReceived.Clear();     // Clear previous data received
                DataReceived.AddRange(e.DataBuffer);                    
            }
            catch (Exception exp)
            {
                string errorMsg ="Error:" + exp.Message;
                DataReceived.AddRange(ASCIIEncoding.ASCII.GetBytes(errorMsg));
            }
        }

        /// <summary>
        /// Use this function to clean up the UsbRaw class
        /// </summary>
        public void Dispose()
        {
            usbRaw.DisableEvent(UsbRawEventType.UsbInterrupt, EventMechanism.Handler);

            if (usbRaw != null)
            {
                usbRaw.Dispose();
            }              
        }

    }
    #endregion UsbRaw

用法:

1
2
3
4
5
6
7
UsbRaw usbRaw = new UsbRaw("USB0::0x1448::0x8CA0::NI-VISA-30004::RAW");

byte[] sendData = new byte[] { 0x53, 0x4c, 0x56 };
usbRaw.Write(4, sendData);      // Write bytes to the USB Device
byte[] readData = usbRaw.Read();   // Read bytes from the USB Device

usbRaw.Dispose();

希望这对某人有帮助。


有一个通用工具包WinDriver,用于在用户模式下编写支持#.NET的USB驱动程序。


我很幸运地尝试了其中一些建议。我最终使用Java和hid4java库编写了一个可行的解决方案。作为控制台应用程序,我可以使用Process.Start()从C#中将其外壳化,并传递参数以及读取响应。这提供了基本的HID I / O,但没有连接/断开事件。为此,我需要将其重写以作为守护程序/服务运行,并使用命名管道或其他服务器/客户端传输。到目前为止,自hi4java库"正常运行"以来,足以完成工作。


使用本文,我已经获得了Teensy的界面,效果很好


大多数USB芯片组都附带驱动程序。 Silicon Labs有一个。


推荐阅读

    远程命令连接linux?

    远程命令连接linux?,系统,密码,名称,图片,网络,软件,百度,地址,服务,电脑,Lin

    连接linux桌面命令?

    连接linux桌面命令?,系统,软件,工作,密码,电脑,信息,工具,网站,地址,名称,lin

    linux命令解决方案?

    linux命令解决方案?,系统,管理,数据,电子,工作,电脑,软件,情况,不了,档案,lin

    linux访问连接命令?

    linux访问连接命令?,系统,地址,网络,密码,服务,软件,位置,对外,处分,命令,怎

    linux命令行拨号连接?

    linux命令行拨号连接?,系统,网络,软件,手机,服务,密码,地址,名称,电话号码,

    linux命令查看连接数?

    linux命令查看连接数?,数字,对比,网络,系统,数据,地址,状态,通讯,信息,命令,l

    linux命令连接光驱?

    linux命令连接光驱?,系统,位置,设备,数据,电脑,服务,资料,盘中,智能,管理,Lin

    linux跳板机连接命令?

    linux跳板机连接命令?,地址,服务,密码,工具,中国,网络,位置,系统,电脑,在线,

    linux命令查看连接数?

    linux命令查看连接数?,数字,对比,网络,系统,数据,地址,状态,通讯,信息,命令,l

    linux命令行拨号连接?

    linux命令行拨号连接?,系统,网络,软件,手机,服务,密码,地址,名称,电话号码,

    linux命令连接光驱?

    linux命令连接光驱?,系统,位置,设备,数据,电脑,服务,资料,盘中,智能,管理,Lin

    linux命令逻辑连接符?

    linux命令逻辑连接符?,系统,网络,名字,环境,信息,名称,设备,发行,位置,较大,L

    linux跳板机连接命令?

    linux跳板机连接命令?,地址,服务,密码,工具,中国,网络,位置,系统,电脑,在线,

    linux连接外网命令?

    linux连接外网命令?,网络,系统,工具,情况,软件,信息,地址,代理,地方,数据,请

    linux命令连接ip?

    linux命令连接ip?,地址,系统,网络,工作,信息,命令,密码,名称,设备,服务,linux

    linux断开线程命令?

    linux断开线程命令?,系统,状态,工作,代码,线程,入口,网络,管理,名称,命令,lin

    linux命令连接网址?

    linux命令连接网址?,网址,系统,地址,服务,传播,数据,命令,名字,环境,网站,如

    linux命令创建项目组?

    linux命令创建项目组?,管理,密码,项目,命令,系统,位置,文件,用户组,用户,文

    linux连接多条命令?

    linux连接多条命令?,工具,情况,命令,分行,服务,地址,连续,终端,窗口,主机,lin

    linux有线网连接命令?

    linux有线网连接命令?,系统,网络,软件,电脑,密码,地址,信息,虚拟机,终端,命