Android热点连接管理(一)

手机上的wifi功能,多半都被当做客户端在使用。当做热点共享网络时的场景比较少。
最近做一个尝试,将所有试图连接到Android便携热点的客户端的信息,通过底层一直上报上来,最终增加API供上层应用调用。
在原生的Android代码中,其实已经有一个WifiDevice类来表示当前连接至wifi热点的客户端信息,我们先来看一下这个类是怎样定义的。

/**
 * Describes information about a detected Wi-Fi STA.
 * {@hide}
 */
public class WifiDevice implements Parcelable {
    /**
     * The device MAC address is the unique id of a Wi-Fi STA
     */
    public String deviceAddress = "";

    /**
     * The device name is a readable string of a Wi-Fi STA
     */
    public String deviceName = "";

    /**
     * The device state is the state of a Wi-Fi STA
     */
    public int deviceState = 0;

    /**
     * These definitions are for deviceState
     */
    public static final int DISCONNECTED = 0;
    public static final int CONNECTED    = 1;
    public static final int BLACKLISTED  = 2;

    private static final String AP_STA_CONNECTED_STR   = "AP-STA-CONNECTED";
    private static final String AP_STA_DISCONNECTED_STR   = "AP-STA-DISCONNECTED";
	private static final String AP_STA_REPORT_STR   = "AP-STA-REPORT";

    /** {@hide} */
    public WifiDevice() {}

   /**
     * @param string formats supported include
     *
     *  AP-STA-CONNECTED 42:fc:89:a8:96:09
     *  AP-STA-DISCONNECTED 42:fc:89:a8:96:09
     *
     *  Note: The events formats can be looked up in the hostapd code
     * @hide
     */
    public WifiDevice(String dataString) throws IllegalArgumentException {
        String[] tokens = dataString.split(" ");

        if (tokens.length < 2) {
            throw new IllegalArgumentException();
        }

        if (tokens[0].indexOf(AP_STA_CONNECTED_STR) != -1) {
            deviceState = CONNECTED;
        } else if (tokens[0].indexOf(AP_STA_DISCONNECTED_STR) != -1) {
            deviceState = DISCONNECTED;
        }else if (tokens[0].indexOf(AP_STA_REPORT_STR) != -1) {
            deviceState = BLACKLISTED;
        } else {
            throw new IllegalArgumentException();
        }

        deviceAddress = tokens[1];
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof WifiDevice)) {
            return false;
        }

        WifiDevice other = (WifiDevice) obj;

        if (deviceAddress == null) {
            return (other.deviceAddress == null);
        } else {
            return deviceAddress.equals(other.deviceAddress);
        }
    }

    /** Implement the Parcelable interface {@hide} */
    public int describeContents() {
        return 0;
    }

    /** Implement the Parcelable interface {@hide} */
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(deviceAddress);
        dest.writeString(deviceName);
        dest.writeInt(deviceState);
    }

    /** Implement the Parcelable interface {@hide} */
    public static final Creator<WifiDevice> CREATOR =
        new Creator<WifiDevice>() {
            public WifiDevice createFromParcel(Parcel in) {
                WifiDevice device = new WifiDevice();
                device.deviceAddress = in.readString();
                device.deviceName = in.readString();
                device.deviceState = in.readInt();
                return device;
            }

            public WifiDevice[] newArray(int size) {
                return new WifiDevice[size];
            }
        };
}<span style="font-family:Microsoft YaHei;font-size:12px;"> 
</span>

比起其他复杂的类来说,WifiDevice.java非常简单,只有几个成员变量和方法。 它代表一个接入到wifi热点的客户端的信息,其中包括了客户端的MAC地址,主机名和接入状态。

一般情况下,我们需要的客户端信息也就是这几个内容了。

不过有一个问题,WifiDevice只能表示已经正常接入热点的客户端,而那些曾经试图连接热点,但是并没有连接成功的客户端呢(比如由于密码错误而连接失败的)?如果我们

希望能得到周边所有曾经和热点发生过关系(!?)的客户端的信息呢?如何获取这写信息呢?

