Android p2p简单实现

p2p技术,可以让具备相应硬件的 Android 4.0(API 级别 14)或更高版本设备在没有中间接入点的情况下,通过 wlan 进行直接互联。使用这些 api,您可以实现支持 wlan p2p 的设备间相互发现和连接,从而获得比蓝牙连接更远距离的高速连接通信效果。对于多人游戏或照片共享等需要在用户之间共享数据的应用而言,这一技术非常有用。

一、服务发布

一个设备上可以有多个p2p服务存在,通过addLocalService可以在设备上发布自己的p2p服务,通过removeLocalService可以移除自己的p2p服务

// 获取 WifiP2pManager
mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
// 获取 WifiP2pManager.Channel
mChannel = mManager.initialize(this, getMainLooper(), null);
Map map = new HashMap<>();
map.put("port", "8888");
// 构造一个 WifiP2pDnsSdServiceInfo,可通过此对象发布自己的信息,比如上面的 port 8888 
p2pDnsSdServiceInfo = WifiP2pDnsSdServiceInfo.newInstance(serviceName, serviceType, map);
// 发布自己的p2p服务
mManager.addLocalService(mChannel, p2pDnsSdServiceInfo, new WifiP2pManager.ActionListener() {
                        @Override
                        public void onSuccess() {
                            Log.i(TAG, "startServer onSuccess");
                        }

                        @Override
                        public void onFailure(int reason) {
                            Log.i(TAG, "startServer onFailure:" + reason);
                        }
                    });

serviceName是服务的名字,可以自定义,这里设置为 DON_TEST
serviceType是服务类型,格式为 _._,表示协议+传输类型。

例如:

  • _ipp._tcp,协议为ipp(打印机服务),通过tcp传输数据
  • _presence._tcp 协议为presence(xmpp.org的消息传递协议),通过tcp传输数据

二、设备&服务搜索

1、注册广播

p2p设备信息及连接状态的变化,需要通过监听系统广播来实现,常用到的广播有以下几个:

  • WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION p2p网络状态广播
  • WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION p2p设备列表修改广播
  • WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION 当前设备信息修改广播
  • WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION p2p连接状态广播
        IntentFilter intentFilter = new 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(broadCastReceiver, intentFilter);
2、搜索设备

通过方法discoverPeers可搜索设备周围的p2p设备

mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
               @Override
               public void onSuccess() {
                     Log.i(TAG, "search onSuccess");
               }
  
               @Override
               public void onFailure(int reason) {
                      Log.i(TAG, "search onFailure:" + reason);
               }
 });

discoverPeers会触发广播 WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION,收到此广播后,通过方法requestPeers可获取周边p2p设备列表

    private WifiP2pManager.PeerListListener peerListListener = new WifiP2pManager.PeerListListener() {
        @Override
        public void onPeersAvailable(WifiP2pDeviceList wifiP2pDeviceList) {
            // 获取设备列表
            if (wifiP2pDeviceList != null && !wifiP2pDeviceList.getDeviceList().isEmpty()) {
                mainActivity.updateDevices(wifiP2pDeviceList.getDeviceList());
            }
        }
    };
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.i(TAG, "onReceive " + action);
        if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {// p2p状态
            int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
            if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
                // Wifi P2P is enabled
                Log.i(TAG, "Wifi P2P is enabled");
            } else {
                // Wi-Fi P2P is not enabled
                Log.i(TAG, "Wifi P2P is not enabled");
            }
        } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {// p2p设备变化监听
            if (mManager != null) {
                mManager.requestPeers(mChannel, peerListListener);
            }
        } 
    }
3、搜索服务

通过 一、服务发布,发布了一个名为 DON_TEST 的服务,通过discoverServices方法就可以搜索到这个服务,同时需要通过setDnsSdResponseListeners方法设置监听WifiP2pManager.DnsSdServiceResponseListenerWifiP2pManager.DnsSdTxtRecordListener,在搜索过程中这两个监听会回调搜索到的服务信息。

