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

移动开发 Android
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对象,它是后续操作的关键。

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

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

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

4.「设备发现」:

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

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

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

5.「建立连接」:

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

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

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

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

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

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

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

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

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

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

责任编辑:武晓燕 来源: 沐雨花飞蝶
相关推荐

2012-12-10 09:46:21

P2P云存储Symform

2009-04-07 10:39:13

2011-11-17 16:58:11

AndroidAdobeAIR

2009-05-18 09:11:00

IPTV融合宽带

2023-03-14 12:43:57

2022-07-19 16:59:04

流媒体传输IPC物联网

2010-07-13 14:41:14

2010-03-22 15:27:40

云计算

2020-03-05 20:30:15

Syncthing文件同步工具开源

2011-12-19 09:46:31

2010-03-10 10:51:30

2010-10-29 09:43:50

Wi-Fi DirecWi-Fi联

2013-03-13 09:24:56

2010-07-07 10:31:45

2013-12-12 13:46:40

大数据金融P2P大数据

2017-11-20 17:53:00

阿里开源容器

2012-09-25 13:47:43

C#网络协议P2P

2010-06-28 11:15:45

BitTorrent协

2018-08-16 07:29:02

2015-04-27 11:49:23

点赞
收藏

51CTO技术栈公众号