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使用流程总结:
- 「权限声明」:
在AndroidManifest.xml中声明必要的权限,包括网络访问权限和文件读写权限。
- 「初始化」:
在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的相关方法来断开连接,并释放相关资源。