Demo 下载地址:https://github.com/mengzhinan/WiFi_P2P_test
GoogleAndroid Doc:https://developer.android.google.cn/guide/topics/connectivity/wifip2p
Wifi P2P (peer to peer):义为 Wifi 点对点,也叫 Wifi 直连(Wifi Direct),他是 Wifi Display(投屏) 应用的技术基础。
官方描述:
使用 WLAN 直连 (P2P) 技术,可以让具备相应硬件的 Android 4.0(API 级别 14)或更高版本设备在没有中间接入点的情况下,通过 WLAN 进行直接互联。使用这些 API,您可以实现支持 WLAN P2P 的设备间相互发现和连接,从而获得比蓝牙连接更远距离的高速连接通信效果。对于多人游戏或照片共享等需要在用户之间共享数据的应用而言,这一技术非常有用。
总结以下优点:
1、有比蓝牙更远的传输距离。未测试
2、有比蓝牙更快速的数据传输速度,更大的带宽。未测试
3、只需要打开 Wifi 即可,不需要加入任何网络或 AP,即可实现对等点连接通讯。
可实现通过 Wifi 连接,同时使用数据网络的场景,比喻:手机遥控无人机的同时,无人机需要访问远程服务器上传数据。
虽然上面提到两台或多台 Android 设备通过 Wifi P2P 通讯时不需要加入任何网络,但是 Wifi P2P 协议还是需要组件网络才能发现对方并建立 TCP 连接通讯的。在组网和通讯阶段一共有 3 个角色:
1、P2P Group Owner,或称为群主,充当服务端,并需要创建 ServerSocket 等待客户端的连接,获得 IO 流与客户端通讯或转发消息给其他客户端。
2、P2P Client,或称为组员,充当客户端,需要创建 Socket 与服务器通讯。
3、P2P Device,在上面的过程中,服务器端和客户端都是一个独立的设备,拥有唯一的设备特征信息。
4、广播接收器:
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION:检查 Wi-Fi P2P 是否已启用。Android 4.0 以上系统才有此功能。
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION:对等设备发生变化,一般是在调用 discoverPeers 方法后发送此广播。在此广播中,你可以调用 requestPeers 方法,获得扫描到的对等设备列表。
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION:连接状态发生变化,一般在调用 connect 或 cancelConnect 方法时会发送此广播。状态共有 5 种:WifiP2pDevice.AVAILABLE、WifiP2pDevice.INVITED、WifiP2pDevice.CONNECTED、WifiP2pDevice.FAILED 和 WifiP2pDevice.UNAVAILABLE 。
当判断连接信息为连接状态时,即 networkInfo.isConnected() ,你应当继续请求连接的具体信息 mManager.requestConnectionInfo(...),然后获得群主的详细设备信息,建立 Socket 通讯。
WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION:此设备的WiFi状态更改回调,应用可使用 requestDeviceInfo() 来检索当前连接信息。
在未组网之前,是不存在群主、组员之称的。只有在设备尝试发现并连接对方时,系统才会通过 P2P 协议尝试使多端设备组件为一个群组,并自动确定某一个设备为群主。但是本人在实测过程中发现,是需要先有群主,才会加入组员组网通讯的。
更底层的原理参考:https://blog.csdn.net/wirelessdisplay/article/details/53365377
绘制了一张流程图,描述我 Demo 的连接过程。
服务端流程:
客户端流程:
1、设置相关权限:
因为在建立 P2P 连接后,需要建立 Socket 通讯,所以需要 INTERNET 权限。
2、注册广播,有 4 个核心广播 Action:
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
// Check to see if Wi-Fi is enabled and notify appropriate activity
// 检查 Wi-Fi P2P 是否已启用
int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
boolean isEnabled = (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED);
if (mWifiP2PListener != null) {
mWifiP2PListener.onWifiP2pEnabled(isEnabled);
}
} else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
// Call WifiP2pManager.requestPeers() to get a list of current peers
WifiP2pDeviceList wifiP2pDeviceList = intent.getParcelableExtra(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
if (mWifiP2PListener != null && wifiP2pDeviceList != null) {
mWifiP2PListener.onPeersAvailable(wifiP2pDeviceList.getDeviceList());
}
// 异步方法
WifiP2PHelper.getInstance(context).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() 来检索当前连接信息。
NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
if (networkInfo != null
&& networkInfo.isConnected()
&& mManager != null
&& mWifiP2PListener != null) {
WifiP2PHelper.getInstance(context).requestConnectInfo();
} else {
if (mWifiP2PListener != null) {
mWifiP2PListener.onConnectionInfoAvailable(null);
}
}
} 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() 来检索当前连接信息。
WifiP2pDevice wifiP2pDevice = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
if (mWifiP2PListener != null) {
mWifiP2PListener.onSelfDeviceAvailable(wifiP2pDevice);
}
}
}
3、初始化 Wifi P2P:
private WifiP2PHelper(Context context) {
if (context == null || context.getApplicationContext() == null) {
throw new IllegalArgumentException("context is null exception.");
}
mApplicationContext = context.getApplicationContext();
mManager = (WifiP2pManager) mApplicationContext.getSystemService(Context.WIFI_P2P_SERVICE);
// 将此应用注册到 WLAN P2P 框架
mChannel = mManager.initialize(mApplicationContext, Looper.getMainLooper(), null);
mReceiver = new WifiP2PBroadCastReceiver(mManager, mChannel);
}
初始化阶段的核心就是调用 manager.initialize(...) 方法,得到群组内通讯的通道对象 channel。
4、服务端创建群组:
public void createGroup() {
mManager.createGroup(mChannel, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
if (mWifiP2PListener != null) {
mWifiP2PListener.onCreateGroup(true);
}
}
@Override
public void onFailure(int reason) {
if (mWifiP2PListener != null) {
mWifiP2PListener.onCreateGroup(false);
}
}
});
}
5、客户端扫描对等设备:
public void discover() {
mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
if (mWifiP2PListener != null) {
mWifiP2PListener.onDiscoverPeers(true);
}
}
@Override
public void onFailure(int reason) {
if (mWifiP2PListener != null) {
mWifiP2PListener.onDiscoverPeers(false);
}
}
});
}
6、客户端收到扫描成功广播后,请求扫描到的结果。尝试了解设备,获取到连接广播,在连接成功时,请求连接的详细信息,获取服务端的 IP 地址,建立 Socket 连接。
} else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
// Call WifiP2pManager.requestPeers() to get a list of current peers
WifiP2pDeviceList wifiP2pDeviceList = intent.getParcelableExtra(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
if (mWifiP2PListener != null && wifiP2pDeviceList != null) {
mWifiP2PListener.onPeersAvailable(wifiP2pDeviceList.getDeviceList());
}
// 异步方法
WifiP2PHelper.getInstance(context).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() 来检索当前连接信息。
NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
if (networkInfo != null
&& networkInfo.isConnected()
&& mManager != null
&& mWifiP2PListener != null) {
WifiP2PHelper.getInstance(context).requestConnectInfo();
} else {
if (mWifiP2PListener != null) {
mWifiP2PListener.onConnectionInfoAvailable(null);
}
}
}
7、服务器创建 ServerSocket 接收客户端,并死循环读取 InputStream 数据:
private void initSocket() {
try {
serverSocket = new ServerSocket(SERVER_PORT);
// 需要设置为无限超时
// serverSocket.setSoTimeout(10000);
while (!isQuitReadClient) {
socket = serverSocket.accept();
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
String text = "连接到客户端 -> " + PHONE_INFO;
IOHelper.writeText(outputStream, text);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void receive() {
try {
while (!isQuitReadMessage) {
Thread.sleep(500);
String text = IOHelper.readText(inputStream);
if (TextUtils.isEmpty(text)) {
continue;
}
postToUI(text);
}
} catch (Exception e) {
e.printStackTrace();
}
}
8、客户端床架 Socket 连接服务端,并死循环读取 InputStream:
private void initSocket() {
try {
socket = new Socket(serverIP, SERVER_PORT);
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
String text = "连接到服务端 -> " + PHONE_INFO;
IOHelper.writeText(outputStream, text);
} catch (IOException e) {
e.printStackTrace();
}
}
private void receive() {
try {
while (!isQuitReadMessage) {
Thread.sleep(500);
String text = IOHelper.readText(inputStream);
if (TextUtils.isEmpty(text)) {
continue;
}
postToUI(text);
}
} catch (Exception e) {
e.printStackTrace();
}
}
服务端:
客户端:
服务端接收连接确认对话框:
1、需要先创建群组,客户端才可以连接并加入组,否则连接不上。
2、服务端 ServerSocket 在等待时不要设置超时,否则遇到客户端连不上问题时难以排查。
3、如果服务端退出时没有移除群组,或客户端退出时没有断开连接,在下次连接时会出现连不上问题,不确定原因。
4、客户端首次连接服务端时,服务端会弹出请求对话框。服务端同意后才会建立连接。
Demo 下载地址:https://github.com/mengzhinan/WiFi_P2P_test