11

走进Android WiFi P2P技术,一探设备间点对点通信实现细节

 1 month ago
source link: https://www.51cto.com/article/785319.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

走进Android WiFi P2P技术,一探设备间点对点通信实现细节

作者:Reathin 2024-04-03 08:25:52
WiFi P2P被广泛应用于移动设备之间的文件共享、游戏联机、音乐播放等应用场景中。相较于蓝牙,WiFi P2P具有更快的搜索速度和传输速度,以及更远的传输距离。而且只需要打开WiFi即可,不需要加入任何网络或AP,即可实现对等点连接通讯。对于需要在用户之间共享数据的应用,如多人游戏或照片共享非常有用。

WiFi P2P技术

WiFi P2P(Peer-to-Peer),也被称为WiFi Direct,是WiFi联盟发布的一个协议。允许无线网络中的设备在无需无线路由器的情况下相互连接,通过WiFi直接实现两台设备之间的无线点对点通信。原理与基于AP(接入点)的通信方式类似,支持P2P的设备可以在同一个小组内互传数据,实现同屏功能。

WiFi P2P被广泛应用于移动设备之间的文件共享、游戏联机、音乐播放等应用场景中。相较于蓝牙,WiFi P2P具有更快的搜索速度和传输速度,以及更远的传输距离。而且只需要打开WiFi即可,不需要加入任何网络或AP,即可实现对等点连接通讯。对于需要在用户之间共享数据的应用,如多人游戏或照片共享非常有用。

WiFi P2P也存在一些安全性问题,如用户隐私泄露、恶意软件和病毒传播,以及侵权和违法内容的传播。为了保护用户的安全和隐私,一些P2P网络提供了匿名化处理功能,使用安全搜索引擎,以及设置过滤器来阻止违法和侵权内容的共享。

Android WiFi P2P架构

在P2P架构中,定义了两种主要角色:P2P Group Owner(简称GO)和P2P Client(简称GC)。GO的作用类似于Infrastructure BSS中的AP(接入点),而GC的作用类似于Infrastructure BSS中的STA(站点)。当两台设备通过P2P连接后,会随机(也可以手动指定)指派其中一台设备为组拥有者(GO),相当于一台服务器,另一台设备为组成员(GC)。其他设备可以通过与GO设备连接加入组,但不能直接和GC设备连接。

图片
图片

在Android系统中,WiFi P2P功能是在Android 4.0及更高版本系统中加入的。它可以通过WifiP2pManager类进行实现,这个类提供了许多方法来扫描可用设备、建立P2P连接并传输数据等功能。开发者可以通过这些方法来实现设备之间的文件传输等操作。

在设备发现阶段,Android WiFi P2P使用Probe Request和Probe Response帧来交换设备信息。在2.4GHz的1、6、11频段上发送Probe Request帧,这几个频段被称为Social Channels。一旦Listen Channel选择好后,在整个P2P Discovery阶段就不能更改,用于快速发现周围的Group。

尽管Android WiFi P2P功能强大,目前在Android系统中只是内置了设备的搜索和链接功能,并没有像蓝牙那样有许多应用。在实际开发中,可能需要通过软件手段解决一些逻辑和权限问题。

Android应用WiFi P2P实现数据传输

在Android中,WiFi P2P可以通过WifiP2pManager类进行实现。开发者可以通过获取WifiP2pManager实例,并进行广播接受者的创建和注册,调用其他WiFi P2P的API,实现设备间的搜索、连接和数据传输等功能。例如,指定某一台设备为服务器,创建群组并等待客户端的连接请求,而客户端则可以主动搜索附近的设备并加入群组,向服务器发起文件传输请求。

