java 串口通信 简单封装

Java提供了 CommunicationAPI(包含于javax.comm包中)用于通过与机器无关的方式,控制各种外部设备。Communications API,是标准的Java的扩展部分,它在JavaAPI中是没有附带的。因此,必须先在SUN公司网站的Java站点(www.java.sun.com)上下载这个扩展类库。

  1.1Communications API 简介

 Communications API 的核心是抽象的CommPort类及其两个子类:SerialPort类和ParallePort类。其中,SerialPort类是用于串口通信的类,ParallePort类是用于并行口通信的类。CommPort类还提供了常规的通信模式和方法,例如:getInputStream( )方法和getOutputStream( )方法,专用于与端口上的设备进行通信。

  然而,这些类的构造方法都被有意的设置为非公有的(non-public)。所以,不能直接构造对象,而是先通过静态的CommPortIdentifer.getPortIdentifiers()获得端口列表;再从这个端口列表中选择所需要的端口,并调用CommPortIdentifer对象的Open( )方法,这样,就能得到一个CommPort对象。当然,还要将这个CommPort对象的类型转换为某个非抽象的子类,表明是特定的通讯设备。该子类可以是SerialPort类和ParallePort类中的一个。下面将分别对CommPort类,CommPortIdentifier类,串口类SerialPort进行详细的介绍。

  1.2 CommPortIdentifier类

  CommPortIdentifier类的方法如下:

方法 说明

addPortName(String, int, CommDriver) 添加端口名到端口列表里

addPortOwnershipListener(CommPortOwnershipListener) 添加端口拥有的监听器

removePortOwnershipListener(CommPortOwnershipListener) 移除端口拥有的监听器

getCurrentOwner() 得到当前占有端口的对象或应用程序

getName() 得到端口名称

getPortIdentifier(CommPort) 得到参数打开的端口的CommPortIdentifier类型对象

getPortIdentifier(String) 得到以参数命名的端口的CommPortIdentifier类型对象

getPortIdentifiers() 得到系统中的端口列表

getPortType() 得到端口的类型

isCurrentlyOwned() 判断当前端口是否被占用

open(FileDescriptor) 用文件描述的类型打开端口

open(String, int) 打开端口,两个参数:程序名称,延迟时间(毫秒数)

  1.3 SerialPort类

SerialPort关于串口参数的静态成员变量

成员变量 说明 成员变量 说明 成员变量 说明

DATABITS_5 数据位为5 STOPBITS_2 停止位为2 PARITY_ODD 奇检验

DATABITS_6 数据位为6 STOPBITS_1 停止位为1 PARITY_MARK 标记检验

DATABITS_7 数据位为7 STOPBITS_1_5 停止为1.5 PARITY_NONE 空格检验

DATABITS_8 数据位为8 PARITY_EVEN 偶检验 PARITY_SPACE 无检验

SerialPort对象的关于串口参数的函数

方法 说明 方法 说明

getBaudRate() 得到波特率 getParity() 得到检验类型

getDataBits() 得到数据位数 getStopBits() 得到停止位数

setSerialPortParams(int, int, int, int) 设置串口参数依次为(波特率,数据位,停止位,奇偶检验)

SerialPort关于事件的静态成员变量

成员变量 说明 成员变量 说明

BI Break interrupt中断 FE Framing error错误

CD Carrier detect载波侦听 OE Overrun error错误

CTS Clear to send清除以传送 PE Parity error奇偶检验错误

DSR Data set ready数据备妥 RI Ring indicator响铃侦测

DATA_AVAILABLE 串口中的可用数据 OUTPUT_BUFFER_EMPTY 输出缓冲区空

SerialPort中关于事件的方法

方法 说明 方法 说明 方法 说明

isCD() 是否有载波 isCTS() 是否清除以传送 isDSR() 数据是否备妥

isDTR() 是否数据端备妥 isRI() 是否响铃侦测 isRTS()  是否要求传送

addEventListener(SerialPortEventListener)   向SerialPort对象中添加串口事件监听器

removeEventListener() 移除SerialPort对象中的串口事件监听器

notifyOnBreakInterrupt(boolean) 设置中断事件true有效,false无效

notifyOnCarrierDetect(boolean) 设置载波监听事件true有效,false无效

notifyOnCTS(boolean) 设置清除发送事件true有效,false无效

notifyOnDataAvailable(boolean) 设置串口有数据的事件true有效,false无效

notifyOnDSR(boolean) 设置数据备妥事件true有效,false无效

notifyOnFramingError(boolean) 设置发生错误事件true有效,false无效

notifyOnOutputEmpty(boolean) 设置发送缓冲区为空事件true有效,false无效

notifyOnParityError(boolean) 设置发生奇偶检验错误事件true有效,false无效

notifyOnRingIndicator(boolean) 设置响铃侦测事件true有效,false无效

getEventType() 得到发生的事件类型返回值为int型

sendBreak(int) 设置中断过程的时间,参数为毫秒值

setRTS(boolean) 设置或清除RTS位

setDTR(boolean) 设置或清除DTR位

SerialPort中的其他常用方法

方法 说明

close() 关闭串口

getOutputStream() 得到OutputStream类型的输出流

getInputStream() 得到InputStream类型的输入流

 

什么是串口通讯?

串行通讯协议有很多种,像RS232,RS485,RS422,甚至现今流行的USB等都是串行通讯协议。而串行通讯技术的应用无处不在。可能大家见的最多就是电脑的串口与Modem的通讯。在PC机刚开始在中国流行起来时(大约是在90年代前五年),那时甚至有人用一条串行线进行两台电脑之间的数据共享。除了这些,手机,PDA,USB鼠标、键盘等等都是以串行通讯的方式与电脑连接。还有像多串口卡,各种种类的具有串口通讯接口的检测与测量仪器,串口通讯的网络设备等也是通过串口与计算机连接的。

虽然串口通讯协议有多种,不过目前还是以RS232的通讯方式居多。

 

RS232通讯基础

RS-232-C(又称 EIA RS-232-C,以下简称RS232)是在1970年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。RS232是一个全双工的通讯协议,它可以同时进行数据接收和发送的工作。RS232的端口通常有两种:9针(DB9)和25针(DB25)。

DB9和DB25的常用针脚定义

 9针串口(DB9)

 25针串口(DB25)

 

针号

 功能说明

 缩写 针号

 功能说明

 缩写 

1

 数据载波检测

 DCD 8

 数据载波检测

 DCD 

2

 接收数据 RXD 3

 接收数据 RXD 

3

 发送数据

 TXD 2

 发送数据

 TXD 

4

 数据终端准备

 DTR 20

 数据终端准备

 DTR 

5

 信号地

 GND 7

 信号地

 GND 

6

 数据设备准备好

 DSR 6 

 数据准备好

 DSR 

7

 请求发送 RTS 4

 请求发送

 RTS 

8

 清除发送

 CTS 5

 清除发送

 CTS 

9

 振铃指示

 RI 22

 振铃指示

 RI

 

常见的边线方式

常见的通讯方式是三线式,这种方式是将两个RS232设备的发送端(TXD)和接收端(RXD)及接地端(GND)互相连接,也是许多读者所知道的连接方式:

(9针)

2(RXD) ---------

 3(TXD 

3(TXD) ---------

 2(TXD) 

5(GND) ---------

 5(GND)

 

(25针)

2(RXD) ---------

 3(TXD 

3(TXD) ---------

 2(RXD) 

7(GND) ---------

 7(GND)

 

这种方式分别将两端的RS232接口的2--3,3---2,5(7)---5(7)针脚连接起来。其中2是数据接收线(RXD),3是数据发送线(TXD),5(7)是接地(RND)。如果有一台式PC,和一部NoteBook电脑,就可以用这种方式连线了。用三线式可以将大多数的RS232设备连接起来。但如果你认死了2--3,3--2,5(7)--5(7)对接这个理,会发现在连某些RS232设备时并不奏效。这是因为有些设备在电路内部已将2和3线调换过来了,你只要2,3,5(7)针一一对应就行了。

大致了解了RS232之后,我主要关心的是如何使用Java进行串口通讯。

Java Communications API

Sun的J2SE中并没有直接提供以上提到的任何一种串行通讯协议的开发包,而是以独立的jar包形式发布在java.sun.com网站上(从这里下载)----即comm.jar,称之为Javatm Communications API,它是J2SE的标准扩展。comm.jar并不是最近才有,早在1998年时,sun就已经发布了这个开发包。comm.jar分别提供了对常用的RS232串行端口和IEEE1284并行端口通讯的支持。目前sun发布的comm.jar只有Windows和Solaris平台两个版本,如果你需要Linux平台下的,可以使用rxtx。

在使用comm.jar之前,必须知道如何安装它。这也是困扰许多初学java RS232通讯者的一个难题。如果我们电脑上安装了JDK, 它将同时为我们安装一份JRE(Java Runtime Entironment),通常我们运行程序时都是以JRE来运行的。所以以下的安装适用于JRE。如果你是用JDK来运行程序的,请将相应的<JRE_HOME>改成<JDK_HOME>。

下载了comm.jar开发包后,与之一起的还有两个重要的文件,win32com.dll和javax.comm.properties。 comm.jar提供了通讯用的java API,而win32com.dll提供了供comm.jar调用的本地驱动接口。而javax.comm.properties是这个驱动的类配置文件。首先将comm.jar复制到<JRE_HOME>libext目录。再将win32com.dll复制到你的RS232应用程序运行的目录,即user.dir。然后将javax.comm.properties复制到<JRE_HOME>lib目录。

Comm API基础

所有的comm API位于javax.comm包下面。从Comm API的javadoc来看,它介绍给我们的只有区区以下13个类或接口:

javax.comm.CommDriver 

javax.comm.CommPort 

javax.comm.ParallelPort 

javax.comm.SerialPort 

javax.comm.CommPortIdentifier 

javax.comm.CommPortOwnershipListener

javax.comm.ParallelPortEvent 

javax.comm.SerialPortEvent 

javax.comm.ParallelPortEventListener (extends java.util.EventListener) 

javax.comm.SerialPortEventListener (extends java.util.EventListener) 

javax.comm.NoSuchPortException 

javax.comm.PortInUseException 

javax.comm.UnsupportedCommOperationException 

下面讲解一下几个主要类或接口。

1.枚举出系统所有的RS232端口

在开始使用RS232端口通讯之前,我们想知道系统有哪些端口是可用的,以下代码列出系统中所有可用的RS232端口:

Enumeration en = CommPortIdentifier.getPortIdentifiers();

CommPortIdentifier portId;

while (en.hasMoreElements()) 

{

portId = (CommPortIdentifier) en.nextElement();

/*如果端口类型是串口,则打印出其端口信息*/

if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) 

{

System.out.println(portId.getName());

}

}

在我的电脑上以上程序输出以下结果:

COM1

COM2

CommPortIdentifier类的getPortIdentifiers方法可以找到系统所有的串口,每个串口对应一个CommPortIdentifier类的实例。

2.打开端口

如果你使用端口,必须先打开它。

try{

CommPort serialPort = portId.open("My App", 60);

/* 从端口中读取数据*/

InputStream input = serialPort.getInputStream();

input.read(...); 

/* 往端口中写数据*/

OutputStream output = serialPort.getOutputStream();

output.write(...)

...

}catch(PortInUseException ex)

{ ... }

通过CommPortIdentifier的open方法可以返回一个CommPort对象。open方法有两个参数,第一个是String,通常设置为你的应用程序的名字。第二个参数是时间,即开启端口超时的毫秒数。当端口被另外的应用程序占用时,将抛出PortInUseException异常。

在这里CommPortIdentifier类和CommPort类有什么区别呢?其实它们两者是一一对应的关系。CommPortIdentifier主要负责端口的初始化和开启,以及管理它们的占有权。而CommPort则是跟实际的输入和输出功能有关的。通过CommPort的getInputStream()可以取得端口的输入流,它是java.io.InputStream接口的一个实例。我们可以用标准的InputStream的操作接口来读取流中的数据,就像通过FileInputSteam读取文件的内容一样。相应的,CommPort的getOutputStream可以获得端口的输出流,这样就可以往串口输出数据了。

3. 关闭端口

使用完的端口,必须记得将其关闭,这样可以让其它的程序有机会使用它,不然其它程序使用该端口时可能会抛出端口正在使用中的错误。很奇怪的是,CommPortIdentifier类只提供了开启端口的方法,而要关闭端口,则要调用CommPort类的close()方法。

通讯方式

CommPort的输入流的读取方式与文件的输入流有些不一样,那就是你可能永远不知这个InputStream何时结束,除非对方的OutputStream向你发送了一个特定数据表示发送结束,你收到这个特定字符后,再行关闭你的InputStream。而comm.jar提供了两种灵活的方式让你读取数据。

1. 轮询方式(Polling)

举个例子,你同GF相约一起出门去看电影,但你的GF好打扮,这一打扮可能就是半小时甚至一小时以上。这时你就耐不住了,每两分钟就催问一次“好了没?”,如此这样,直到你的GF说OK了才算完。这个就叫轮询(Polling)。

在程序中,轮询通常设计成一个封闭的循环,当满足某个条件时即结束循环。刚才那个例子中,你的GF说“OK了!”,这个就是结束你轮询的条件。在单线程的程序中,当循环一直执行某项任务而又无法预知它何时结束时,此时你的程序看起来可能就像死机一样。在VB程序中,这个问题可以用在循环结构中插入一个doEvent语句来解决。而Java中,最好的方式是使用线程,就像以下代码片断一样。

public TestPort extend Thread

{

...

InputStream input = serialPort.getInputStream();

StringBuffer buf = new StringBuffer();

boolean stopped = false;

...

public void run()

{

try {

while( !stopped )

int ch = input.read();

if ( ch==''q'' || ch==''Q'' )

{

/* 结束读取,关闭端口...*/

stopped = true;

...

}

else

{

buf.append((char)ch);

...

}

}catch (InterruptedException e) { }}

}

2. 监听方式(listening)

Comm API支持标准的Java Bean型的事件模型。也就是说,你可以使用类似AddXXXListener这样的方法为一个串口注册自己的监听器,以监听方式进行数据读取。

如要对端口监听,你必须先取得CommPortIdentifier类的一个实例,

CommPort serialPort = portId.open("My App", 60);

从而取得SerialPort,再调用它的addEventListener方法为它添加监听器,

serialPort.addEventListener(new MyPortListener());

SerialPort的监听器必须继承于SerialPortEventListener接口。当有任何SerialPort的事件发生时,将自动调用监听器中的serialEvent方法。Serial Event有以下几种类型:

BI - 通讯中断.

CD - 载波检测.

CTS - 清除发送.

DATA_AVAILABLE - 有数据到达.

DSR - 数据设备准备好.

FE - 帧错误.

OE - 溢位错误.

OUTPUT_BUFFER_EMPTY - 输出缓冲区已清空.

PE - 奇偶校验错.

RI -  振铃指示.

下面是一个监听器的示例,

public void MyPortListener implements SerialPortEventListener

{

 

这个监听器只是简单打印每个发生的事件名称。而对于大多数应用程序来说,通常关心是DATA_AVAILABLE事件,当数据从外部设备传送到端口上来时将触发此事件。此时就可以使用前面提到过的方法,serialPort.getInputStream()来从InputStream中读取数据了。

完整的程序

为节省篇幅,本文只提供了一些代码片断来帮助读者来理解Comm API的用法。你可以从Comm API的开发包中取得完整的可运行的演示程序。请先下载了comm API的开发包,解压之后有一个名为Sample的目录,里面有几个演示程序,分别是:

1) BlackBox: A Serial Port BlackBox application.

2) ParallelBlackBox: A Parallel Port BlackBox application

3) SerialDemo: A simpler SerialPort sample application

4) Simple: A very simple comm application

5) NullDriver: A template for driver writers. Can be used as the starting point

to write a driver for the Comm API.

6) porting: A template CommPortIdentifier java file for people interested

in porting the Comm API to a new platform.

其中,第1),3),4)是关于rs232通讯的演示程序。而其它的,2)是并行端口的演示程序。5)和6)是开发自己的端口驱动程序的模板程序,有兴趣的读者可以自行研究。

public void serialEvent(SerialPortEvent evt)

{ switch (evt.getEventType())

{ case SerialPortEvent.CTS :

System.out.println("CTS event occured.");

break; case SerialPortEvent.CD :

System.out.println("CD event occured.");

break; case SerialPortEvent.BI :

System.out.println("BI event occured.");

break; case SerialPortEvent.DSR :

System.out.println("DSR event occured.");

break; case SerialPortEvent.FE :

System.out.println("FE event occured.");

break; case SerialPortEvent.OE :

System.out.println("OE event occured.");

break; case SerialPortEvent.PE :

System.out.println("PE event occured.");

break; case SerialPortEvent.RI :

System.out.println("RI event occured.");

break; case SerialPortEvent.OUTPUT_BUFFER_EMPTY :

System.out.println("OUTPUT_BUFFER_EMPTY event occured.");

break; case SerialPortEvent.DATA_AVAILABLE :

System.out.println("DATA_AVAILABLE event occured.");

int ch;

StringBuffer buf = new StringBuffer();InputStream input = serialPort.getInputStream

try {

while ( (ch=input.read()) > 0) {

buf.append((char)ch); 

}

System.out.print(buf);

} catch (IOException e) {}

break;

}}

 

 

java串口通讯

最近在搞C的串口通讯,C里面没有线程的概念,所以C对串口的读写只能在一个进程里面,这样

如果串口的缓存有问题,就会导致报告丢失(正好是我们遇到的),我们来看看支持线程的java是如何来解决这个问题的。

 

本文介绍了一个简单的通过串口实现全双工通讯的Java类库,该类库大大的简化了对串口进行操作的过程。 

本类库主要包括:SerialBean.java (与其他应用程序的接口), SerialBuffer.java (用来保存从串口所接收数据的缓冲区), ReadSerial.java (从串口读取数据的程序)。另外本类库还提供了一个例程SerialExample.java 作为示范。在下面的内容中将逐一对这几个部分进行详细介绍。

SerialBean

SerialBean是本类库与其他应用程序的接口。该类库中定义了SerialBean的构造方法以及初始化串口,从串口读取数据,往串口写入数据以及关闭串口的函数。具体介绍如下:

public SerialBean(int PortID) 

本函数构造一个指向特定串口的SerialBean,该串口由参数PortID所指定。PortID = 1 表示COM1,PortID = 2 表示COM2,由此类推。 

public int Initialize() 

本函数初始化所指定的串口并返回初始化结果。如果初始化成功返回1,否则返回-1。初始化的结果是该串口被SerialBean独占性使用,其参数被设置为9600, N, 8, 1。如果串口被成功初始化,则打开一个进程读取从串口传入的数据并将其保存在缓冲区中。 

public String ReadPort(int Length) 

本函数从串口(缓冲区)中读取指定长度的一个字符串。参数Length指定所返回字符串的长度。 

public void WritePort(String Msg) 

本函数向串口发送一个字符串。参数Msg是需要发送的字符串。 

public void ClosePort() 

本函数停止串口检测进程并关闭串口。

 

SerialBean的源代码如下:    package serial;

    import java.io.*;

    import java.util.*;

    import javax.comm.*;

    /**

     *

     * This bean provides some basic functions to implement full dulplex

     * information exchange through the srial port.

     *

     */

    public class SerialBean

    {

        static String PortName;

        CommPortIdentifier portId;

        SerialPort serialPort;

        static OutputStream out;

        static InputStream  in;

        SerialBuffer SB;

        ReadSerial   RT;

            /**

             *

             * Constructor

             *

             * @param PortID the ID of the serial to be used. 1 for COM1,

             * 2 for COM2, etc.

             *

             */

            public SerialBean(int PortID)

            {

                PortName = "COM" + PortID;

            }

            /**

             *

             * This function initialize the serial port for communication. It startss a

             * thread which consistently monitors the serial port. Any signal capturred

             * from the serial port is stored into a buffer area.

             *

             */

            public int Initialize()

            {

                int InitSuccess = 1;

                int InitFail    = -1;

            try

            {

                portId = CommPortIdentifier.getPortIdentifier(PortName);

                try

                {

                    serialPort = (SerialPort)

                    portId.open("Serial_Communication", 2000);

                } catch (PortInUseException e)

                {

                    return InitFail;

                }

                //Use InputStream in to read from the serial port, and OutputStream

                //out to write to the serial port.

                try

                {

                    in  = serialPort.getInputStream();

                    out = serialPort.getOutputStream();

                } catch (IOException e)

                {

                    return InitFail;

                }

                //Initialize the communication parameters to 9600, 8, 1, none.

                try

                {

                     serialPort.setSerialPortParams(9600,

                                SerialPort.DATABITS_8,

                                SerialPort.STOPBITS_1,

                                SerialPort.PARITY_NONE);

                } catch (UnsupportedCommOperationException e)

                {

                    return InitFail;

                }

            } catch (NoSuchPortException e)

            {

                return InitFail;

            }

            // when successfully open the serial port,  create a new serial buffer,

            // then create a thread that consistently accepts incoming signals from

            // the serial port. Incoming signals are stored in the serial buffer.

            SB = new SerialBuffer();

            RT = new ReadSerial(SB, in);

            RT.start();

            // return success information

            return InitSuccess;

            }

            /**

             *

             * This function returns a string with a certain length from the incomin

             * messages.

             *

             * @param Length The length of the string to be returned.

             *

             */

            public String ReadPort(int Length)

            {

                String Msg;

                Msg = SB.GetMsg(Length);

                return Msg;

            }

            /**

             *

             * This function sends a message through the serial port.

             *

             * @param Msg The string to be sent.

             *

             */

            public void WritePort(String Msg)

            {

                int c;

                try

                {

                    for (int i = 0; i < Msg.length(); i++)

                        out.write(Msg.charAt(i));

                } catch (IOException e)  {}

            }

            /**

             *

             * This function closes the serial port in use.

             *

             */

            public void ClosePort()

            {

                RT.stop();

                serialPort.close();

            }

    }

   

 

SerialBuffer

SerialBuffer是本类库中所定义的串口缓冲区,它定义了往该缓冲区中写入数据和从该缓冲区中读取数据所需要的函数。public synchronized String GetMsg(int Length) 

本函数从串口(缓冲区)中读取指定长度的一个字符串。参数Length指定所返回字符串的长度。 

public synchronized void PutChar(int c) 

本函数望串口缓冲区中写入一个字符,参数c 是需要写入的字符。 

在往缓冲区写入数据或者是从缓冲区读取数据的时候,必须保证数据的同步,因此GetMsg和PutChar函数均被声明为synchronized并在具体实现中采取措施实现的数据的同步。

 

SerialBuffer的源代码如下:    package serial;

    /**

     *

     * This class implements the buffer area to store incoming data from the serial

     * port.

     *

     */

    public class SerialBuffer

    {

        private String Content = "";

        private String CurrentMsg, TempContent;

        private boolean available = false;

        private int LengthNeeded = 1;

            /**

             *

             * This function returns a string with a certain length from the incomin

             * messages.

             *

             * @param Length The length of the string to be returned.

             *

             */

        public synchronized String GetMsg(int Length)

        {

            LengthNeeded = Length;

            notifyAll();

            if (LengthNeeded > Content.length())

            {

                available = false;

                while (available == false)

                {

                    try

                    {

                        wait();

                    } catch (InterruptedException e) { }

                }

            }

            CurrentMsg  = Content.substring(0, LengthNeeded);

            TempContent = Content.substring(LengthNeeded);

            Content = TempContent;

            LengthNeeded = 1;

            notifyAll();

            return CurrentMsg;

        }

            /**

             *

             * This function stores a character captured from the serial port to the

             * buffer area.

             *

             * @param t The char value of the character to be stored.

             *

             */

        public synchronized void PutChar(int c)

        {

            Character d = new Character((char) c);

            Content = Content.concat(d.toString());

            if (LengthNeeded < Content.length())

            {

                available = true;

            }

            notifyAll();

        }

    }

   

 

ReadSerial

ReadSerial是一个进程,它不断的从指定的串口读取数据并将其存放到缓冲区中。public ReadSerial(SerialBuffer SB, InputStreamPort) 

本函数构造一个ReadSerial进程,参数SB指定存放传入数据的缓冲区,参数Port指定从串口所接收的数据流。 

public void run() 

ReadSerial进程的主函数,它不断的从指定的串口读取数据并将其存放到缓冲区中。

 

ReadSerial的源代码如下:    package serial;

    import java.io.*;

    /**

     *

     * This class reads message from the specific serial port and save

     * the message to the serial buffer.

     *

     */

    public class ReadSerial extends Thread

    {

        private SerialBuffer ComBuffer;

        private InputStream ComPort;

            /**

             *

             * Constructor

             *

             * @param SB The buffer to save the incoming messages.

             * @param Port The InputStream from the specific serial port.

             *

             */

        public ReadSerial(SerialBuffer SB, InputStream Port)

        {

            ComBuffer = SB;

            ComPort = Port;

        }

        public void run()

        {

            int c;

            try

            {

                while (true)

                {

                    c = ComPort.read();

                    ComBuffer.PutChar(c);

                }

            } catch (IOException e) {}

        }

    }

 

 

SerialExample

SerialExample是本类库所提供的一个例程。它所实现的功能是打开串口COM1,对其进行初始化,从串口读取信息对其进行处理后将处理结果发送到串口。    import serial.*;

    import java.io.*;

    /**

     *

     * This is an example of how to use the SerialBean. It opens COM1 and reads

     * six messages with different length form the serial port.

     *

     */

    class SerialExample

    {

        public static void main(String[] args)

        {

            //TO DO: Add your JAVA codes here

            SerialBean SB = new SerialBean(1);

            String Msg;

            SB.Initialize();

            for (int i = 5; i <= 10; i++)

            {

                Msg = SB.ReadPort(i);

                SB.WritePort("Reply: " + Msg);

            }

            SB.ClosePort();

        }

    }

 

 

编译与调试

本类库中使用了Java Communication API (javax.comm)。这是一个Java扩展类库,并不包括在标准的Java SDK当中。如果你尚未安装这个扩展类库的话,你应该从Sun公司的Java站点下载这个类库并将其安装在你的系统上。在所下载的包里面包括一个安装说明,如果你没有正确安装这个类库及其运行环境的话,运行这个程序的时候你会找不到串口。

正确安装Java Communication API并将上述程序编译通过以后,你可以按如下方法测试这个程序。如果你只有一台机器,你可以利用一条RS-232电缆将COM1和COM2连接起来,在COM1上运行SerialExample,在COM2上运行Windows提供的超级终端程序。如果你有两台机器的话,你可以利用一条RS-232电缆将两台机器的COM1(或者是COM2)连接起来,在一端运行例程,另外一端运行Windows提供的超级终端程序。如果有必要的话,可以对SerialExample中所声明的串口进行相应改动。

 

一、概述

随着手机的逐渐普及,它的主要业务之一“短信”的使用量也水涨船高。但使用手机发短信还有一些不方便的地方,如输入汉字慢、功能有限、手机的存储容量有限等。因此,近几年开始兴起使用电脑向手机发送短信。使用电脑发送短信的方法很多,如通过126、新浪等短信平台通过注册自己的手机号,就可以通过电脑发短信了。但这样做有一些不足,如发短信时电脑必须联入Internet,而且一般使用电脑发短信的费用要比直接使用手机发短信的费用高一些。

当然,还有其它方法发短信。如象126那样租网通或移动的短信服务器,然后通过短信服务器发送短信。这种方式虽然很直接,但是价格昂贵,不是一般人可以承受的(只有象126、新浪这样的服务网站才能用得起)。

最省钱的方法就是到网上去找一个可以免费发短信的软件,我以前使用过一个叫“灵犀机器人”的软件,它们可以有限地免费发送短信,但好象现在也都收费了。这种软件现在越来越少了。

那么是否有折衷的方法,使发短信的费用和手机一样,而且又可以象电脑一样方便地输入、保存、修改和查询短信呢?答案是肯定的,那就是通过数据线将手机和电脑连在一起,使用电脑控制手机发短信。而且这样做电脑无需联入Internet。

二、如何通过数据线控制手机发短信

一般手机的数据线可以通过COM口或USB口和计算机进行通讯。在本文中我们采用带有COM口的数据线,因为控制COM口比控制USB口更容易、更简单。通过Java和COM口进行通讯有很多方法,可以在Java中直接调用系统API,也可以采用第三方的Java库(这些库在底层也是通过调用系统API实现的)。在本文中我们采用第二种方法,也就是通过第三方的Java库来和COM口进行通讯。在网上这种库很多,在本文中介绍了如何使用Sun的Java通讯API和手机进行通讯,可以在Sun的官方网站下载Java通讯API库 。

 

 

 

-------------------------------接口-------------------------------

package anole.server.common;

import java.util.List;

public interface CommService {

    /**
     * 设置端口名称
     * @param portName 需要操作的端口名称
     */
    public void setPortName(String portName);

    /**
     * 设置读取超时时长
     * @param timeOut 读取超时时长
     */
    public void setTimeOut(long timeOut);

    /**
     * 设置传输速率
     * @param rate 传输速率
     */
    public void setRate(int rate);

    /**
     * 设置数据位
     * @param dataBit 数据位
     */
    public void setDataBit(int dataBit);

    /**
     * 设置停止位
     * @param stopBit 停止位
     */
    public void setStopBit(int stopBit);

    /**
     * 设置奇偶校验
     * @param parity 奇偶校验
     */
    public void setParity(int parity);

    /**
     * 获取本机串口列表
     * @return 本机串口名列表
     */
    public List<String> listPortName();

    /**
     * 发送命令到通信端口
     * @param comCode 发送的字符 此字符为16进制字符串,从00到FF
     * @param splitFlag 分割符
     * @return   0 发送成功 1 命令非十六进制 2 IO操作异常
     */
    public int send(String comCode, String splitFlag);

    /**
     * 发送命令到通信端口
     * @param comCode 发送的字符 此字符为16进制字符串,从00到FF
     * @return  0 发送成功 1 命令非十六进制 2 IO操作异常
     */
    public int send(String comCode);

