基于usb4java实现的java下的usb通信

转https://www.cnblogs.com/sowhat4999/p/4572720.html

项目地址:点击打开

使用java开发的好处就是跨平台,基本上java的开发的程序在linux、mac、MS上都可以运行,对应这java的那句经典名言:一次编写,到处运行。这个项目里面有两种包选择,一个是low-level(libus)一个是high-level(javax-usb),相关的优缺点在官方网站上已经说明了,我这里就不翻译了,不过前者好像基于libusb已经好久不更新了,所以还是选择后者。

配置:你需要在你包的根目录下新建一个名为:javax.usb.properties的文件,里面的内容是这样的:

javax.usb.services = org.usb4java.javax.Services

查找usb设备,其实通过usb通信流程大体上都是一致,之前我做过android与arduino通过usb通信,然后java通信走了一遍之后发现是一样的。USB 设备棵树上进行管理所有物理集线器连接一个虚拟的 USB集线器更多集线器可以连接这些集线器任何集线器可以大量连接的 USB设备

通常需要使用之前搜索特定设备,下面的一个例子如何扫描一个特定供应商产品 id 第一个设备设备:

复制代码
public UsbDevice findDevice(UsbHub hub, short vendorId, short productId)
{
    for (UsbDevice device : (List<UsbDevice>) hub.getAttachedUsbDevices())
    {
        UsbDeviceDescriptor desc = device.getUsbDeviceDescriptor();
        if (desc.idVendor() == vendorId && desc.idProduct() == productId) return device;
        if (device.isUsbHub())
        {
            device = findDevice((UsbHub) device, vendorId, productId);
            if (device != null) return device;
        }
    }
    return null;
}
复制代码

接口

当你想要与一个接口或者这个接口的端点进行通信时,那么你在使用它之前必须要claim它,并且当你结束时你必须释放它。比如:下面的代码:

复制代码
UsbConfiguration configuration = device.getActiveUsbConfiguration();
UsbInterface iface = configuration.getUsbInterface((byte) 1);
iface.claim();
try
{
    ... Communicate with the interface or endpoints ...
}
finally        
{
    iface.release();
}
复制代码

可能出现的一种情况是你想要通信的接口已经被内核驱动使用,在这种情况下你可能需要通过传递一个接口策略到claim方法以此尝试强制claim:

复制代码
iface.claim(new UsbInterfacePolicy()
{            
    @Override
    public boolean forceClaim(UsbInterface usbInterface)
    {
        return true;
    }
});
复制代码

需要注意的是,接口策略只是为了实现基础USB的一个提示.接口策略在MS-Windows上将被忽略,因为libusb在windows上不支持分派驱动。

同步 I/O

这个example发送8个字节到端点0x03:

复制代码
UsbEndpoint endpoint = iface.getUsbEndpoint(0x03);
UsbPipe pipe = endpoint.getUsbPipe();
pipe.open();
try
{
    int sent = pipe.syncSubmit(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
    System.out.println(sent + " bytes sent");
}
finally
{
    pipe.close();
}
复制代码

这个example是从端点0x83读取8个字节:

复制代码
UsbEndpoint endpoint = iface.getUsbEndpoint((byte) 0x83);
UsbPipe pipe = endpoint.getUsbPipe();
pipe.open();
try
{
    byte[] data = new byte[8];
    int received = pipe.syncSubmit(data);
    System.out.println(received + " bytes received");
}
finally
{
    pipe.close();
}
复制代码

异步 I/O

同步I/O操作与异步I/O操作类似,仅仅是使用asyncSubmint方法取代syncSubmit方法。syncSubmit方法将会阻塞直到请求完成,而asyncSubmit不会阻塞并且立即返回,为了接受请求的返回,你必须为Pipe添加一个监听器,像下面这样:

复制代码
pipe.addUsbPipeListener(new UsbPipeListener()
{            
    @Override
    public void errorEventOccurred(UsbPipeErrorEvent event)
    {
        UsbException error = event.getUsbException();
        ... Handle error ...
    }
@Override
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> dataEventOccurred(UsbPipeDataEvent event)
{
    </span><span style="color: #0000ff;">byte</span>[] data =<span style="color: #000000;"> event.getData();
    ... Process received data ...
}

});

复制代码

遇到的问题:在Linux上不能打开设备Device,其实是权限的问题导致的,你应该知道在Linux下权限分配是一门很重要的学问,其实该问题在官方网站的FAQ一栏已经提供了解决的办法,地址:点击打开

在Linux环境下,你需要给你想要通信的usb设备写一个许可文件。在此之前,你可以以root用户运行你的程序检查设备是否可以获取,如果有上述操作有作用,那么我推荐配置下udev以便当设备连接上后你的用户拥有对设备写的权限

  SUBSYSTEM=="usb",ATTR{idVendor}=="89ab",ATTR{idProduct}=="4567",MODE="0660",GROUP="wheel"

需要该更改的就是PID、VID、GROUP,关于PID和VID信息以及查看相关的接口、端口信息的查看,linux下可以使用一个叫usbview的软件,软件安装很简单,仅仅一条命令:

sudo apt-get insall usbview

注意启动的时候需要权限,而且还需要以图形界面显示(sudo usbview在我的Linux Mint没有反应的):

sudo gksu usbview

 参考官方写的一段简单的代码:

复制代码
package nir.desktop.demo;

import java.util.List;

import javax.usb.UsbConfiguration;
import javax.usb.UsbConst;
import javax.usb.UsbControlIrp;
import javax.usb.UsbDevice;
import javax.usb.UsbDeviceDescriptor;
import javax.usb.UsbEndpoint;
import javax.usb.UsbException;
import javax.usb.UsbHostManager;
import javax.usb.UsbHub;
import javax.usb.UsbInterface;
import javax.usb.UsbInterfacePolicy;
import javax.usb.UsbPipe;
import javax.usb.event.UsbPipeDataEvent;
import javax.usb.event.UsbPipeErrorEvent;
import javax.usb.event.UsbPipeListener;

public class UsbConn {
private static final short VENDOR_ID = 0x2341;
/ The product ID of the missile launcher. */
private static final short PRODUCT_ID = 0x43;
// private static final short VENDOR_ID = 0x10c4;
// // / The product ID of the missile launcher. */
// private static final short PRODUCT_ID = -5536;
private static UsbPipe pipe81, pipe01;

</span><span style="color: #008000;">/**</span><span style="color: #008000;">
 * 依据VID和PID找到设备device
 * 
 * </span><span style="color: #808080;">@param</span><span style="color: #008000;"> hub
 * </span><span style="color: #808080;">@return</span>
 <span style="color: #008000;">*/</span><span style="color: #000000;">
@SuppressWarnings(</span>"unchecked"<span style="color: #000000;">)
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span><span style="color: #000000;"> UsbDevice findMissileLauncher(UsbHub hub) {
    UsbDevice launcher </span>= <span style="color: #0000ff;">null</span><span style="color: #000000;">;

    </span><span style="color: #0000ff;">for</span> (UsbDevice device : (List&lt;UsbDevice&gt;<span style="color: #000000;">) hub.getAttachedUsbDevices()) {
        </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (device.isUsbHub()) {
            launcher </span>=<span style="color: #000000;"> findMissileLauncher((UsbHub) device);
            </span><span style="color: #0000ff;">if</span> (launcher != <span style="color: #0000ff;">null</span><span style="color: #000000;">)
                </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> launcher;
        } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> {
            UsbDeviceDescriptor desc </span>=<span style="color: #000000;"> device.getUsbDeviceDescriptor();
            </span><span style="color: #0000ff;">if</span> (desc.idVendor() ==<span style="color: #000000;"> VENDOR_ID
                    </span>&amp;&amp; desc.idProduct() ==<span style="color: #000000;"> PRODUCT_ID) {
                System.out.println(</span>"找到设备:" +<span style="color: #000000;"> device);
                </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> device;
            }
        }
    }
    </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">null</span><span style="color: #000000;">;
}

</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> sendMessage(UsbDevice device, <span style="color: #0000ff;">byte</span><span style="color: #000000;">[] message)
        </span><span style="color: #0000ff;">throws</span><span style="color: #000000;"> UsbException {
    UsbControlIrp irp </span>=<span style="color: #000000;"> device
            .createUsbControlIrp(
                    (</span><span style="color: #0000ff;">byte</span>) (UsbConst.REQUESTTYPE_TYPE_CLASS |<span style="color: #000000;"> UsbConst.REQUESTTYPE_RECIPIENT_INTERFACE),
                    (</span><span style="color: #0000ff;">byte</span>) 0x09, (<span style="color: #0000ff;">short</span>) 2, (<span style="color: #0000ff;">short</span>) 1<span style="color: #000000;">);
    irp.setData(message);
    device.syncSubmit(irp);
}

</span><span style="color: #008000;">/**</span><span style="color: #008000;">
 * 注意权限的配置问题,在linux下可能无法打开device,解决办法参考官方的FAQ
 * 
 * </span><span style="color: #808080;">@param</span><span style="color: #008000;"> args
 </span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> main(String[] args) {
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> TODO Auto-generated method stub</span>

UsbDevice device;
try {
device = findMissileLauncher(UsbHostManager.getUsbServices()
.getRootUsbHub());
if (device == null) {
System.out.println(“Missile launcher not found.”);
System.exit(1);
return;
}
UsbConfiguration configuration = device.getActiveUsbConfiguration();//获取配置信息
UsbInterface iface = configuration.getUsbInterface((byte) 1);//接口
iface.claim(new UsbInterfacePolicy() {

            @Override
            </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">boolean</span><span style="color: #000000;"> forceClaim(UsbInterface arg0) {
                </span><span style="color: #008000;">//</span><span style="color: #008000;"> TODO Auto-generated method stub</span>
                <span style="color: #0000ff;">return</span> <span style="color: #0000ff;">true</span><span style="color: #000000;">;
            }
        });
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> for (UsbEndpoint endpoints : (List&lt;UsbEndpoint&gt;) iface
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> .getUsbEndpoints()) {
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> System.out.println("---&gt;"+endpoints.getUsbEndpointDescriptor());
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> }</span>
        UsbEndpoint endpoint81 = iface.getUsbEndpoint((<span style="color: #0000ff;">byte</span>) 0x83);<span style="color: #008000;">//</span><span style="color: #008000;">接受数据地址</span>
        UsbEndpoint endpoint01 = iface.getUsbEndpoint((<span style="color: #0000ff;">byte</span>)0x04);<span style="color: #008000;">//</span><span style="color: #008000;">发送数据地址</span>
        pipe81 =<span style="color: #000000;"> endpoint81.getUsbPipe();
        pipe81.open();
        pipe01 </span>=<span style="color: #000000;"> endpoint01.getUsbPipe();
        pipe01.open();
        </span><span style="color: #0000ff;">byte</span>[] dataSend = { (<span style="color: #0000ff;">byte</span>) 0x00 };<span style="color: #008000;">//</span><span style="color: #008000;">需要发送的数据</span>

pipe01.asyncSubmit(dataSend);
pipe81.addUsbPipeListener(new UsbPipeListener() {

            @Override
            </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> errorEventOccurred(UsbPipeErrorEvent arg0) {
                </span><span style="color: #008000;">//</span><span style="color: #008000;"> TODO Auto-generated method stub</span>

System.out.println(arg0);
}

            @Override
            </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> dataEventOccurred(UsbPipeDataEvent arg0) {
                </span><span style="color: #008000;">//</span><span style="color: #008000;"> TODO Auto-generated method stub</span>
                System.out.println(<span style="color: #0000ff;">new</span><span style="color: #000000;"> String(arg0.getData()));
            }
        });

// pipe81.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
//
}
}
}

复制代码

其中的一些接口、端口都是依据usbview写的,数据是发成功了(RX灯亮了),但是因为我使用的是arduino,里面的代码不知道怎么写,如果有这方便的高手还请帮我一下,谢谢。FQ看了下有的人用的是同步传输出现的问题,相关的解决办法,这里我就完全粘贴、复制了:Here is an abbreviated example. Note that I have removed from this, allchecks, error handling, and such.It is just the logic of creating two pipes for two endpoints. One IN andone OUT. Then using them to do a write and a read.It assumes that endpoint 2 is an OUT and endpoint 4 is an IN.Note also that the name of an IN endpoint is OR'd with 0x80:

import javax.usb.*;

public class Example {

public static void main(String[] args) throws Exception {
UsbDevice usbDevice = …getUsbDevice by matching vID, pID, knowing
the path… or whatever works for you…
usbDevice.getUsbConfigurations();
UsbConfiguration config = usbDevice.getActiveUsbConfiguration();
UsbInterface xface = config.getUsbInterface((byte)0);
xface.claim();

UsbEndpoint endPt2 = xface.getUsbEndpoint((byte)2); //OUT
endpoint
UsbEndpoint endPt4 = xface.getUsbEndpoint((byte)(4 | 0x80) ); //IN
endpoint

UsbPipe pipe2 = endPt2.getUsbPipe();
pipe2.open();
UsbPipe pipe4 = endPt4.getUsbPipe();
pipe4.open();

//Use the pipes:
byte[] bytesToSend = new byte[] {1,2,3}; //Going to send out these 3
bytes
UsbIrp irpSend = pipe2.createUsbIrp();
irpSend.setData(bytesToSend);
pipe2.asyncSubmit(irpSend); //Send out some bytes
irpSend.waitUntilComplete(1000); //Wait up to 1 second

byte[] bytesToRead = new byte[3]; //Going to read 3 bytes into here
UsbIrp irpRead = pipe2.createUsbIrp();
irpRead.setData(bytesToRead);
pipe4.asyncSubmit(irpRead); //Read some bytes
irpRead.waitUntilComplete(1000); //Wait up to 1 second
}
}

View Code

下面是我监听数据返回点完整代码:

 UsbConn.java:

package DemoMath;

import java.util.List;

import javax.usb.UsbConfiguration;
import javax.usb.UsbConst;
import javax.usb.UsbControlIrp;
import javax.usb.UsbDevice;
import javax.usb.UsbDeviceDescriptor;
import javax.usb.UsbEndpoint;
import javax.usb.UsbException;
import javax.usb.UsbHostManager;
import javax.usb.UsbHub;
import javax.usb.UsbInterface;
import javax.usb.UsbInterfacePolicy;
import javax.usb.UsbPipe;
import javax.usb.util.UsbUtil;

public class UsbConn {
private static final short VENDOR_ID = 0x04b4;
private static final short PRODUCT_ID = 0x1004;
private static final byte INTERFACE_AD= 0x00;
private static final byte ENDPOINT_OUT= 0x02;
private static final byte ENDPOINT_IN= (byte) 0x86;
private static final byte[] COMMAND = {0x01,0x00};

@SuppressWarnings(</span><span style="color: #800000;">"</span><span style="color: #800000;">unchecked</span><span style="color: #800000;">"</span><span style="color: #000000;">)
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span><span style="color: #000000;"> UsbDevice findMissileLauncher(UsbHub hub) {
    UsbDevice launcher </span>= <span style="color: #0000ff;">null</span><span style="color: #000000;">;

    </span><span style="color: #0000ff;">for</span> (UsbDevice device : (List&lt;UsbDevice&gt;<span style="color: #000000;">) hub.getAttachedUsbDevices()) {
        </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (device.isUsbHub()) {
            launcher </span>=<span style="color: #000000;"> findMissileLauncher((UsbHub) device);
            </span><span style="color: #0000ff;">if</span> (launcher != <span style="color: #0000ff;">null</span><span style="color: #000000;">)
                </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> launcher;
        } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> {
            UsbDeviceDescriptor desc </span>=<span style="color: #000000;"> device.getUsbDeviceDescriptor();
            </span><span style="color: #0000ff;">if</span> (desc.idVendor() ==<span style="color: #000000;"> VENDOR_ID
                    </span>&amp;&amp; desc.idProduct() ==<span style="color: #000000;"> PRODUCT_ID) {
                System.</span><span style="color: #0000ff;">out</span>.println(<span style="color: #800000;">"</span><span style="color: #800000;">发现设备</span><span style="color: #800000;">"</span> +<span style="color: #000000;"> device);
                </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> device;
            }
        }
    }
    </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">null</span><span style="color: #000000;">;
}
</span><span style="color: #008000;">//</span><span style="color: #008000;">command for controlTransfer</span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> sendMessage(UsbDevice device, <span style="color: #0000ff;">byte</span><span style="color: #000000;">[] message)
        throws UsbException {
    UsbControlIrp irp </span>=<span style="color: #000000;"> device
            .createUsbControlIrp(
                    (</span><span style="color: #0000ff;">byte</span>) (UsbConst.REQUESTTYPE_TYPE_CLASS |<span style="color: #000000;"> UsbConst.REQUESTTYPE_RECIPIENT_INTERFACE),
                    (</span><span style="color: #0000ff;">byte</span>) <span style="color: #800080;">0x09</span>, (<span style="color: #0000ff;">short</span>) <span style="color: #800080;">2</span>, (<span style="color: #0000ff;">short</span>) <span style="color: #800080;">1</span><span style="color: #000000;">);
    irp.setData(message);
    device.syncSubmit(irp);
}
</span><span style="color: #008000;">/*</span><span style="color: #008000;">*
 * Class to listen in a dedicated Thread for data coming events.
 * This really could be used for any HID device.
 </span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">class</span><span style="color: #000000;"> HidMouseRunnable implements Runnable
{
    </span><span style="color: #008000;">/*</span><span style="color: #008000;"> This pipe must be the HID interface's interrupt-type in-direction endpoint's pipe. </span><span style="color: #008000;">*/</span>
    <span style="color: #0000ff;">public</span> HidMouseRunnable(UsbPipe pipe) { usbPipe =<span style="color: #000000;"> pipe; }
    </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> run()
    {
        </span><span style="color: #0000ff;">byte</span>[] buffer = <span style="color: #0000ff;">new</span> <span style="color: #0000ff;">byte</span><span style="color: #000000;">[UsbUtil.unsignedInt(usbPipe.getUsbEndpoint().getUsbEndpointDescriptor().wMaxPacketSize())];
        @SuppressWarnings(</span><span style="color: #800000;">"</span><span style="color: #800000;">unused</span><span style="color: #800000;">"</span><span style="color: #000000;">)
        </span><span style="color: #0000ff;">int</span> length = <span style="color: #800080;">0</span><span style="color: #000000;">;

        </span><span style="color: #0000ff;">while</span><span style="color: #000000;"> (running) {
            </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
                length </span>=<span style="color: #000000;"> usbPipe.syncSubmit(buffer);
            } </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> ( UsbException uE ) {
                </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (running) {
                    System.</span><span style="color: #0000ff;">out</span>.println(<span style="color: #800000;">"</span><span style="color: #800000;">Unable to submit data buffer to HID mouse : </span><span style="color: #800000;">"</span> +<span style="color: #000000;"> uE.getMessage());
                    </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
                }
            }
            </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (running) {

// System.out.print(“Got " + length + " bytes of data from HID mouse :”);
// for (int i=0; i<length; i++)
// System.out.print(" 0x" + UsbUtil.toHexString(buffer[i]));
try {
String result = DataFix.getHexString(buffer);
System.out.println(result);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/
* Stop/abort listening for data events.
/
public void stop()
{
running = false;
usbPipe.abortAllSubmissions();
}
public boolean running = true;
public UsbPipe usbPipe = null;
}
/*
* get the correct Interface for USB
* @param device
* @return
* @throws UsbException
/
public static UsbInterface readInit() throws UsbException{
UsbDevice device = findMissileLauncher(UsbHostManager.getUsbServices()
.getRootUsbHub());
if (device == null) {
System.out.println(“Missile launcher not found.”);
System.exit(1);
}
UsbConfiguration configuration = device.getActiveUsbConfiguration();
UsbInterface iface = configuration.getUsbInterface(INTERFACE_AD);//Interface Alternate Number
//if you using the MS os,may be you need judge,because MS do not need follow code,by tong
iface.claim(new UsbInterfacePolicy() {
@Override
public boolean forceClaim(UsbInterface arg0) {
// TODO Auto-generated method stub
return true;
}
});
return iface;
}
/*
* 异步bulk传输,by tong
* @param usbInterface
* @param data
/
public static void syncSend(UsbInterface usbInterface,byte[] data) {
UsbEndpoint endpoint = usbInterface.getUsbEndpoint(ENDPOINT_OUT);
UsbPipe outPipe = endpoint.getUsbPipe();
try {
outPipe.open();
outPipe.syncSubmit(data);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
outPipe.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static HidMouseRunnable listenData(UsbInterface usbInterface) {
UsbEndpoint endpoint = usbInterface.getUsbEndpoint(ENDPOINT_IN);
UsbPipe inPipe = endpoint.getUsbPipe();
HidMouseRunnable hmR = null;
try {
inPipe.open();
hmR = new HidMouseRunnable(inPipe);
Thread t = new Thread(hmR);
t.start();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return hmR;
}
/*
* 主程序入口
*
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
UsbInterface iface;
try {
iface = readInit();
listenData(iface);
syncSend(iface, COMMAND);
} catch (UsbException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

View Code

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值