蓝牙开发之从手机走向PC【2】——手机与手机之间的通信实现

时间:2021-07-14 15:26:03

     上篇文章讲述了开发环境的搭建和一些相关知识的介绍,这篇文章准备介绍下怎样实现手机和手机之间通过蓝牙实现互联通信的程序,然后接下来的日子可能会写个简单的通过蓝牙互联的手机小游戏(其他的事情比较多,加上笔者比较懒,呵呵,见谅~)。

     这个小程序时个C/S结构的,但是只有一个Jar包。运行程序后的首页会有一个二选一选项(server或者client),当你选择server后单击select按钮会进入服务器界面,单击setup按钮,那么便会开启服务器端的程序,并且循环监听来自客户端的蓝牙连接;而如果选择client选项,则会进入客户端界面,单击connect则会开始搜索周围的设备并遍历设备上的目标服务,如果搜索到服务的话则会连接上服务器,这时候你可以在文本框中输入信息并点击发送,服务器则会反馈相应的信息。

    呵呵,虽然实现的功能简单,但是想要做更复杂的应用,这一步还是必须得走的~先看下我的程序文件结构吧:

    ---core                                  //核心包名

        ---BlueMessage.java           //Midlet主类,程序入口

    ---components                      //组件包名

        ---MainForm.java               //起始主界面(在此选择客户端还是服务器端)

        ---BlueClient.java              //客户端界面,继承自Form,实现CommandListener接口

        ---BlueServer.java             //服务器端界面,继承自Form,实现CommandListener接口

    ---bluetooth

        ---BlueClientService.java    //封装了客户端蓝牙服务的类,实现Runnable和DiscoveryListener接口

        ---BlueServerService.java  //封装了服务器端蓝牙服务的类,实现Runnable接口

     

     好了,对于程序文件结构有了一定的了解后,来看下部分代码吧:

     BlueMessage.Java文件:

     /**

 * Midlet应用程序主类
 * 
@author  royen
 * 
@since  2010.1.24
 
*/
public   class  BlueMessage  extends  MIDlet {

    
public  BlueMessage() {
        MainForm form
= new  MainForm( this );
        Display.getDisplay(
this ).setCurrent(form);        
    }

    
/**
     * 退出应用程序
     
*/
    
public   void  ExitMidlet() {        
        
try {
            
this .destroyApp( true );        
        }
        
catch (Exception ex){
            System.out.println(
" occur exception  " + ex.getMessage());
        }
    }
    
    
/**
     * 导航到其他界面    
     * 
@param  dis
     
*/
    
public   void  NavigateTo(Displayable dis){
        Display.getDisplay(
this ).setCurrent(dis);
    }
    
    
protected   void  startApp()  throws  MIDletStateChangeException {        
    }    
    
protected   void  destroyApp( boolean  arg0)  throws  MIDletStateChangeException {  
    }

    protectedvoid pauseApp() {
        
// TODO Auto-generated method stub}

 

      该文件中在构造函数中指出了MainForm为初始界面,并提供了ExitMidlet和NavigateTo应用程序级的函数供调用。

 

       components包中的MainForm.java文件:       

/**
 * 程序主界面
 * 
@author  royen
 * 
@since  2010.1.24
 
*/
public   class  MainForm  extends  Form  implements  CommandListener{    
    
// 应用程序主类
     private  BlueMessage parent = null ;
    
    
// 选择项控件
     private  ChoiceGroup choiceGp = null ;

    
// 按钮控件
     private  Command cmdSelect = null ;;
    
private  Command cmdExit = null ;        
            
    
// 客户端和服务器端
     private  BlueClient client = null ;
    
private  BlueServer server = null ;
    
    
public  MainForm( BlueMessage parent ) {
        
super ( " type select " );    
   
        
this .parent = parent;
        
        FormLoad();        
    }

    
/**
     * 窗体加载初始化
     
*/
    
private   void  FormLoad(){
        
// 生成选择项控件
        choiceGp = new  ChoiceGroup( " select application type: " ,Choice.EXCLUSIVE);
        choiceGp.append(
" client " null );
        choiceGp.append(
" server " null );

        
// 生成按钮控件
        cmdSelect = new  Command( " select " ,Command.OK, 1 );
        cmdExit
= new  Command( " exit " ,Command.EXIT, 1 );

        
// 添加控件
         this .append(choiceGp);
        
this .addCommand(cmdSelect);
        
this .addCommand(cmdExit);
        
        
// 添加按键事件监听
         this .setCommandListener( this );
        
    }    
    
    
/**
     * 按钮事件处理函数
     
*/     
    
public   void  commandAction(Command cmd, Displayable dis){
        
// 点击退出按钮
         if (cmd == cmdExit){            
            parent.ExitMidlet();
        }
        
else   if (cmd == cmdSelect){   // 点击选择按钮
            
            
switch (choiceGp.getSelectedIndex()){
            
case   0 :
                client
= new  BlueClient(parent, this );
                parent.NavigateTo(client);
                
break ;
            
case   1 :
                server
= new  BlueServer(parent, this );
                parent.NavigateTo(server);
                
break ;
            
default :
                
break ;
            }
        }
            
    }
}

 

      Mainform类中主要提供Server和Client选项供用户选择,详细代码不解释,最后会给出完整的源程序下载~

 

      components包中的BlueClient.java文件内容:

      蓝牙开发之从手机走向PC【2】——手机与手机之间的通信实现代码

 

      components包中的BlueServer.java文件内容: 

      蓝牙开发之从手机走向PC【2】——手机与手机之间的通信实现代码

 

      以上两个类分别是客户端和服务器端的界面类,所有的逻辑操作都被放到bluetooth包中的对应的类中了,几个文件笔者都注释的比较翔实了,所以暂不解释。

 

      bluetooth包中的BlueServerService.java文件:      

/**
 * 实现服务器端蓝牙服务的类
 * 
@author  royen
 * 
@since  2010.1.25
 
*/
public   class  BlueServerService  implements  Runnable{
    
// 服务标示符
     private   final   static  UUID SERVER_UUID = new  UUID( " F0E0D0C0B0A000908070605040302010 " , false );
    
    
// 流连接通知器
     private  StreamConnectionNotifier notifier;
    
    
// 服务器端界面
     private  BlueServer serverForm;
    
    
// 服务记录
    ServiceRecord serviceRecord;    
    
    
public  BlueServerService(BlueServer frm){
        
this .serverForm = frm;
    }
    
    
/**
     * 开启服务线程
     
*/
    
public   void  run() {
        
boolean  btReady = false ;
        
try {
            
// 获取本地蓝牙设备
            LocalDevice localDevice = LocalDevice.getLocalDevice();
            
            
if ( ! localDevice.setDiscoverable(DiscoveryAgent.GIAC)){
                System.out.println(
" set discoveryMode failed~ " );
                
return  ;
            }
            
            notifier
= (StreamConnectionNotifier)Connector.open(getConnectionStr());
            
            serviceRecord
= localDevice.getRecord(notifier);
            
            btReady
= true ;
        }
        
catch (Exception ex){
            System.out.println(
" occur exception  " + ex.getMessage());
        }
        
        
if ( ! btReady){
            System.out.println(
" bluetooth init failed~ " );
            
return  ;
        }
        
        serverForm.appendInfo(
" service setup,waiting for connect... " );        
        
        
// 切换界面
        serverForm.changeForm();
        
        
while ( true ){
            StreamConnection conn
= null ;
            
try {
                conn
= notifier.acceptAndOpen();
                
            }
            
catch (Exception ex){
                System.out.println(
" occur exception when accept connection~ " );
                
continue ;
            }    
            
            
// 开启对连接的处理线程
             new  Thread( new  ProcessConnection(conn)).start();            
        }
    }    
    
    
/**
     * 获取连接字符串
     * 
@return
     
*/
    
private  String getConnectionStr(){
        StringBuffer sb
= new  StringBuffer( " btspp:// " );
        sb.append(
" localhost " ).append( " : " );
        sb.append(SERVER_UUID.toString());
        sb.append(
" ;name=BlueMessage " );
        sb.append(
" ;authorize=false " );
        
return  sb.toString();
    }    
    
    
/**
     * 处理客户端连接的线程
     * 
@author  royen
     * 
@since  2010.1.25
     
*/
    
private   class  ProcessConnection  implements  Runnable{
        
// 连接流
         private  StreamConnection conn = null ;
        
        
public  ProcessConnection(StreamConnection conn){
            
this .conn = conn;
        }
        
        
public   void  run() {            
            
try {
                String inputStr
= readInputString(conn);

                serverForm.appendInfo(
" recive message from client:  " + inputStr);

                String outputString;
                
                
if (inputStr.startsWith( " connect... " )){
                    outputString
= " welcome... " ;
                }
                
else {
                    
// 生成响应
                    outputString = " server echo  " + inputStr;
                }
                
                sendOutputString(outputString,conn);
                
                conn.close();

            }
            
catch (Exception ex){
                System.out.println(
" occur exception ,message is  " + ex.getMessage());
            }
        }
        
        
/**
         * 读取接受的数据
         * 
@param  conn
         * 
@return
         
*/
        
private  String readInputString(StreamConnection conn){            
            
try {
                DataInputStream dis
= conn.openDataInputStream();
                
                String msg
= dis.readUTF();
                
                dis.close();
                
                
return  msg;
            }
            
catch (Exception ex){
                System.out.println(
" occur exception when read data~ " );
                
return  ex.getMessage();
            }            
        }
        
        
/**
         * 发送反馈信息
         * 
@param  msg
         * 
@param  conn
         
*/
        
private   void  sendOutputString(String msg,StreamConnection conn){
            
try {
                DataOutputStream dos
= conn.openDataOutputStream();
                
                dos.writeUTF(msg);
                
                dos.close();
            }
            
catch (Exception ex){
                System.out.println(
" occur exception when send data~ " );
            }
        }
    }
}

 

       在该文件中需要解释下的是SERVER_UUID,这个是全球用户唯一标示符,在蓝牙服务中我们服务器端其实就是将带有这一唯一标识的服务发布出去,而客户端则是根据这个UUID来在设备中搜索这一服务的。其分为长短标识,短标识说明采用的链接协议,长标识则代表服务的标识。其他的笔者都有比较详细的注释,这儿不再赘述。

        bluetooth包中的BlueClientService.java文件:

/**
 * 实现客户端蓝牙服务的类
 * 
@author  royen
 * 
@since  2010.1.24
 
*/
public   class  BlueClientService  implements  Runnable,DiscoveryListener{    
    
// 目标服务标识
     private   final   static  UUID TARGET_UUID = new  UUID( " F0E0D0C0B0A000908070605040302010 " , false );
    
    
// 存储发现的设备和服务的集合
     private  Vector devices = new  Vector();
    
private  Vector records = new  Vector();
    
    
// 存储服务属性的集合
     private  UUID[] uuidSet = null ;
    
    
// 蓝牙发现代理类
     private  DiscoveryAgent discoveryAgent = null ;
    
    
// 客户端界面
     private  BlueClient clientForm;
    
    
// 服务搜索的事务id集合
     private   int [] transIDs;
    
    
// 存活的服务索引
     private   int  activeIndex =- 1 ;
    
    
public  BlueClientService(BlueClient frm){
        
this .clientForm = frm;
    }    
    
    
public   synchronized   void  run() {
        
// 发现设备和服务的过程中,给用户以Gauge 
        Gauge g = new  Gauge( null , false ,Gauge.INDEFINITE,Gauge.CONTINUOUS_RUNNING);    
        clientForm.append(g);
        
        
// 如果初始化失败
         if ( ! initLocalDevice()){
            clientForm.appendInfo(
" bluetooth init failed~ " );
            
return ;
        }
        
        
// 生成服务和属性的全球唯一标示符
        uuidSet = new  UUID[ 2 ];
        uuidSet[
0 ] = new  UUID( 0x1101 );     // 0x1101表示 采用btspp协议
        uuidSet[ 1 ] = TARGET_UUID;          // 目标服务标示
        
        
try {
            discoveryAgent.startInquiry(DiscoveryAgent.GIAC, 
this );
        }
        
catch (Exception ex){
            clientForm.appendInfo(ex.getMessage());
            
return ;
        }
        
        
try {
            
// 阻塞,由inquiryCompleted函数回调唤醒
            wait();
        } 
        
catch (InterruptedException ex){
            clientForm.appendInfo(ex.getMessage());
            
return ;
        }
        
        
// 添加显示信息
        clientForm.appendInfo( " search device completed,find  " + devices.size() + "  devices~ " );
        clientForm.appendInfo(
" now begin to search service... " );        
        
        transIDs
= new   int [devices.size()];
        
        
// 遍历开启目标服务的蓝牙设备
         for ( int  i = 0 ;i < devices.size(); i ++  ){
            RemoteDevice rd
= (RemoteDevice)devices.elementAt(i);
            
try {
                
// 记录每一次服务搜索的事务ID
                transIDs[i] = discoveryAgent.searchServices( null , uuidSet, rd,  this );
            }
            
catch (BluetoothStateException ex){
                
continue ;
            }
        }
        
        
try {
            
// 阻塞,由serviceSearchCompleted函数回调唤醒
            wait();
        }
        
catch (InterruptedException ex){
            clientForm.appendInfo(ex.getMessage());
            
return ;
        }    
        
        
// 添加显示信息
        clientForm.appendInfo( " service search finished~,find  " + records.size() + "  services " );
        
        
if (records.size() > 0 ){
            
// 搜索到服务,改变客户端界面
            clientForm.changeForm();
        }    
        
        
// 获取存活的服务索引
        activeIndex = getActiveService();
        
    }        
    
    
/**
     * 获取存活的服务索引
     * 
@return
     
*/
    
private   int  getActiveService(){         
        
for ( int  i = 0 ;i < records.size();i ++ ){
            ServiceRecord sr
= (ServiceRecord)records.elementAt(i);
            
if (accessService(sr, " connect... " )){
                
return  i;                
            }
        }        
        
return   - 1 ;
    }
    
    
/**
     * 发送信息到服务器
     * 
@param  msg
     
*/
    
public   void  sendMsg(String msg){
        accessService((ServiceRecord)records.elementAt(activeIndex),msg);
    }
    
    
/**
     * 访问指定的服务
     * 
@param  sr
     * 
@return
     
*/
    
private   boolean  accessService(ServiceRecord sr,String msg){
        
boolean  result = false ;
        
try  {
            String url 
=  sr.getConnectionURL(
                    ServiceRecord.NOAUTHENTICATE_NOENCRYPT, 
false );
            StreamConnection conn 
=  (StreamConnection) Connector.open(url);
            DataOutputStream dos
= conn.openDataOutputStream();
            dos.writeUTF(msg);
            dos.close();
            DataInputStream dis
= conn.openDataInputStream();
            String echo
= dis.readUTF();
            dis.close();
            clientForm.appendInfo(
" echo from server: " + echo);
            result
= true ;

        }
        
catch  (IOException e) {
            System.out.println(
" exception here " );
        }
        
return  result;
    }
    
    
/**
     * 初始化本地蓝牙设备
     * 
@return
     
*/
    
private   boolean  initLocalDevice(){
        
boolean  isReady = false ;
        
try {
            LocalDevice localDevice
= LocalDevice.getLocalDevice();
            
            discoveryAgent
= localDevice.getDiscoveryAgent();
            
            
// 设置自身设备不可访问性
            localDevice.setDiscoverable(DiscoveryAgent.NOT_DISCOVERABLE);
            
            isReady
= true ;
        }
        
catch (Exception ex){
            isReady
= false ;
        }
        
return  isReady;
    }    
    
    
/**
     * 设备发现
     
*/
    
public   void  deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) {
        
if (devices.indexOf(btDevice) ==- 1 ){
            devices.addElement(btDevice);
        }
    }

    
/**
     * 搜索完毕
     
*/
    
public   void  inquiryCompleted( int  disType){
        
synchronized ( this ){
            notify();
        }
    }

    
/**
     * 服务发现
     
*/
    
public   void  servicesDiscovered( int  transID, ServiceRecord[] servRecord) {
        
for ( int  i = 0 ;i < servRecord.length;i ++ ){
            records.addElement(servRecord[i]);
        }
    }

    
/**
     * 服务搜索完毕
     
*/
    
public   void  serviceSearchCompleted( int  transID,  int  respCode) {
        
// 遍历,并标志搜索到的服务
         for ( int  i = 0 ;i < transIDs.length;i ++ ){
            
if (transIDs[i] == transID){
                transIDs[i]
=- 1 ;
            }
        }
        
        
// 如果对所有的设备的服务都搜索完毕
         boolean  finished = false ;
        
for ( int  i = 0 ;i < transIDs.length;i ++ ){
            
if (transIDs[i] ==- 1 ){
                finished
= true ;
                
break ;
            }
        }
        
        
if (finished){
            
synchronized ( this ){
                notify();
            }
        }
        
    }

}

 

      这个文件中需要解释的是BlueClientService类实现了DiscoveryListener接口,由于客户端需要进行周围蓝牙设备以及发布的服务搜索,所以必须实现DiscoveryListener接口的四个回调函数,分别为deviceDiscovered(搜索设备时候每发现一个设备时回调)、inquiryCompleted(搜索设备结束时回调)、servicesDiscovered(搜索服务时候回调)和serviceSearchCompleted(服务搜索完毕的时候回调)。

 

      以上便是一个简单版的手机蓝牙通信程序,下载完整的源程序包: /Files/royenhome/BlueMessage.rar ,程序可执行Jar包:/Files/royenhome/BlueMessage的Jar包.rar 

     有什么问题,欢迎一起学些交流~