    /**
     * 发送命令到通信端口
     * @param comCode 发送的数组的数值为0到255
     * @return   0 发送成功 1 命令非十六进制 2 IO操作异常
     */
    public int send(char[] comCode);

    /**
     * 发送命令到通信端口
     * @param b 发送的数据
     * @return  0 发送成功  2 IO操作异常
     */
    public int send(byte[] b);

    /**
     * 读取通信端口数据
     * @param endFlag 当有结果返回是 数据结束符
     * @param timeout 等待数据超时时间 以毫秒计算
     * @return 通信端口返回数据
     */
    public byte[] recevie(String endFlag, long timeout);

    /**
     * 读取通信端口数据
     * @param dataLength 当有数据返回,返回数据长度
     * @param timeout 等待数据超时时间 以毫秒计算
     * @return 通信端口返回数据
     */
    public byte[] recevie(int dataLength, long timeout);

    /**
     * 读取通信端口数据
     * @return 通信端口返回数据
     */
    public byte[] recevie();

    /**
     * 关闭端口
     * @return 0:正常开启  1:开启失败;2;端口设置错误;3;未找到端口;4;端口被占用 5 IO开启异常
     */
    public int open();

    /**
     * 关闭端口
     * @return 0:正常关闭  1:关闭失败
     */
    public int close();
}
------------------------自定义异常-------------------------------

package anole.server.common.util;

public class NoByteException extends Throwable {
    private static final long serialVersionUID = 1L;

    NoByteException(String string) {
        super();
    }

}

---------------------------数据转换-------------------------------------------

 

package anole.server.common.util;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;

public class CodeConvert {

    /**
     * 十六进制字符与数字转换表。
     */
    public static final String HEX_STRING = "0123456789abcdef";

    /**
     * 将十六进制字符转换成数字。
     * @param c 将转换的字符。
     * @return 转换成的数字。
     * @throws NotByteException
     */
    private static int StringHexToInt(char c) throws NoByteException {
        char cc = Character.toLowerCase(c);
        int value = HEX_STRING.indexOf(cc);
        if (value == -1) {
            throw new NoByteException("必须是十六进制字符");
        }
        return value;
    }