WifiP2pManager.DnsSdServiceResponseListener用于监听搜索到的服务信息

    boolean isConnecting = false;
    private WifiP2pManager.DnsSdServiceResponseListener dnsListener = new WifiP2pManager.DnsSdServiceResponseListener() {
        @Override
        public void onDnsSdServiceAvailable(String instanceName, String registrationType, WifiP2pDevice device) {
            Log.i(TAG, "onDnsSdServiceAvailable " + instanceName + " " + isConnecting);
            if (isConnecting) {
                return;
            }
            if (serviceName.equals(instanceName)) {
                isConnecting = true;
                connect(device);
            }
        }
    };

WifiP2pManager.DnsSdTxtRecordListener用于获取服务发布携带的额外信息,例如 一、服务发布 中的携带的信息 port 8888

  private WifiP2pManager.DnsSdTxtRecordListener recordListener = new WifiP2pManager.DnsSdTxtRecordListener() {
        @Override
        public void onDnsSdTxtRecordAvailable(String fullDomain, Map record, WifiP2pDevice device) {
            Log.i(TAG, "onDnsSdTxtRecordAvailable " + fullDomain);
        }
    };
        mManager.setDnsSdResponseListeners(mChannel, dnsListener, recordListener);
        serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
        mManager.addServiceRequest(mChannel, serviceRequest, new WifiP2pManager.ActionListener() {
            @Override
            public void onSuccess() {

            }

            @Override
            public void onFailure(int reason) {

            }
        });
        // 搜索服务
        mManager.discoverServices(mChannel, new WifiP2pManager.ActionListener() {
            @Override
            public void onSuccess() {
                Log.i(TAG, "search onSuccess");
            }

            @Override
            public void onFailure(int reason) {
                Log.i(TAG, "search onFailure:" + reason);
            }
        });

三、文件传输

1、设备连接

通过WifiP2pManager.DnsSdServiceResponseListener可以搜索到可用的服务信息,通过方法 onDnsSdServiceAvailable 中的参数 WifiP2pDevice可获取到服务所在的设备信息,通过 connect方法可以连接该设备

    public void connect(WifiP2pDevice device) {
        Log.i(TAG, "connect " + device.deviceName);
        WifiP2pConfig config = new WifiP2pConfig();
        config.deviceAddress = device.deviceAddress;
        config.wps.setup = WpsInfo.PBC;
        mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() {

            @Override
            public void onSuccess() {
                //success logic
                Log.i(TAG, "connect success");
            }

            @Override
            public void onFailure(int reason) {
                //failure logic
                Log.i(TAG, "connect onFailure " + reason);
                isConnecting = false;
            }
        });
    }

connect调用成功之后,如果是首次连接,对端手机会显示一个弹窗,用户选择允许连接之后,两部手机手机才能建立连接。
建立连接之后,系统会发送广播WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION,收到这个广播之后,可通过requestConnectionInfo方法获取连接信息 WifiP2pInfo,通过WifiP2pInfo可获取对方的ip

    public void requestConnectionInfo() {
        Log.i(TAG, "requestConnectionInfo");
        mManager.requestConnectionInfo(mChannel, new WifiP2pManager.ConnectionInfoListener() {
            @Override
            public void onConnectionInfoAvailable(WifiP2pInfo wifiP2pInfo) {
                try {
                    InetAddress address = InetAddress.getByName(wifiP2pInfo.groupOwnerAddress.getHostAddress());
                    Log.i(TAG, "address " + address.toString());
                } catch (Exception e) {
                    Log.w(TAG, e);
                }
            }
        });
    }
2、创建群组

连接到了一起,此时系统会自动创建一个群组(Group)并随机指定一台设备为群主(GroupOwner)。此时,对于两台设备来说,群主的IP地址是可知的(系统回调函数中有提供),但客户端的IP地址需要再来通过其他方法来主动获取。

可以通过创建群组,指定某台设备作为服务器端(群主),即指定某台设备用来接收文件,因此,服务器端要主动创建群组,并等待客户端的连接。

        mManager.createGroup(mChannel, new WifiP2pManager.ActionListener() {
            @Override
            public void onSuccess() {
                Log.i(TAG, "createGroup onSuccess");
            }

            @Override
            public void onFailure(int reason) {
                Log.i(TAG, "createGroup onFailure: " + reason);
            }
        });