先抛开以上的问题,我们先来了解一下,正常接入的客户端是如何获取它的信息的。那先得看一下Tethering.java(\frameworks\base\services\core\java\com\android\server\connectivity\Tethering.java)

tether这个词意思是拴绳,拴住的意思,这里可以理解成是分享的意思,比如 WIFI_TETHERING(用WIFI分享网路),
 USB_TETHERING(用USB分享网络)。仔细阅读Tethering中的代码,可以看到,这个类主要就是

为网络共享服务的,其中包括USB共享,蓝牙共享和wifi共享。直接说Tethering,其实是跳过了connectivityservice的,上层获取客户端信息,其实是通过ConnectivityManager调用ConnectivityService的方法,最终

调用到Tethering的 getTetherConnectedSta() 方法。

    public List<WifiDevice> getTetherConnectedSta() {
        Iterator it;
        List<WifiDevice> TetherConnectedStaList = new ArrayList<WifiDevice>();

        if (mContext.getResources().getBoolean(com.android.internal.R.bool.config_softap_extention)) {
            it = mConnectedDeviceMap.keySet().iterator();
            while(it.hasNext()) {
                String key = (String)it.next();
                WifiDevice device = (WifiDevice)mConnectedDeviceMap.get(key);
                if (VDBG) {
                    Log.d(TAG, "getTetherConnectedSta: addr=" + key + " name=" + device.deviceName);
                }
                TetherConnectedStaList.add(device);
            }
        }

        return TetherConnectedStaList;
    }

仔细看下,无非是在Tethering中定义了一个mConnectedDeviceMap成员,专门用来存放接入的客户端信息,以下是定义

private HashMap<String, WifiDevice> mConnectedDeviceMap = new HashMap<String, WifiDevice>();

有两个地方更新了这个HashMap。其实两个地方都是 一样的,一个是已经保存这个设备的IP信息的,另一个是如果没有保存过,则先启动一个DnsmasqThread线程,给客户端分配IP后,

再讲这个WifiDevice保存在HashMap中。总体来说就是在第一个方法interfaceMessageRecevied(String message)中保存了接入的客户端信息。

1.

    private static class DnsmasqThread extends Thread {
        private final Tethering mTethering;
        private int mInterval;
        private int mMaxTimes;
        private WifiDevice mDevice;

        public DnsmasqThread(Tethering tethering, WifiDevice device,
            int interval, int maxTimes) {
            super("Tethering");
            mTethering = tethering;
            mInterval = interval;
            mMaxTimes = maxTimes;
            mDevice = device;
        }

        public void run() {
            boolean result = false;

            try {
                while (mMaxTimes > 0) {
                    result = mTethering.readDeviceInfoFromDnsmasq(mDevice);
                    if (result) {
                        if (DBG) Log.d(TAG, "Successfully poll device info for " + mDevice.deviceAddress);
                        break;
                    }

                    mMaxTimes --;
                    Thread.sleep(mInterval);
                }
            } catch (Exception ex) {
                result = false;
                Log.e(TAG, "Pulling " + mDevice.deviceAddress +  "error" + ex);
            }

            if (!result) {
                if (DBG) Log.d(TAG, "Pulling timeout, suppose STA uses static ip " + mDevice.deviceAddress);
            }

            // When STA uses static ip, device info will be unavaiable from dnsmasq,
            // thus no matter the result is success or failure, we will broadcast the event.
            // But if the device is not in L2 connected state, it means the hostapd connection is
            // disconnected before dnsmasq get device info, so in this case, don't broadcast
            // connection event.
            WifiDevice other = mTethering.mL2ConnectedDeviceMap.get(mDevice.deviceAddress);
            if (other != null && other.deviceState == WifiDevice.CONNECTED) {
                mTethering.mConnectedDeviceMap.put(mDevice.deviceAddress, mDevice);
                mTethering.sendTetherConnectStateChangedBroadcast();
            } else {
                if (DBG) Log.d(TAG, "Device " + mDevice.deviceAddress + "already disconnected, ignoring");
            }
        }
    }

