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<String, String> 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是服务类型,格式为 _<protocol>._<transportlayer>,表示协议+传输类型。

例如:

  • _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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349