3、传输文件

server端

package com.don.p2pdemo;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class FileServerAsyncTask extends AsyncTask {
    private final String TAG = "p2p:FileServerAsyncTask";

    private Context context;

    public FileServerAsyncTask(Context context) {
        this.context = context;
    }

    @Override
    protected String doInBackground(Object[] params) {
        try {
            /**
             * Create a server socket and wait for client connections. This
             * call blocks until a connection is accepted from a client
             */
            ServerSocket serverSocket = new ServerSocket(8888);
            Socket client = serverSocket.accept();
            /**
             * If this code is reached, a client has connected and transferred data
             * Save the input stream from the client as a JPEG file
             */
            final File f = new File(Environment.getExternalStorageDirectory() + "/wifip2pshared-" + System.currentTimeMillis()
                    + ".jpg");
            File dirs = new File(f.getParent());
            if (!dirs.exists())
                dirs.mkdirs();
            f.createNewFile();
            InputStream inputstream = client.getInputStream();
            copyFile(inputstream, new FileOutputStream(f));
            serverSocket.close();
            return f.getAbsolutePath();
        } catch (IOException e) {
            Log.w(TAG, e);
            return null;
        }
    }

    private void copyFile(InputStream inputStream, FileOutputStream fileStream) throws IOException {
        byte[] bytes = new byte[1024];
        int index = 0;
        while ((index = inputStream.read(bytes)) != -1) {
            fileStream.write(bytes, 0, index);
        }
        fileStream.flush();
    }

    /**
     * Start activity that can handle the JPEG image
     */
    @Override
    protected void onPostExecute(Object result) {
        if (result != null) {
            Intent intent = new Intent();
            intent.setAction(android.content.Intent.ACTION_VIEW);
            intent.setDataAndType(Uri.parse("file://" + result), "image/*");
            context.startActivity(intent);
        }
    }
}

客户端

    boolean isSending = false;
    public void sendImage(String host, int port) {
        if (isSending) {
            return;
        }
        Log.i(TAG, "sendImage " + host + " " + port);
        Context context = this.getApplicationContext();
        int len;
        Socket socket = null;
        byte buf[] = new byte[1024];
        try {
            socket = new Socket(host, port);
            OutputStream outputStream = socket.getOutputStream();
            InputStream inputStream = context.getAssets().open("icon.png");
            while ((len = inputStream.read(buf)) != -1) {
                outputStream.write(buf, 0, len);
            }
            outputStream.close();
            inputStream.close();
            Log.i(TAG, "sendImage close");
        } catch (Exception e) {
            Log.w(TAG, e);
        } finally {
            if (socket != null) {
                if (socket.isConnected()) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        //catch logic
                    }
                }
            }
        }
        isSending = false;
        Log.i(TAG, "sendImage end");
    }

在两部手机通过p2p连接之后,客户端传输文件到服务端

    public void requestConnectionInfo() {
        Log.i(TAG, "requestConnectionInfo");
        mManager.requestConnectionInfo(mChannel, new WifiP2pManager.ConnectionInfoListener() {
            @Override
            public void onConnectionInfoAvailable(final WifiP2pInfo wifiP2pInfo) {
                Log.i(TAG, "onConnectionInfoAvailable 群主:" + wifiP2pInfo.isGroupOwner);
                if (wifiP2pInfo.isGroupOwner) {
                    AsyncTask asyncTask = new FileServerAsyncTask(MainActivity.this);
                    asyncTask.execute();
                } else {
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                InetAddress address = InetAddress.getByName(wifiP2pInfo.groupOwnerAddress.getHostAddress());
                                Log.i(TAG, "address " + address.toString());
                                sendImage(address.toString().replace("/",""), 8888);
                            } catch (Exception e) {
                                Log.w(TAG, e);
                            }
                        }
                    }).start();
                }
            }
        });
    }

参考:
https://developer.android.google.cn/guide/topics/connectivity/wifip2p#java
https://blog.csdn.net/weixin_33735676/article/details/87990760

你可能感兴趣的:(Android p2p简单实现)