2.

 public void interfaceMessageRecevied(String message) {
        // if softap extension feature not enabled, do nothing
        if (!mContext.getResources().getBoolean(com.android.internal.R.bool.config_softap_extention)) {
            return;
        }

        if (DBG) Log.d(TAG, "interfaceMessageRecevied: message=" + message);

        try {
            WifiDevice device = new WifiDevice(message);

            if (device.deviceState == WifiDevice.CONNECTED) {
                mL2ConnectedDeviceMap.put(device.deviceAddress, device);

                // When hostapd reported STA-connection event, it is possible that device
                // info can't fetched from dnsmasq, then we start a thread to poll the
                // device info, the thread will exit after device info avaiable.
                // For static ip case, dnsmasq don't hold the device info, thus thread
                // will exit after a timeout.
                if (readDeviceInfoFromDnsmasq(device)) {
                    mConnectedDeviceMap.put(device.deviceAddress, device);
                    sendTetherConnectStateChangedBroadcast();
                } else {
                    if (DBG) Log.d(TAG, "Starting poll device info for " + device.deviceAddress);
                    new DnsmasqThread(this, device,
                        DNSMASQ_POLLING_INTERVAL, DNSMASQ_POLLING_MAX_TIMES).start();
                }
            } else if (device.deviceState == WifiDevice.DISCONNECTED) {
                mL2ConnectedDeviceMap.remove(device.deviceAddress);
                mConnectedDeviceMap.remove(device.deviceAddress);
                sendTetherConnectStateChangedBroadcast();

。。。。。。。

interfaceMessageRecevied(String message)中,很明显是入参message中携带了客户端信息,这个信息由谁发送,看一下它的调用关系。NetworkManagementService.java中的

notifyInterfaceMessage(String message)调用了它。

    /**
     * Notify our observers of a change in the data activity state of the interface
     */
    private void notifyInterfaceMessage(String message) {
        final int length = mObservers.beginBroadcast();
        for (int i = 0; i < length; i++) {
            try {
                mObservers.getBroadcastItem(i).interfaceMessageRecevied(message);
            } catch (RemoteException e) {
            } catch (RuntimeException e) {
            }
        }
        mObservers.finishBroadcast();
    }

而它的上一级调用关系是在NetworkManagementService中的NetdCallbackReceiver接收到Event事件后,根据事件的类型来逐个处理的。

 case NetdResponseCode.InterfaceMessage:
                    /*
                     * An message arrived in network interface.
                     * Format: "NNN IfaceMessage <3>AP-STA-CONNECTED 00:08:22:64:9d:84
                     */
                    if (cooked.length < 3 || !cooked[1].equals("IfaceMessage")) {
                        throw new IllegalStateException(errorMessage);
                    }
                    Slog.d(TAG, "onEvent: "+ raw);
                    if(cooked[4] != null) {
                        notifyInterfaceMessage(cooked[3] + " " + cooked[4]);
                    } else {
                        notifyInterfaceMessage(cooked[3]);
                    }
                    return true;
                    // break;

看上面,是接收到InterfaceMessage消息后,进行了消息处理,消息格式应该是注释中的格式“NNN IfaceMessage <3>AP-STA-CONNECTED 00:08:22:64:9d:84”,

InterfaceMessage消息又是从何而来呢?

这块直接跳到HAL层了,Netd进程,不知道你是否有所了解,我之前也小小研究过一下,不过仅限于使用,因为之前做过Android系统的数据卡,启动软AP时,都是直接

使用Netd和Softap等命令了,很好用。不过 没有更深入的了解,,后面有机会再好好学习一下。此处消息的传递,就是,来源于softapcontroller。

void *SoftapController::threadStart(void *obj){
    SoftapController *me = reinterpret_cast<SoftapController *>(obj);
    struct wpa_ctrl *ctrl;
    int count = 0;

    ALOGD("SoftapController::threadStart...");

    DIR *dir = NULL;

    dir = opendir(HOSTAPD_SOCKETS_DIR);
    if (NULL == dir && errno == ENOENT) {
        mkdir(HOSTAPD_SOCKETS_DIR, S_IRWXU|S_IRWXG|S_IRWXO);
        chown(HOSTAPD_SOCKETS_DIR, AID_WIFI, AID_WIFI);
        chmod(HOSTAPD_SOCKETS_DIR, S_IRWXU|S_IRWXG);
    } else {
         if (dir != NULL) { /* Directory already exists */
             ALOGD("%s already exists", HOSTAPD_SOCKETS_DIR);
             closedir(dir);
         }
         if (errno == EACCES)
             ALOGE("Cant open %s , check permissions ", HOSTAPD_SOCKETS_DIR);
    }
    chmod(HOSTAPD_DHCP_DIR, S_IRWXU|S_IRWXG|S_IRWXO);

    ctrl = wpa_ctrl_open(HOSTAPD_UNIX_FILE);
    while (ctrl == NULL) {
        /*
         * Try to connect to hostapd via wpa_ctrl interface.
         * During conneciton process, it is possible that hostapd
         * has station connected to it.
         * Set sleep time to a appropriate value to lower the
         * ratio that miss the STA-CONNECTED msg from hostapd
         */
        usleep(20000);
        ctrl = wpa_ctrl_open(HOSTAPD_UNIX_FILE);
        if (ctrl != NULL || count >= 150) {
            break;
        }
        count ++;
    }
    if (count == 150 && ctrl == NULL) {
        ALOGE("Connection to hostapd Error.");
        return NULL;
    }

    if (wpa_ctrl_attach(ctrl) != 0) {
        wpa_ctrl_close(ctrl);
        ALOGE("Attach to hostapd Error.");
        return NULL;
    }

    while(me->mHostapdFlag) {
        int res = 0;
        char buf[256];
        char dest_str[300];
        while (wpa_ctrl_pending(ctrl)) {
            size_t len = sizeof(buf) - 1;
            res = wpa_ctrl_recv(ctrl, buf, &len);
            if (res == 0) {
                buf[len] = '\0';
                ALOGD("Get event from hostapd (%s)", buf);
                memset(dest_str, 0x0, sizeof(dest_str));
                snprintf(dest_str, sizeof(dest_str), "IfaceMessage active %s", buf);
                me->mSpsl->sendBroadcast(ResponseCode::InterfaceMessage, dest_str, false);
            } else {
                break;
            }
        }

        if (res < 0) {
            break;
        }
        sleep(2);
    }

    wpa_ctrl_detach(ctrl);
    wpa_ctrl_close(ctrl);

    return NULL;
}

此处是怎样通信的,我还没有搞的彻底明白,不过并不影响我们理解整个流程,总是肯定是通过socket之类的方式,将InterfaceMessage消息发送出去,

消息中所携带的字符串保存在"dest_str"中。如果你抓取一个logcat的log,就可以看到消息字符串的格式为“IfaceMessage active <3>AP-STA-CONNECTED 00:0a:f5:8a:be:58”,

可以和前面讲的消息格式对应上。

再往前追溯一下,softapcontroller的消息来自于hostapd,  (ps. hostapd的知识又是一个比较大的分支了,此处略去,可以把它理解成驱动和上层承上启下的一个枢纽,它可以接收

来自wifi驱动的底层消息,处理后分门别类的通知给上层)。其他的不多说,消息是从sta_info.c中的ap_sta_set_authorized(struct hostapd_data *hapd, struct sta_info *sta, int authorized)

发出的。这里面主要是发送connect和disconnect消息的,如果sta->flags & WLAN_STA_AUTHORIZED,就是说如果鉴权过了话,就发送connect, 没有过就发送disconnect消息。

void ap_sta_set_authorized(struct hostapd_data *hapd, struct sta_info *sta,
			   int authorized)
{
	const u8 *dev_addr = NULL;
	char buf[100];
#ifdef CONFIG_P2P
	u8 addr[ETH_ALEN];
#endif /* CONFIG_P2P */

	if (!!authorized == !!(sta->flags & WLAN_STA_AUTHORIZED))
		return;

#ifdef CONFIG_P2P
	if (hapd->p2p_group == NULL) {
		if (sta->p2p_ie != NULL &&
		    p2p_parse_dev_addr_in_p2p_ie(sta->p2p_ie, addr) == 0)
			dev_addr = addr;
	} else
		dev_addr = p2p_group_get_dev_addr(hapd->p2p_group, sta->addr);
#endif /* CONFIG_P2P */

	if (dev_addr)
		os_snprintf(buf, sizeof(buf), MACSTR " p2p_dev_addr=" MACSTR,
			    MAC2STR(sta->addr), MAC2STR(dev_addr));
	else
		os_snprintf(buf, sizeof(buf), MACSTR, MAC2STR(sta->addr));

	if (authorized) {
		wpa_msg(hapd->msg_ctx, MSG_INFO, AP_STA_CONNECTED "%s", buf);

		if (hapd->msg_ctx_parent &&
		    hapd->msg_ctx_parent != hapd->msg_ctx)
			wpa_msg_no_global(hapd->msg_ctx_parent, MSG_INFO,
					  AP_STA_CONNECTED "%s", buf);

		sta->flags |= WLAN_STA_AUTHORIZED;
	} else {
		wpa_msg(hapd->msg_ctx, MSG_INFO, AP_STA_DISCONNECTED "%s", buf);

		if (hapd->msg_ctx_parent &&
		    hapd->msg_ctx_parent != hapd->msg_ctx)
			wpa_msg_no_global(hapd->msg_ctx_parent, MSG_INFO,
					  AP_STA_DISCONNECTED "%s", buf);

		sta->flags &= ~WLAN_STA_AUTHORIZED;
	}

	if (hapd->sta_authorized_cb)
		hapd->sta_authorized_cb(hapd->sta_authorized_cb_ctx,
					sta->addr, authorized, dev_addr);
}

一开始只知道此处会有消息发送,但是不知道是怎样发送了,仔细看了一下wpa_msg()函数的原型,才搞明白了。

void wpa_msg(void *ctx, int level, const char *fmt, ...)

{

va_list ap;

char *buf;

int buflen;

int len;

char prefix[130];

va_start(ap, fmt);

buflen = vsnprintf(NULL, 0, fmt, ap) + 1;

va_end(ap);

buf = os_malloc(buflen);

if (buf == NULL) {

wpa_printf(MSG_ERROR, "wpa_msg: Failed to allocate message "

"buffer");

return;

}

wpa_printf(MSG_DEBUG, "@@@@wpa_msg");

va_start(ap, fmt);

prefix[0] = ‘\0‘;

if (wpa_msg_ifname_cb) {

const char *ifname = wpa_msg_ifname_cb(ctx);

if (ifname) {

int res = os_snprintf(prefix, sizeof(prefix), "%s: ",

ifname);

if (res < 0 || res >= (int) sizeof(prefix))

prefix[0] = ‘\0‘;

}

}

len = vsnprintf(buf, buflen, fmt, ap);

va_end(ap);

wpa_printf(level, "%s%s", prefix, buf);

if (wpa_msg_cb){

wpa_printf(MSG_DEBUG, "@@@@wpa_msg_cb");

wpa_msg_cb(ctx, level, 0, buf, len);

}

os_free(buf);

}

注意后面全局变量wpa_msg_cb, 在hostapd_ctrl_iface_init(struct hostapd_data *hapd)中,初始化的时候,可以看到这么一行代码:

 
hapd->msg_ctx = hapd;

wpa_msg_register_cb(hostapd_ctrl_iface_msg_cb);

这里注册了一个回调hostapd_ctrl_iface_msg_cb(void *ctx, int level, int global, const char *txt, size_t len),这函数调用了hostapd_ctrl_iface_send(.....)。

而这个和wpa_msg_cb有什么关系呢?那你就得看看wpa_msg_register_cb了,原来它是将注册的回调函数指针赋给了wpa_msg_cb,那么wpa_msg_cb相当于

最终调用了hostapd_ctrl_iface_send用于发送消息啦。

       void wpa_msg_register_cb(wpa_msg_cb_func func)

{

wpa_msg_cb = func;

}

以上就是整个framework接收到接入客户端连接成功,并且获取客户端信息的流程,不过我原本看的时候,是从下往上看的,先看了hostapd发送消息的部分,再看的上层。

时间: 2024-10-01 14:32:23

Android热点连接管理(一)的相关文章

Android自动连接指定的wifi,免密码或指定密码

一.运行时的状态 遇到一个这样的要求:“不进行扫描操作,怎么对指定的免密码WIFI进行连接(之前没有连接过)”,于是动手写了一个Demo,如图所示未连接成功时的状态,第一个编辑框让用户输入SSID,第二个编辑框输入密码,密码可以根据实例情况输入,也可以不输入密码,因为有些Wifi免密码.这里的免密码不是指可以破解wifi密码.注意图片中手机顶部的wifi图标,是没有的,说明此时并没有打开手机的wifi.在手机上运行状态如下所示: 输入SSID,点击连接后的状态,当手机的wifi没有打开时,程序将

android 网络连接 wifi gprs的连接

package com.example.androidday15_network1; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.net.NetworkInfo.State; import android.os.Bundle; import andr

Android_ConnectivityManager连接管理

ConnectivityManager ConnectivityManager作为安卓网络连接管理类,主要功能如下: 1. 通知应用网络状态的改变,发送广播 ACTION:CONNECTIVITY_ACTION 2. WiFi,GPRS等网络的连接管理(是否可用,连接状态等) 3. 提供了一种api来让应用去请求或是选择网络来进行数据的传输 需要的权限: <uses-permission android:name="android.permission.ACCESS_NETWORK_STA

Android 热点相关操作

Android未提供对该API的直接访问, 需要使用反射, 代码较简单, 如下 GetHotspotState.java package club.seliote.hotspotscanner.utils; import android.content.Context; import android.net.wifi.WifiManager; import java.lang.reflect.Method; /** * 用于获取热点状态 */ public class GetHotspotSta

树莓派安装系统 ssh vnc之手机热点连接

首先在树莓派官网下载镜像 https://www.raspberrypi.org/downloads/ 下载适合的版本 这里下载的是raspbian系统插入SD卡 建议用SDformatter软件格式化FAT格式 然后用WIN32 disk imager软件镜像的位置复制到左边 右边选择U盘位置 注意最好镜像路径不带中文 完成后系统其实已经装好   SD卡插入树莓派,通过HDIM连接显示器开机就可以了 第二部  没有显示器 通过手机热点连接ssh vnc控制系统 1.向boot文件夹下新建ssh

Android设备连接Unity Profiler性能分析器

Unity提供两种方式让Developer的Android设备连接Profiler进行性能分析: 1.通过wifi,Android设备和计算机处于同一个Wlan中. 2.通过USB ADB 一般情况我们的计算机都是网线,所以我们采用ADB的方式.相比与wifi,ADB也更及时的反应设备性能. 官方的英文文档如下: http://docs.unity3d.com/Manual/Profiler.html For ADB profiling, follow these steps: Attach y

Android的软件包管理服务PackageManagerService源码分析

Android系统下的apk程序都是通过名为PackageManagerService的包管理服务来管理的.PacketManagerService是安卓系统的一个重要服务,由SystemServer启动,主要实现apk程序包的解析,安装,更新,移动,卸载等服务.不管是系统apk(/system/app),还是我们手工安装上去的,系统所有的apk都是由其管理的. 以android 4.0.4的源码为例,android4.0.4/frameworks/base/services/java/com/

Android代码连接Wifi时被系统切换到其他Wifi的问题

首先说下Android代码连接Wifi的几个步骤:(以下涉及到具体API函数自查哈,写的时候凭借印象大致写了下) 转载请注明出处: 胖虎:http://blog.csdn.net/ljphhj 1.首先要开启Wifi连接开关,mWifiManager.setWifiEnabled(true) 2.通过获取List<ScanResult>来获取到Wifi连接列表.(mWifiManager.getScanResults) 3.获取List<WifiConfiguration>列表.(

android 进程/线程管理(四)续----消息机制的思考(自定义消息机制)

继续分析handler 和looper 先看看handler的 public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } 所以消息的处理分层三种,就是 1.传入一