图片
图片
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
获取WifiP2pManager和WifiP2pManager.Channel对象
mWifiP2pManager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager
mWifiP2pManager?.initialize(this, Looper.getMainLooper()) {
    Log.d(TAG, "Channel断开连接")
}
服务端创建群组
//服务端创建群组
mWifiP2pManager?.createGroup(mChannel, object : WifiP2pManager.ActionListener {
    override fun onSuccess() {
        Log.d(TAG, "创建群组成功")
    }

    override fun onFailure(reason: Int) {
        Log.w(TAG, "创建群组失败$reason")
    }
})
客户端搜索对等设备
//客户端搜索对等设备
mWifiP2pManager?.discoverPeers(mChannel, object : WifiP2pManager.ActionListener {
    override fun onSuccess() {
        Log.d(TAG, "搜索成功")
    }

    override fun onFailure(reason: Int) {
        Log.d(TAG, "搜索失败:$reason")
    }

})
//使用异步方法(推荐通过广播监听) 获取设备列表
mWifiP2pManager?.requestPeers(mChannel) {
    mDeviceList.addAll(it.deviceList)
    if (mDeviceList.isEmpty()) {
        //没有设备
        runOnUiThread { Toast.makeText(this, "没有发现设备", Toast.LENGTH_SHORT).show() }
    } else {
        //刷新列表
        runOnUiThread { mDeviceAdapter.notifyDataSetChanged() }
    }
}
val config = WifiP2pConfig().apply {
    this.deviceAddress = wifiP2pDevice.deviceAddress
    this.wps.setup = WpsInfo.PBC
}
mWifiP2pManager?.connect(mChannel, config, object : WifiP2pManager.ActionListener {
    override fun onSuccess() {
        Log.d(TAG, "连接成功")
    }

    override fun onFailure(reason: Int) {
        Log.w(TAG, "连接失败$reason")
    }

})
服务端创建Socket进行数据读写
// 将数据发送给客户端
//需要创建子线程 否则在主线程网络操作直接闪退
val serverSocket = ServerSocket(8888)
val socket = serverSocket.accept()
val inputStream = socket.getInputStream()
val outputStream = socket.getOutputStream()
//发送数据
outputStream?.write(data)
//此处为了方便 实际需要开启线程读取 并且要有合适的延迟
while (!mQuitReadData) {
    val reader = inputStream.bufferedReader(StandardCharsets.UTF_8)
    val text = reader.readLine()
    Log.d(TAG, "读取到的数据$text")
}
客户端创建Socket进行数据读写
//需要创建子线程 否则在主线程网络操作直接闪退
val address: InetAddress = info.groupOwnerAddress
val socket = Socket(address, 8888)
val inputStream = socket.getInputStream()
val outputStream = socket.getOutputStream()
//发送数据
outputStream?.write(data)
//此处为了方便 实际需要开启线程读取 并且要有合适的延迟
while (!mQuitReadData) {
    val reader = inputStream.bufferedReader(StandardCharsets.UTF_8)
    val text = reader.readLine()
    Log.d(TAG, "读取到的数据$text")
}
class MainActivity : AppCompatActivity() {

    private val TAG = MainActivity::class.java.simpleName

    private lateinit var mBinding: ActivityMainBinding

    private var mWifiP2pManager: WifiP2pManager? = null
    private var mChannel: WifiP2pManager.Channel? = null

    private var mDeviceList = arrayListOf<WifiP2pDevice>()
    private lateinit var mDeviceAdapter: DeviceAdapter

    private var mQuitReadData = true

    @SuppressLint("NotifyDataSetChanged")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        mBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(mBinding.root)
        ViewCompat.setOnApplyWindowInsetsListener(mBinding.main) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

