android 蓝牙socket(spp)用法

1 综述

蓝牙socket连接必须实现服务器端和客户端的机制。一个设备必须打开一个Server Socket,而另一个必须发起连接(使用服务器端设备的MAC地址发起连接)。当服务器端和客户端在同一个RFCOMM信道上都有一个BluetoothSocket时,则两端就建立了连接。此刻,每个设备都能获得一个输入输出流,进行数据传输。下面分别介绍服务器端和客户端的实现。

 

2 服务器端

服务器端有两个线程,一个是accept线程负责监听来自client的连接请求,一个是connected线程负责与client 进行数据传输。
accept线程首先创建一个server socket,通过调用listenUsingRfcommWithServiceRecord(String, UUID)方法得到一个BluetoothServerSocket对象。[注: 用此方法listenUsingRfcommWithServiceRecord(String, UUID)创建的socket,需要双方进行配对确认,之后双方在一个加密且鉴权过的通信信道上通信。而用listenUsingInsecureRfcommWithServiceRecord(String, UUID)创建的socket不需要双方进行配对确认,通信信道上没有鉴权过的link key,容易受到MITM(Man-in-the-Middle Attack)攻击。Bluetooth 2.1之前的设备link key不被加密,Bluetooth 2.1之后的设备link key会被加密。server端创建的socket是否需要进行配对确认还需要client端创建与之对应的socket配对使用,如createRfcommSocketToServiceRecord(UUID)和 createInsecureRfcommSocketToServiceRecord(UUID) ]。字符串参数为服务的标识名称,名字是任意的,可以简单地是应用程序的名称。UUID用来标识服务,当客户端试图连接本设备时,它必须携带一个相同的UUID,连接才会被接受。

        public AcceptThread(boolean isAndroid) {
            BluetoothServerSocket tmp = null;
           // Create a new listening server socket
            try {
                if(isAndroid)
                    tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE, UUID_ANDROID_DEVICE);
                else
                   tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE, UUID_OTHER_DEVICE);
            } catch (IOException e) { }
            mmServerSocket = tmp;
        }

通过调用accept()来侦听连接请求。这是一个阻塞线程,直到接收一个连接或者产生异常才会返回。当客户端携带的UUID与侦听它Socket注册的UUID匹配,连接请求才会被接受。如果成功,accept()将返回一个BluetoothSocket对象。accept()调用不应该在主Activity UI线程中进行,因为它是个阻塞线程,会妨碍应用中其他的交互。

        public void run() {
            setName("AcceptThread" + mSocketType);
            BluetoothSocket socket = null;
            // Listen to the server socket if we're not connected
            while (mState != BluetoothState.STATE_CONNECTED && isRunning) {
                try {
                    // This is a blocking call and will only return on a
                    // successful connection or an exception
                    socket = mmServerSocket.accept();
                } catch (IOException e) {
                    break;
                }
                // If a connection was accepted
                if (socket != null) {
                    synchronized (BluetoothService.this) {
                        switch (mState) {
                        case BluetoothState.STATE_LISTEN:
                        case BluetoothState.STATE_CONNECTING:
                            // Situation normal. Start the connected thread.
                            connected(socket, socket.getRemoteDevice(),
                                    mSocketType);
                            break;
                        case BluetoothState.STATE_NONE:
                        case BluetoothState.STATE_CONNECTED:
                            // Either not ready or already connected. Terminate new socket.
                            try {
                                socket.close();
                            } catch (IOException e) { }
                            break;
                        }
                    }
                }
            }
        }

除非需要再接受另外的连接,否则的话调用close()释放Server Socket及其资源,但不会关闭accept()返回的BluetoothSocket对象。与TCP/IP不同,RFCOMM同一时刻一个信道只允许一个客户端连接,因此大多是情况下意味着在BluetoothServerSocket接受一个连接请求后应该立即调用close()。

 

3 客户端

客户端也有两个线程,一个是connect线程负责发起与server的连接,一个是connected线程负责与server进行数据传输。

为了实现与远程服务器设备的连接,必须首先获得一个代表远程设备BluetoothDevice的对象。然后使用BluetoothDevice对象来获取一个BluetoothSocket以实现连接。BluetoothDevice调用方法createRfcommSocketToServiceRecord(UUID)获取一个BluetoothSocket对象。
        public ConnectThread(BluetoothDevice device) {
            mmDevice = device;
            BluetoothSocket tmp = null;
            // Get a BluetoothSocket for a connection with the
            // given BluetoothDevice
            try {
                if(BluetoothService.this.isAndroid)
                    tmp = device.createInsecureRfcommSocketToServiceRecord(UUID_ANDROID_DEVICE);
                else
                    tmp = device.createInsecureRfcommSocketToServiceRecord(UUID_OTHER_DEVICE);
            } catch (IOException e) { }
            mmSocket = tmp;
        }
调用connect()建立连接。当调用这个方法的时候,系统会在远程设备上完成一个SDP查找来匹配UUID。如果查找成功并且远程设备接受连接,就共享RFCOMM信道,connect()会返回。这个方法也是一个阻塞的调用。如果连接失败或者超时都会抛出异常。
        public void run() {
            // Always cancel discovery because it will slow down a connection
            mAdapter.cancelDiscovery();
            // Make a connection to the BluetoothSocket
            try {
                // This is a blocking call and will only return on a
                // successful connection or an exception
                mmSocket.connect();
            } catch (IOException e) {
                // Close the socket
                try {
                    mmSocket.close();
                } catch (IOException e2) { }
                connectionFailed();
                return;
            }
            // Reset the ConnectThread because we're done
            synchronized (BluetoothService.this) {
                mConnectThread = null;
            }
            // Start the connected thread
            connected(mmSocket, mmDevice, mSocketType);
        }
 

4 connected线程

如果两个设备成功建立了连接,两个设备都进入connected状态,各自会有一个BluetoothSocket,此时可以进行设备间通信了。使用BluetoothSocket,传输任何数据通常来说都比较容易,通常如下进行:
+分别使用getInputStream()和getOutputStream()获取输入输出流来处理传输。
+调用read(byte[])和write(byte[])来实现数据读写。
需要用一个专门的connected线程来实现流的读写,因为方法read(byte[])和write(byte[])都是阻塞调用。read(byte[])会阻塞,直到流中有数据可读。write(byte[])虽然通常不会阻塞,但是如果远程设备调用read(byte[])不够快而导致中间缓冲区满,它也可能阻塞。所以线程中的主循环应该用于读取InputStream。线程中也应该有单独的方法用来完成写OutputStream。
//获得输入输出流
        public ConnectedThread(BluetoothSocket socket, String socketType) {
            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;
            // Get the BluetoothSocket input and output streams
            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) { }
            mmInStream = tmpIn;
            mmOutStream = tmpOut;
        }
 
//线程的主循环用于读取数据
        public void run() {
            byte[] buffer;
            ArrayList<Integer> arr_byte = new ArrayList<Integer>();
            // Keep listening to the InputStream while connected
            while (true) {
                try {
                    int data = mmInStream.read();
                    if(data == 0x0A) { 
                    } else if(data == 0x0D) {
                        buffer = new byte[arr_byte.size()];
                        for(int i = 0 ; i < arr_byte.size() ; i++) {
                            buffer[i] = arr_byte.get(i).byteValue();
                        }
                        // Send the obtained bytes to the UI Activity
                        mHandler.obtainMessage(BluetoothState.MESSAGE_READ
                                , buffer.length, -1, buffer).sendToTarget();
                        arr_byte = new ArrayList<Integer>();
                    } else {
                        arr_byte.add(data);
                    }
                } catch (IOException e) {
                    connectionLost();
                    // Start the service over to restart listening mode
                    BluetoothService.this.start(BluetoothService.this.isAndroid);
                    break;
                }
            }
        }
 
//用来写数据的函数
        public void write(byte[] buffer) {
            try {
                mmOutStream.write(buffer);
                // Share the sent message back to the UI Activity
                mHandler.obtainMessage(BluetoothState.MESSAGE_WRITE
                        , -1, -1, buffer).sendToTarget();
            } catch (IOException e) { }
        }
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值