    /**
     * 将字节数组转换成十六进制的字符表现形式。
     * @param b 字节数组。
     * @return 十六进制字符表现形式。
     */
    public static String ByteToString(byte[] b) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < b.length; i++) {
            StringBuilder append = sb.append(HEX_STRING.charAt((b[i] >> 4) & 0xf)).append(HEX_STRING.charAt((b[i]) & 0xf));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 将字节数组转换成十六进制的字符表现形式。
     * @param b 字节数组。
     * @param splitString 字符串分割符
     * @return 十六进制字符表现形式。
     */
    public static String ByteToString(byte[] b, String splitString) {
        StringBuilder sb = new StringBuilder();
        int bLen = b.length;
        sb.append(HEX_STRING.charAt((b[0] >> 4) & 0xf)).append(HEX_STRING.charAt((b[0]) & 0xf));
        for (int i = 1; i < b.length; i++) {
            sb.append(splitString);
            sb.append(HEX_STRING.charAt((b[i] >> 4) & 0xf)).append(HEX_STRING.charAt((b[i]) & 0xf));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 将十六进制字符串转换成字节数组。
     * @param str 需要转换的字符串。
     * @return 字节数组。
     * @throws NoByteException 不是byte类型字符串
     */
    public static byte[] StringToByte(String str) throws NoByteException {
        char[] c = str.toCharArray();
        byte[] b = new byte[c.length / 2];
        int m = 0;
        for (int i = 0; i < c.length; i += 2) {
            int j = StringHexToInt(c[i]) << 4;
            int k = StringHexToInt(c[i + 1]);
            b[m] = (byte) (j | k);
            m++;
        }
        return b;
    }

    public static byte[] CharsToBytes(char[] chars) {
        Charset cs = Charset.forName("UTF-8");
        CharBuffer cb = CharBuffer.allocate(chars.length);
        cb.put(chars);
        cb.flip();
        ByteBuffer bb = cs.encode(cb);
        return bb.array();
    }

    public static char[] BytesToChars(byte[] bytes) {
        Charset cs = Charset.forName("UTF-8");
        ByteBuffer bb = ByteBuffer.allocate(bytes.length);
        bb.put(bytes);
        bb.flip();
        CharBuffer cb = cs.decode(bb);
        return cb.array();
    }

    /**
     * 将十六进制字符串转换成字节数组。
     * @param str 需要转换的字符串。
     * @param splitString 字符串分割符
     * @return 字节数组。
     * @throws NoByteException 不是byte类型字符串
     */
    public static byte[] StringToByte(String str, String splitString) throws NoByteException {
        return StringToByte(str.replace(splitString, ""));
    }
}
--------------------------------串口监听器----------------------------------------------------

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package anole.server.common.serialService;

import anole.server.common.util.CodeConvert;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.TooManyListenersException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Administrator
 */
public class SerialHandler implements SerialPortEventListener {

    private SerialPort serialPort;
    private long timeOut = 4000;
    private String portName;
    private OutputStream os;
    private InputStream is;
    private int rate = 9600;
    private int dataBit = SerialPort.DATABITS_8;
    private int stopBit = SerialPort.STOPBITS_1;
    private int parity = SerialPort.PARITY_NONE;
    private boolean isComplate = true;
    private byte[] readBuffer;
    private int bufferSize = 10240;

    public synchronized void setReadBuffer(byte[] readBuffer) {
        isComplate = false;
        while (isComplate) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        byte[] temp = readBuffer;
        if (this.readBuffer != null) {
            if (temp != null) {
                int rLen = this.readBuffer.length;
                int tLen = temp.length;
                byte[] in = new byte[rLen + tLen];
                System.arraycopy(this.readBuffer, 0, in, 0, rLen);
                System.arraycopy(temp, 0, in, rLen, tLen);
            }
        } else {
            this.readBuffer = temp;
        }
        isComplate = true;
        this.notifyAll();
    }

    public synchronized byte[] getReadBuffer() {
        while (!isComplate) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        byte[] temp = this.readBuffer;
        this.readBuffer = null;
        this.notifyAll();
        return temp;
    }

    public void setTimeOut(long timeOut) {
        this.timeOut = timeOut;
    }

    public void setBufferSize(int bufferSize) {
        this.bufferSize = bufferSize;
    }

    public int send(byte[] bs) {
        int flag = 0;
        try {
            os.write(bs);
            os.flush();
        } catch (IOException e) {
            flag = 2;
        }
        return flag;
    }

    public byte[] recevie(int dataLength, long timeOut) {
        this.timeOut = timeOut;
        byte[] bs = null;
        byte[] temp = null;
        int cLen = 0;
        int tLen = 0;
        int lLen = 0;
        long curr = new Date().getTime();
        while ((bs == null || bs.length < dataLength) && new Date().getTime() - curr < this.timeOut) {
            try {
                //获取缓冲区数据
                temp = null;//临时数据清空
                temp = getReadBuffer();
                tLen = 0;
                if (temp != null) {
                    System.out.println("临时数据:" + CodeConvert.ByteToString(temp));
                    tLen = temp.length;
                }
                //计算要返回数据长度
                if (bs != null) {
                    cLen = bs.length;
                }
                System.out.println("当前数据长度:" + cLen);
                //若 当前要返回数据长度 加上 临时数据长度 大于 要求的数据长度
                if ((cLen + tLen) > dataLength) {
                    System.out.println("长度超出" + (cLen + tLen));
                    //计算需要拷贝的数据长度
                    lLen = dataLength - cLen;
                    byte[] in = new byte[cLen + lLen];
                    System.arraycopy(bs, 0, in, 0, cLen);
                    System.arraycopy(temp, 0, in, cLen, lLen);
                    bs = in;
                    break;
                } //将临时数据追加到要返回的数据之后
                else if (temp != null && bs != null) {
                    //将临时数组添加到当前数组之后
                    System.out.println("附加数据");
                    byte[] in = new byte[cLen + tLen];
                    System.arraycopy(bs, 0, in, 0, cLen);
                    System.arraycopy(temp, 0, in, cLen, tLen);
                    bs = in;
                } else if (temp != null && bs == null) {
                    System.out.println("初始化数组");
                    bs = temp;
                }
                //若 数据长度不够 则 线程睡眠1000ms
                if (bs != null) {
                    cLen = bs.length;
                }
                if (cLen < dataLength) {
                    System.out.println("线程睡眠1000ms");
                    Thread.sleep(1000);
                }

            } catch (InterruptedException ex) {
                Logger.getLogger(SerialHandler.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        int aLen = bs.length;
        bs = Arrays.copyOf(bs, aLen);
        System.out.println("当前返回数据:" + CodeConvert.ByteToString(bs));
        return bs;
    }

    public byte[] recevie(byte endFlag, long timeOut) {
        this.timeOut = timeOut;
        byte[] bs = null;
        byte[] temp = null;
        int cLen = 0;
        int tLen = 0;
        int lLen = 0;
        int iIndex = -1;
        long curr = new Date().getTime();
        while (iIndex < 0 && new Date().getTime() - curr < this.timeOut) {
            try {
                //获取缓冲区数据
                temp = null;
                tLen = 0;
                temp = getReadBuffer();
                if (temp != null) {
                    System.out.println("临时数据:" + CodeConvert.ByteToString(temp));
                    tLen = temp.length;
                }

                //计算要返回数据长度
                if (bs != null) {
                    cLen = bs.length;
                }

                System.out.println("临时数据长度:" + tLen);

                for (int i = 0; i < tLen; i++) {
                    if (temp[i] == endFlag) {
                        iIndex = i + 1;
                    }
                }
                System.out.println("位置:" + iIndex);

                lLen = tLen + cLen;

                //若 临时数据中未发现结束符
                if (iIndex < 0) {
                    //扩容 追加到要返回的数据之后
                    byte[] in = new byte[lLen];
                    if (temp != null && bs != null) {
                        System.arraycopy(bs, 0, in, 0, cLen);
                        System.arraycopy(temp, 0, in, cLen, tLen);
                        bs = in;

                    } else if (temp != null && bs == null) {
                        bs = temp;
                    }
                    Thread.sleep(1000);
                } else {
                    if (temp != null && bs != null) {//非第一个包中就包含了结束符号
                        //扩容 追加到要返回的数据之后
                        byte[] in = new byte[lLen];
                        System.arraycopy(bs, 0, in, 0, cLen);
                        System.arraycopy(temp, 0, in, cLen, iIndex);
                        bs = in;
                        break;
                    } else if (temp != null && bs == null) {//第一个包中就包含了结束符号
                        bs = Arrays.copyOf(temp, iIndex);
                        break;
                    }
                }
            } catch (InterruptedException ex) {
                Logger.getLogger(SerialHandler.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        int aLen = new String(bs).trim().length();
        bs = Arrays.copyOf(bs, aLen);
        System.out.println("返回的数据:" + CodeConvert.ByteToString(bs));
        return bs;
    }

    public byte[] recevie() {
        return readBuffer;
    }

    ;

    @Override
    public void serialEvent(SerialPortEvent event) {
        switch (event.getEventType()) {
            case SerialPortEvent.BI:
            case SerialPortEvent.OE:
            case SerialPortEvent.FE:
            case SerialPortEvent.PE:
            case SerialPortEvent.CD:
            case SerialPortEvent.CTS:
            case SerialPortEvent.DSR:
            case SerialPortEvent.RI:
            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
                break;
            case SerialPortEvent.DATA_AVAILABLE:
                try {
                    int numBytes = 0;
                    byte[] bs = new byte[bufferSize];
                    while (is.available() > 0) {
                        //将数据读入readBuffer中。 
                        numBytes = is.read(bs);
                    }

                    setReadBuffer(Arrays.copyOf(bs, numBytes));
                } catch (IOException e) {
                    Logger.getLogger(SerialHandler.class.getName()).log(Level.SEVERE, null, e);
                }
                break;
        }
    }

    public int open() {
        int flag = 0;
        CommPortIdentifier portId = null;
        try {
            portId = CommPortIdentifier.getPortIdentifier(portName);
        } catch (NoSuchPortException e) {
            flag = 3;//未找到端口
            Logger.getLogger(SerialHandler.class.getName()).log(Level.SEVERE, null, e);
        }
        try {
            serialPort = (SerialPort) portId.open(SerialServiceImpl.class.getSimpleName(), 2000);
        } catch (PortInUseException e) {
            flag = 4;//端口被占用
            Logger.getLogger(SerialHandler.class.getName()).log(Level.SEVERE, null, e);
        }
        try {
            serialPort.setSerialPortParams(rate, dataBit, stopBit, parity);
            System.out.println("开启端口:portName-" + portName + ":::rate-" + rate + ":::dataBit-" + dataBit + ":::stopBit-" + stopBit + ":::parity-" + parity);
            //serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
        } catch (UnsupportedCommOperationException e) {
            flag = 2;//端口设置错误
            Logger.getLogger(SerialHandler.class.getName()).log(Level.SEVERE, null, e);
        }
        try {
            is = serialPort.getInputStream();
            os = serialPort.getOutputStream();
        } catch (IOException e) {
            flag = 5;//IO开启异常
            Logger.getLogger(SerialHandler.class.getName()).log(Level.SEVERE, null, e);
        }
        try {
            serialPort.addEventListener(this); // 给当前串口天加一个监听器
        } catch (TooManyListenersException e) {
            flag = 6;//监听器添加异常
            Logger.getLogger(SerialHandler.class.getName()).log(Level.SEVERE, null, e);
        }
        serialPort.notifyOnDataAvailable(true); // 当有数据时通知 
        return flag;
    }

    public int close() {
        int flag = 0;
        try {
            if (is != null) {
                is.close();
            }
            if (os != null) {
                os.close();
            }
            serialPort.close();
        } catch (IOException e) {
            flag = 1;
        }
        return flag;
    }

    public List<String> getListPortName() {
        List<String> listPortName = new ArrayList<String>();
        CommPortIdentifier portId;
        @SuppressWarnings("unchecked")
        Enumeration<CommPortIdentifier> en = CommPortIdentifier.getPortIdentifiers();
        while (en.hasMoreElements()) {
            portId = en.nextElement();
            if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                listPortName.add(portId.getName());
            }
        }
        return listPortName;
    }

    public void setRate(int rate) {
        this.rate = rate;
    }

    public void setDataBit(int dataBit) {
        this.dataBit = dataBit;
    }

    public void setStopBit(int stopBit) {
        this.stopBit = stopBit;
    }

    public void setParity(int parity) {
        this.parity = parity;
    }

    public void setPortName(String portName) {
        this.portName = portName;
    }
}
----------------------------接口的实现------------------------------------------------

package anole.server.common.serialService;

import anole.server.common.CommService;
import anole.server.common.util.CodeConvert;
import anole.server.common.util.NoByteException;
import java.util.List;

public class SerialServiceImpl implements CommService {

    private SerialHandler serialHandler = new SerialHandler();
   
    public SerialServiceImpl() {

    }
    public void setTimeOut(long timeOut){
        serialHandler.setTimeOut(timeOut);
    }
    public SerialServiceImpl(String portName) {
        serialHandler.setPortName(portName);
    }

    public SerialServiceImpl(String portName, int rate) {
        serialHandler.setRate(rate);
        serialHandler.setPortName(portName);
    }

    @Override
    public void setPortName(String portName) {
        serialHandler.setPortName(portName);
    }

    @Override
    public void setRate(int rate) {
        serialHandler.setRate(rate);
    }

    @Override
    public void setDataBit(int dataBit) {

        serialHandler.setDataBit(dataBit);
    }

    @Override
    public void setStopBit(int stopBit) {

        serialHandler.setStopBit(stopBit);
    }

    @Override
    public void setParity(int parity) {

        serialHandler.setParity(parity);
    }

    @Override
    public List<String> listPortName() {

        return serialHandler.getListPortName();
    }

    @Override
    public int send(String comCode, String splitFlag) {
        int flag = 0;
        byte[] bs = null;
        try {
            bs = CodeConvert.StringToByte(comCode, splitFlag);
        } catch (NoByteException ne) {
            flag = 1;
        }
        flag = send(bs);
        return flag;
    }

    @Override
    public int send(String comCode) {
        int flag = 0;
        byte[] bs = null;
        try {
            bs = CodeConvert.StringToByte(comCode);
        } catch (NoByteException ne) {
            flag = 1;
        }
        flag = send(bs);
        return flag;
    }

    @Override
    public int send(char[] comCode) {
        int flag = 0;
        byte[] bs = null;
        bs = CodeConvert.CharsToBytes(comCode);
        flag = send(bs);
        return flag;
    }

    @Override
    public int send(byte[] b) {
        int flag = 0;
        serialHandler.send(b);
        return flag;
    }

    @Override
    public byte[] recevie(String endFlag, long timeout) {
        byte[] bs = null;
        byte ef=0;
        try {
            ef = CodeConvert.StringToByte(endFlag)[0];
        } catch (NoByteException ne) {
            int flag = 1;
        }

        serialHandler.recevie(ef, timeout);
        return bs;
    }

    @Override
    public byte[] recevie(int dataLength, long timeout) {
        byte[] bs = null;
        serialHandler.recevie(dataLength, timeout);
        return bs;
    }

    @Override
    public byte[] recevie() {
        byte[] bs = null;
        bs = serialHandler.recevie();
        return bs;
    }

    @Override
    public int open() {
        int flag = 0;
        flag = serialHandler.open();
        return flag;
    }

    @Override
    public int close() {
        int flag = 0;
        serialHandler.close();
        return flag;
    }
}

------测试类-----------


package mm;
import anole.server.common.CommService;
import anole.server.common.serialService.SerialServiceImpl;
import java.util.Iterator;
import java.util.List;

public class Mm {

    public static void main(String[] args) {
       CommService cs=new SerialServiceImpl();
       List<String> ll=cs.listPortName();
       Iterator<String> iter=ll.iterator();
       while(iter.hasNext()){
           System.out.println(iter.next());
       }
       cs.setPortName("COM3");
       cs.open();
       cs.send("AD010D");
       cs.recevie(35, 12000);
       cs.close();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值