        val intentFilter = IntentFilter()
        intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)
        intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)
        intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
        intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)
        registerReceiver(mReceiver, intentFilter)

        mDeviceAdapter = DeviceAdapter(mDeviceList)
        mBinding.rvDeviceList.adapter = mDeviceAdapter
        mDeviceAdapter.mOnItemSelectedListener = object : OnItemSelectedListener {
            override fun onItemSelected(
                parent: AdapterView<*>?,
                view: View?,
                position: Int,
                id: Long
            ) {
                val wifiP2pDevice = mDeviceList[position]
                connect(wifiP2pDevice)
            }

            override fun onNothingSelected(parent: AdapterView<*>?) {
            }
        }

        //通用步骤
        mWifiP2pManager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager

        mChannel = mWifiP2pManager?.initialize(this, Looper.getMainLooper()) {
            Log.d(TAG, "Channel断开连接")
        }

        //服务端部分
        //服务端创建群组
        mWifiP2pManager?.createGroup(mChannel, object : WifiP2pManager.ActionListener {
            override fun onSuccess() {
                Log.d(TAG, "创建群组成功")
            }

            override fun onFailure(reason: Int) {
                Log.w(TAG, "创建群组失败$reason")
            }
        })

        //客户端部分
        //客户端搜索对等设备
        mWifiP2pManager?.discoverPeers(mChannel, object : WifiP2pManager.ActionListener {
            override fun onSuccess() {
                Log.d(TAG, "搜索成功")
            }

            override fun onFailure(reason: Int) {
                Log.d(TAG, "搜索失败:$reason")
            }

        })

        //使用异步方法(推荐通过广播监听) 获取设备列表
        mWifiP2pManager?.requestPeers(mChannel) {
            mDeviceList.addAll(it.deviceList)
            if (mDeviceList.isEmpty()) {
                //没有设备
                runOnUiThread { Toast.makeText(this, "没有发现设备", Toast.LENGTH_SHORT).show() }
            } else {
                //刷新列表
                runOnUiThread { mDeviceAdapter.notifyDataSetChanged() }
            }
        }
    }

    /**
     * 连接设备
     */
    private fun connect(wifiP2pDevice: WifiP2pDevice) {
        val config = WifiP2pConfig().apply {
            this.deviceAddress = wifiP2pDevice.deviceAddress
            this.wps.setup = WpsInfo.PBC
        }
        mWifiP2pManager?.connect(mChannel, config, object : WifiP2pManager.ActionListener {
            override fun onSuccess() {
                Log.d(TAG, "连接成功")
                mQuitReadData = false
                transferData("Hello".toByteArray())
            }

            override fun onFailure(reason: Int) {
                Log.w(TAG, "连接失败$reason")
                mQuitReadData = true
            }

        })
    }

    private fun transferData(data: ByteArray) {
        //请求设备连接信息
        mWifiP2pManager?.requestConnectionInfo(mChannel) { info ->
            if (info.groupFormed && info.isGroupOwner) {
                // 将数据发送给客户端
                val serverSocket = ServerSocket(8888)
                val socket = serverSocket.accept()
                val inputStream = socket.getInputStream()
                val outputStream = socket.getOutputStream()
                //发送数据
                outputStream?.write(data)
                //此处为了方便 实际需要开启线程读取 并且要有合适的延迟
                while (!mQuitReadData) {
                    val reader = inputStream.bufferedReader(StandardCharsets.UTF_8)
                    val text = reader.readLine()
                    Log.d(TAG, "读取到的数据$text")
                }
            } else {
                //设备是客户端
                val address: InetAddress = info.groupOwnerAddress
                val socket = Socket(address, 8888)
                val inputStream = socket.getInputStream()
                val outputStream = socket.getOutputStream()
                //发送数据
                outputStream?.write(data)
                //此处为了方便 实际需要开启线程读取 并且要有合适的延迟
                while (!mQuitReadData) {
                    val reader = inputStream.bufferedReader(StandardCharsets.UTF_8)
                    val text = reader.readLine()
                    Log.d(TAG, "读取到的数据$text")
                }
            }
        }
    }

    private val mReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            val action = intent?.action;
            if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
                // Check to see if Wi-Fi is enabled and notify appropriate activity
                // 检查 Wi-Fi P2P 是否已启用
                val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1)
                val isEnabled = (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED)
            } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
                // Call WifiP2pManager.requestPeers() to get a list of current peers
                //异步方法
                // mWifiP2pManager?.requestPeers();
            } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
                // Respond to new connection or disconnections
                // 链接状态变化回调
                // 此广播 会和 WIFI_P2P_THIS_DEVICE_CHANGED_ACTION 同时回调
                // 注册广播、连接成功、连接失败 三种时机都会调用
                // 应用可使用 requestConnectionInfo()、requestNetworkInfo() 或 requestGroupInfo() 来检索当前连接信息。
            } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
                // Respond to this device's wifi state changing
                // 此设备的WiFi状态更改回调
                // 此广播 会和 WIFI_P2P_CONNECTION_CHANGED_ACTION 同时回调
                // 注册广播、连接成功、连接失败 三种时机都会调用
                // 应用可使用 requestDeviceInfo() 来检索当前连接信息。
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        //移除群组
        mWifiP2pManager?.removeGroup(mChannel, null)
        //取消链接
        mWifiP2pManager?.cancelConnect(mChannel, null)
    }
}

Android WiFi P2P使用流程总结:

  1. 「权限声明」:

在AndroidManifest.xml中声明必要的权限,包括网络访问权限和文件读写权限。

  1. 「初始化」:

在Android应用中,首先需要获取WifiP2pManager实例,并通过调用其initialize方法进行初始化。这将注册应用并准备使用Wi-Fi P2P功能。

初始化完成后,会获得一个Channel对象,它是后续操作的关键。

  1. 「广播接收与处理」:

在整个过程中,应用需要注册并监听特定的广播,以处理Wi-Fi P2P状态变化、设备发现、连接变化等事件。

这些广播会通知应用有关Wi-Fi P2P操作的状态和结果,以便应用可以做出相应的响应。

  1. 「设备发现」:

使用WifiP2pManager的discoverPeers方法开始搜索附近的Wi-Fi P2P设备。

设备会在特定的频段(如2.4GHz的1、6、11频段)上发送Probe Request帧来寻找其他设备。

搜索到的设备会作为列表展示在应用界面上,用户可以从中选择想要连接的设备。

  1. 「建立连接」:

选定一个设备后,作为客户端或服务端(Group Owner,GO)发起连接请求。

通过WifiP2pConfig对象配置连接参数,如目标设备的地址和WPS(Wi-Fi Protected Setup)设置。

使用WifiP2pManager的connect方法尝试建立连接。

  1. 「连接确认与数据传输」:

一旦连接建立成功,设备之间就可以开始数据传输了。

可以通过Socket编程在设备之间建立连接,并传输文件或其他数据。

根据应用需求,可以创建服务端套接字监听客户端的连接请求,也可以作为客户端主动连接到服务端。

  1. 「数据传输完成与断开连接」:

数据传输完成后,应用需要适当地关闭套接字和断开Wi-Fi P2P连接。

使用WifiP2pManager的相关方法来断开连接,并释放相关资源。

责任编辑:武晓燕 来源: 沐雨花飞蝶

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK