首页 文章 精选 留言 我的

精选列表

搜索[代理式开发],共10000篇文章
优秀的个人博客,低调大师

【移动开发】Android中WIFI开发总结(二)

搞了好几天终于有点眉目了,这里接着总结一下Android中WiFi的使用,在前面(http://smallwoniu.blog.51cto.com/3911954/1334951)我们已经简单了解了Wifi实际操作中所用到的几个主要类,今天就剖析一个WIFI搜索、创建、关闭、连接及一些简单的动画的综合案例。 首先,我们还是看一下我们项目的框架 MainActivity类:(处理wifi消息,加载UI) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 /** * 主界面 * @author ZHF * */ public class MainActivity extends Activity implements EventHandler{ //消息事件 public static final int m_nWifiSearchTimeOut = 0 ; // 搜索超时 public static final int m_nWTScanResult = 1 ; // 搜索到wifi返回结果 public static final int m_nWTConnectResult = 2 ; // 连接上wifi热点 public static final int m_nCreateAPResult = 3 ; // 创建热点结果 public static final int m_nUserResult = 4 ; // 用户上线人数更新命令(待定) public static final int m_nWTConnected = 5 ; // 点击连接后断开wifi,3.5秒后刷新adapter //一些常量 public static final String PACKAGE_NAME = "com.zhf.wifidemo.wifi" ; //应用包名 public static final String FIRST_OPEN_KEY = "version" ; //版本号信息 public static final String WIFI_AP_HEADER = "zhf_" ; public static final String WIFI_AP_PASSWORD = "zhf12345" ; //wifi操作事件(枚举)-->方便弹出对话框处理事件 private int wFOperateEnum = WFOperateEnum.NOTHING; //三个重要的类 public WFSearchProcess m_wtSearchProcess; //WiFi搜索进度条线程 public WifiAdmin m_wiFiAdmin; //Wifi管理类 public CreateAPProcess m_createAPProcess; //创建Wifi热点线程 //相关控件 private WFSearchAnimationFrameLayout m_FrameLWTSearchAnimation; //自定义雷达动画布局 private GifView m_gifRadar; //wifi信号动画布局 private LinearLayout m_linearLCreateAP; //创建热点View private ProgressBar m_progBarCreatingAP; //创建热点进度条 private TextView m_textVPromptAP; //创建热点进度条文字 private Button m_btnBack; //左上角返回按钮 private Button m_btnSearchWF; //右上角wifi搜索按钮 private Button m_btnCreateWF; //创建wifi热点 private ListView m_listVWT; //显示信息 private LinearLayout m_LinearLDialog; //提醒对话框 private TextView m_textVContentDialog; //对话框文本内容 private Button m_btnConfirmDialog, m_btnCancelDialog; //提醒对话框上的按钮 private TextView m_textVWTPrompt; //中间文字提示 private boolean m_isFirstOpen = true ; //标记是否是第一次打开 ArrayList<ScanResult> m_listWifi = new ArrayList(); //检测到热点信息列表 private WTAdapter m_wTAdapter; //网络列表适配器 public Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case m_nWifiSearchTimeOut: // 搜索超时 m_wtSearchProcess.stop(); m_FrameLWTSearchAnimation.stopAnimation(); m_listWifi.clear(); //网络列表 //设置控件 m_textVWTPrompt.setVisibility(View.VISIBLE); m_textVWTPrompt.setText( "需要重新搜索,点右上角重新搜索或创建新的热点..." ); break ; case m_nWTScanResult: //扫描到结果 m_listWifi.clear(); if (m_wiFiAdmin.mWifiManager.getScanResults() != null ) { for ( int i = 0 ; i < m_wiFiAdmin.mWifiManager.getScanResults().size(); i++) { ScanResult scanResult = m_wiFiAdmin.mWifiManager.getScanResults().get(i); //和指定连接热点比较,将其他的过滤掉! if (scanResult.SSID.startsWith(WIFI_AP_HEADER)) { m_listWifi.add(scanResult); } } if (m_listWifi.size() > 0 ) { m_wtSearchProcess.stop(); m_FrameLWTSearchAnimation.stopAnimation(); m_textVWTPrompt.setVisibility(View.GONE); //更新列表,显示出搜索到的热点 m_wTAdapter.setData(m_listWifi); m_wTAdapter.notifyDataSetChanged(); } } break ; case m_nWTConnectResult: //连接结果 m_wTAdapter.notifyDataSetChanged(); //刷新适配器数据 break ; case m_nCreateAPResult: //创建wifi热点结果 m_createAPProcess.stop(); m_progBarCreatingAP.setVisibility(View.GONE); //旋转进度条 if ((m_wiFiAdmin.getWifiApState() == 3 || m_wiFiAdmin.getWifiApState() == 13 ) && (m_wiFiAdmin.getApSSID().startsWith(WIFI_AP_HEADER))) { //设置控件 m_textVWTPrompt.setVisibility(View.GONE); m_linearLCreateAP.setVisibility(View.VISIBLE); m_btnCreateWF.setVisibility(View.VISIBLE); m_gifRadar.setVisibility(View.VISIBLE); m_btnCreateWF.setBackgroundResource(R.drawable.x_ap_close); m_textVPromptAP.setText( "热点创建成功!" + "\n热点名:" + m_wiFiAdmin.getApSSID()+ "\n连接密码:zhf12345" ); } else { m_btnCreateWF.setVisibility(View.VISIBLE); m_btnCreateWF.setBackgroundResource(R.drawable.x_wt_create); m_textVPromptAP.setText( "热点创建失败,您可以重新创建或者搜索其它热点" ); } break ; case m_nUserResult : //更新用户上线人数 break ; case m_nWTConnected: //点击连接后断开wifi,3.5s后刷新 m_wTAdapter.notifyDataSetChanged(); break ; } }; }; @Override public void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.wt_main); //搜索Wifi m_wtSearchProcess = new WFSearchProcess( this ); //创建Wifi热点 m_createAPProcess = new CreateAPProcess( this ); //wifi管理类 m_wiFiAdmin = WifiAdmin.getInstance( this ); //初始化View initView(); } /**初始化View**/ private void initView() { // 监听广播 WIFIBroadcast.ehList.add( this ); /******************************实例化布局**************************************/ m_linearLCreateAP = (LinearLayout) findViewById(R.id.create_ap_llayout_wt_main); //创建热点View m_progBarCreatingAP = (ProgressBar) findViewById(R.id.creating_progressBar_wt_main); //创建热点进度条 m_textVPromptAP = (TextView) findViewById(R.id.prompt_ap_text_wt_main); //创建热点进度条文字 m_FrameLWTSearchAnimation = ((WFSearchAnimationFrameLayout) findViewById(R.id.search_animation_wt_main)); // 搜索时的动画 m_listVWT = ((ListView) findViewById(R.id.wt_list_wt_main)); // 搜索到的热点listView //注意此处 m_wTAdapter = new WTAdapter( this , m_listWifi); m_listVWT.setAdapter(m_wTAdapter); m_textVWTPrompt = (TextView) findViewById(R.id.wt_prompt_wt_main); //中间提醒文字 m_gifRadar = (GifView) findViewById(R.id.radar_gif_wt_main); //gif动画 //提醒对话框布局 m_LinearLDialog = (LinearLayout) findViewById(R.id.dialog_layout_wt_main); m_textVContentDialog = (TextView) findViewById(R.id.content_text_wtdialog); m_btnConfirmDialog = (Button) findViewById(R.id.confirm_btn_wtdialog); m_btnCancelDialog = (Button) findViewById(R.id.cancel_btn_wtdialog); //左上角返回键 m_btnBack = (Button) findViewById(R.id.back_btn_wt_main); m_btnBack.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { onBackPressed(); //相当于调用系统返回键,结束当前Activity } }); //右上角搜索热点按钮 m_btnSearchWF = (Button) findViewById(R.id.search_btn_wt_main); m_btnSearchWF.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { if (!m_wtSearchProcess.running) { //搜索线程没有开启 //1.当前热点或wifi连接着 WIFI_STATE_ENABLED 3 //WIFI_AP_STATE_ENABLED 13 if (m_wiFiAdmin.getWifiApState() == 3 || m_wiFiAdmin.getWifiApState() == 13 ) { wFOperateEnum = WFOperateEnum.SEARCH; //搜索wifi事件 m_LinearLDialog.setVisibility(View.VISIBLE); ///wifi提示对话框显示 m_textVContentDialog.setText( "是否关闭当前热点去搜索其他热点?" ); return ; //跳出此方法,交由对话框来处理事件 } //2.当前没有热点或wifi连接着 if (!m_wiFiAdmin.mWifiManager.isWifiEnabled()) { //如果wifi没打开 m_wiFiAdmin.OpenWifi(); } m_textVWTPrompt.setVisibility(View.VISIBLE); //中间提示文字 m_textVWTPrompt.setText( "正在搜索附近的热点..." ); m_linearLCreateAP.setVisibility(View.GONE); //创建wifi热点布局消失 m_gifRadar.setVisibility(View.GONE); //热点连接动画消失 m_btnCreateWF.setBackgroundResource(R.drawable.x_wt_create); //更改按钮文字“创建” //开始搜索wifi m_wiFiAdmin.startScan(); m_wtSearchProcess.start(); //开启搜索线程 m_FrameLWTSearchAnimation.startAnimation(); //开启波纹动画 } else { //搜索线程开启着,再次点击按钮 //重新启动 m_wtSearchProcess.stop(); m_wiFiAdmin.startScan(); //开始搜索wifi m_wtSearchProcess.start(); } } }); //中间创建wifi热点按钮 m_btnCreateWF = (Button) findViewById(R.id.create_btn_wt_main); m_btnCreateWF.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { if (m_wiFiAdmin.getWifiApState() == 4 ) { // WIFI_STATE_UNKNOWN Toast.makeText(getApplicationContext(), "您的设备不支持热点创建!" , Toast.LENGTH_SHORT).show(); return ; } if (m_wiFiAdmin.mWifiManager.isWifiEnabled()) { //目前连着wifi wFOperateEnum = WFOperateEnum.CREATE; //wifi热点创建事件 m_LinearLDialog.setVisibility(View.VISIBLE); //对话框可用 m_textVContentDialog.setText( "创建热点会关闭当前的WiFi,确认继续?" ); return ; } if ((m_wiFiAdmin.getWifiApState() == 3 || m_wiFiAdmin.getWifiApState() == 13 ) && (!m_wiFiAdmin.getApSSID().startsWith(WIFI_AP_HEADER))) { //目前连接着其他热点名 wFOperateEnum = WFOperateEnum.CREATE; //wifi热点创建事件 m_LinearLDialog.setVisibility(View.VISIBLE); m_textVContentDialog.setText( "系统热点被占用,点确定开启热点以传输文件!" ); return ; } if (((m_wiFiAdmin.getWifiApState() == 3 ) || (m_wiFiAdmin.getWifiApState() == 13 )) && (m_wiFiAdmin.getApSSID().startsWith(WIFI_AP_HEADER))) { //目前连接着自己指定的Wifi热点 wFOperateEnum = WFOperateEnum.CLOSE; //wifi热点关闭事件 m_LinearLDialog.setVisibility(View.VISIBLE); m_textVContentDialog.setText( "关闭热点会中断当前传输,您确定这么做?" ); return ; } if (m_wtSearchProcess.running) { m_wtSearchProcess.stop(); //停止线程 m_FrameLWTSearchAnimation.stopAnimation(); //停止动画 } /******************点击创建热点时没有连接wifi或热点的情况*****************************/ //关闭Wifi m_wiFiAdmin.closeWifi(); //创建热点(名字,密码,加密类型,wifi/ap类型) m_wiFiAdmin.createWifiAP(m_wiFiAdmin.createWifiInfo(WIFI_AP_HEADER + getLocalHostName(), WIFI_AP_PASSWORD, 3 , "ap" ), true ); m_createAPProcess.start(); //开启创建热点线程 //将wifi信息列表设置到listview中 m_listWifi.clear(); m_wTAdapter.setData(m_listWifi); m_wTAdapter.notifyDataSetChanged(); //设置布局 m_linearLCreateAP.setVisibility(View.VISIBLE); //热点布局可用 m_progBarCreatingAP.setVisibility(View.VISIBLE); m_textVPromptAP.setText( "正在创建热点" ); //进度条文字 m_btnCreateWF.setVisibility(View.GONE); //点击一次不可再点 m_textVWTPrompt.setVisibility(View.GONE); } }); //对话框确认按钮 m_btnConfirmDialog.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { m_LinearLDialog.setVisibility(View.GONE); //让对话框布局消失 switch (wFOperateEnum) { //根据wifi操作事件 case WFOperateEnum.CLOSE: //关闭wifi热点 //设置布局 m_textVWTPrompt.setVisibility(View.VISIBLE); m_textVWTPrompt.setText( "热点已关闭!可以进行其他操作了!" ); //中间提醒文字 m_btnCreateWF.setBackgroundResource(R.drawable.x_wt_create); //按钮文字改回“创建” m_gifRadar.setVisibility(View.GONE); //热点动画停止 m_linearLCreateAP.setVisibility(View.GONE); //下部创建热点布局不可用 //关闭热点 m_wiFiAdmin.createWifiAP(m_wiFiAdmin.createWifiInfo(m_wiFiAdmin.getApSSID(), "zhf123456" , 3 , "ap" ), false ); break ; case WFOperateEnum.CREATE: //创建wifi热点 if (m_wtSearchProcess.running) { m_wtSearchProcess.stop(); //搜索wifi线程停止 m_FrameLWTSearchAnimation.stopAnimation(); //搜索wifi动画停止 } //关闭wifi m_wiFiAdmin.closeWifi(); //创建WiFi热点 m_wiFiAdmin.createWifiAP(m_wiFiAdmin.createWifiInfo(WIFI_AP_HEADER + getLocalHostName(), WIFI_AP_PASSWORD, 3 , "ap" ), true ); m_createAPProcess.start(); //刷新listView布局 m_listWifi.clear(); m_wTAdapter.setData(m_listWifi); m_wTAdapter.notifyDataSetChanged(); //设置布局 m_linearLCreateAP.setVisibility(View.VISIBLE); m_progBarCreatingAP.setVisibility(View.VISIBLE); //旋转进度条 m_btnCreateWF.setVisibility(View.GONE); m_textVWTPrompt.setVisibility(View.GONE); m_textVPromptAP.setText( "正在创建热点..." ); //进度条文字 break ; case WFOperateEnum.SEARCH: //搜索可用热点 //设置布局 m_textVWTPrompt.setVisibility(View.VISIBLE); m_textVWTPrompt.setText( "正在搜索附近的热点..." ); m_linearLCreateAP.setVisibility(View.GONE); //创建热点布局不可用 m_btnCreateWF.setVisibility(View.VISIBLE); m_btnCreateWF.setBackgroundResource(R.drawable.x_wt_create); //按钮文字改回“创建” m_gifRadar.setVisibility(View.GONE); //热点动画停止 m_linearLCreateAP.setVisibility(View.GONE); //下部创建热点布局不可用 //搜索热点线程关闭 if (m_createAPProcess.running) m_createAPProcess.stop(); //关闭热点 m_wiFiAdmin.createWifiAP(m_wiFiAdmin.createWifiInfo(m_wiFiAdmin.getApSSID(), WIFI_AP_PASSWORD, 3 , "ap" ), false ); //打开wifi m_wiFiAdmin.OpenWifi(); m_wtSearchProcess.start(); m_FrameLWTSearchAnimation.startAnimation(); break ; } } }); //对话框取消按钮事件 m_btnCancelDialog.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { //对话框布局消失 m_LinearLDialog.setVisibility(View.GONE); } }); } /**Wifi是否连接**/ private boolean isWifiConnect() { boolean isConnect = true ; //用于网络连接状态的检测 ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); if (!cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected()) isConnect = false ; return isConnect; } /**获取wifi热点状态**/ public boolean getWifiApState() { try { WifiManager localWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); Method m = localWifiManager.getClass().getMethod( "getWifiApState" , new Class[ 0 ]); int i = (Integer)(m.invoke(localWifiManager, new Object[ 0 ])); return ( 3 == i) || ( 13 == i); //WIFI_STATE_ENABLED 3 //WIFI_AP_STATE_ENABLED 13 } catch (Exception e) { e.printStackTrace(); } return false ; } /**获取手机信息**/ public String getLocalHostName() { String str1 = Build.BRAND; //主板 String str2 = Build.MODEL; //机型 if (- 1 == str2.toUpperCase().indexOf(str1.toUpperCase())) str2 = str1 + "_" + str2; return str2; } private void init() { //线程是否在运行 if ( this .m_wtSearchProcess.running || this .m_createAPProcess.running) { return ; } //没有连接上wifi或者是wifi热点 if (!isWifiConnect() && !getWifiApState()) { m_wiFiAdmin.OpenWifi(); m_wtSearchProcess.start(); //开启搜索wifi超时检测线程 m_wiFiAdmin.startScan(); //开启搜索wifi //开启搜索动画 m_FrameLWTSearchAnimation.startAnimation(); //设置控件 m_textVWTPrompt.setVisibility(View.VISIBLE); m_textVWTPrompt.setText( " 正在搜索附近的热点..." ); m_linearLCreateAP.setVisibility(View.GONE); m_btnCreateWF.setBackgroundResource(R.drawable.x_wt_create); } //连接上wifi if (isWifiConnect()) { this .m_wiFiAdmin.startScan(); this .m_wtSearchProcess.start(); this .m_FrameLWTSearchAnimation.startAnimation(); //设置控件 this .m_textVWTPrompt.setVisibility( 0 ); this .m_textVWTPrompt.setText( "正在搜索附近的热点..." ); this .m_linearLCreateAP.setVisibility(View.GONE); this .m_btnCreateWF.setBackgroundResource(R.drawable.x_wt_create); this .m_gifRadar.setVisibility(View.GONE); m_listWifi.clear(); if (m_wiFiAdmin.mWifiManager.getScanResults() != null ) { for ( int i = 0 ; i < m_wiFiAdmin.mWifiManager.getScanResults().size(); i++) { //识别出自己自定连接的wifi if (m_wiFiAdmin.mWifiManager.getScanResults().get(i).SSID.startsWith(WIFI_AP_HEADER)) { m_listWifi.add(m_wiFiAdmin.mWifiManager.getScanResults().get(i)); //将指定wifi添加进去 } } m_wTAdapter.setData(m_listWifi); //将连接的信息添加到listView中 m_wTAdapter.notifyDataSetChanged(); } //连接上wifi热点 if (getWifiApState()){ if (m_wiFiAdmin.getApSSID().startsWith(WIFI_AP_HEADER)) { //设置控件 m_textVWTPrompt.setVisibility(View.GONE); m_linearLCreateAP.setVisibility(View.VISIBLE); m_progBarCreatingAP.setVisibility(View.GONE); m_btnCreateWF.setVisibility(View.VISIBLE); m_gifRadar.setVisibility(View.VISIBLE); m_btnCreateWF.setBackgroundResource(R.drawable.x_ap_close); m_textVPromptAP.setText( "\n热点名:" + m_wiFiAdmin.getApSSID() + "\n连接密码:zhf12345" ); } } } } @Override protected void onDestroy() { // TODO Auto-generated method stub super .onDestroy(); WIFIBroadcast.ehList.remove( this ); } @Override public void handleConnectChange() { Message msg = mHandler.obtainMessage(m_nWTConnectResult); mHandler.sendMessage(msg); } @Override public void scanResultsAvaiable() { Message msg = mHandler.obtainMessage(m_nWTScanResult); mHandler.sendMessage(msg); } @Override public void wifiStatusNotification() { m_wiFiAdmin.mWifiManager.getWifiState(); //获取当前wifi状态 } } 貌似有点长。。。简单说明一下: 1.首先,初始化主界面(xml有些复杂,慢慢理一下就好了,不是什么大问题)接下来,处理三个重要的按钮事件:右上角的搜索wifi按钮、中间创建或关闭热点按钮、对话框中确定按钮。处理前两者事件时,需关注当前wifi状态,若为占用,需弹出提醒对话框来根据当前WiFi操作(WFOperateEnum类中定义好了)事件,来创建、关闭、搜索等具体事务。 2.之所以定义广播接收者,是为了监听当wifi状态发生变化时,能够及时更新界面 广播接收者WIFIBroadcast类: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package com.zhf.wifidemo.broadcast; import java.util.ArrayList; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.net.wifi.WifiManager; import android.util.Log; /** * 监听wifi热点变化 * @author ZHF * */ public class WIFIBroadcast extends BroadcastReceiver{ public static ArrayList<EventHandler> ehList = new ArrayList<EventHandler>(); @Override public void onReceive(Context context, Intent intent) { //搜索到wifi热点结果的广播: "android.net.wifi.SCAN_RESULTS" if (intent.getAction().endsWith(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { Log.i( "WTScanResults---->扫描到了可用网络---->" , "android.net.wifi.SCAN_RESULTS" ); //遍历通知各个监听接口 for ( int i = 0 ; i < ehList.size(); i++) { ((EventHandler)ehList.get(i)).scanResultsAvaiable(); } //wifi打开或关闭状态变化 "android.net.wifi.WIFI_STATE_CHANGED" } else if (intent.getAction().endsWith(WifiManager.WIFI_STATE_CHANGED_ACTION)) { Log.i( "WTScanResults----->wifi状态变化--->" , "android.net.wifi.WIFI_STATE_CHANGED" ); //这里不需要连接一个SSID(wifi名称) for ( int j = 0 ; j < ehList.size(); j++) { ((EventHandler)ehList.get(j)).wifiStatusNotification(); } //连接上一个SSID后发出的广播,(注:与android.net.wifi.WIFI_STATE_CHANGED的区别) } else if (intent.getAction().endsWith(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { Log.i( "WTScanResults----->网络状态变化---->" , "android.net.wifi.STATE_CHANGE" ); for ( int m = 0 ; m < ehList.size(); m++) { ((EventHandler)ehList.get(m)).handleConnectChange(); } } } /** * 事件监听接口 * @author ZHF * */ public static abstract interface EventHandler { /**处理连接变化事件**/ public abstract void handleConnectChange(); /**扫描结果是有效事件**/ public abstract void scanResultsAvaiable(); /**wifi状态变化事件**/ public abstract void wifiStatusNotification(); } } 说明:监听系统wifi状态变化,内部定义了一个事件处理接口,当wifi状态发生变化时,让其实现类去发送消息更新界面。 CreateAPProcess类: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 package com.zhf.wifidemo.ui; import android.net.wifi.WifiManager; import android.os.Message; import com.zhf.wifidemo.wifi.MainActivity; /** * 创建Wifi热点 * @author ZHF * */ public class CreateAPProcess implements Runnable{ public MainActivity context; public boolean running = false ; private long startTime = 0L; private Thread thread = null ; public CreateAPProcess(MainActivity context) { super (); this .context = context; } @Override public void run() { while ( true ) { if (!running) return ; //WIFI_STATE_ENABLED 3 //WIFI_AP_STATE_ENABLED 13 if ((context.m_wiFiAdmin.getWifiApState() == WifiManager.WIFI_STATE_ENABLED) || (context.m_wiFiAdmin.getWifiApState() == 13 ) || (System.currentTimeMillis() - this .startTime >= 30000L)){ //wifi可用 或 热点可用 Message msg = context.mHandler.obtainMessage(context.m_nCreateAPResult); context.mHandler.sendMessage(msg); } try { Thread.sleep(5L); } catch (Exception e) { e.printStackTrace(); } } } public void start() { try { thread = new Thread( this ); running = true ; startTime = System.currentTimeMillis(); thread.start(); //开启线程 } catch (Exception e) { e.printStackTrace(); } } public void stop() { try { running = false ; thread = null ; startTime = 0L; } catch (Exception e) { // TODO: handle exception } } } 其他类文件具体看源码,当创建一个热点成功后,等待其他客户端搜索 上图: WIFIAdapte类: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 /** * 网络列表适配器 * @author ZHF * */ public class WTAdapter extends BaseAdapter { private LayoutInflater mInflater; private List<ScanResult> mList; //扫描到的网络结果列表 private MainActivity mContext; public WTAdapter(MainActivity context, List<ScanResult> list) { // TODO Auto-generated constructor stub this .mContext = context; this .mList = list; this .mInflater = LayoutInflater.from(context); } //新加的一个函数,用来更新数据 public void setData(List<ScanResult> list) { this .mList = list; notifyDataSetChanged(); } @Override public int getCount() { return mList.size(); } @Override public Object getItem( int position) { return mList.get(position); } @Override public long getItemId( int position) { return position; } @Override public View getView( final int position, View convertView, ViewGroup parent) { //获取点击向的扫描结果 final ScanResult localScanResult = mList.get(position); //获取wifi类 // final WifiAdmin wifiAdmin = WifiAdmin.getInstance(mContext); final WifiAdmin wifiAdmin = mContext.m_wiFiAdmin; final ViewHolder viewHolder; if (convertView == null ) { viewHolder = new ViewHolder(); convertView = mInflater.inflate(R.layout.wtitem, null ); //加载布局模板控件 viewHolder.textVName = ((TextView) convertView.findViewById(R.id.name_text_wtitem)); viewHolder.textConnect = ((TextView) convertView.findViewById(R.id.connect_text_wtitem)); viewHolder.linearLConnectOk = ((LinearLayout) convertView.findViewById(R.id.connect_ok_layout_wtitem)); viewHolder.progressBConnecting = ((ProgressBar) convertView.findViewById(R.id.connecting_progressBar_wtitem)); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } //点击连接处理事件 viewHolder.textConnect.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { //创建wifi网络 WifiConfiguration localWifiConfiguration = wifiAdmin.createWifiInfo(localScanResult.SSID, MainActivity.WIFI_AP_PASSWORD, 3 , "wt" ); //添加到网络 wifiAdmin.addNetwork(localWifiConfiguration); //"点击链接"消失,显示进度条, viewHolder.textConnect.setVisibility(View.GONE); viewHolder.progressBConnecting.setVisibility(View.VISIBLE); viewHolder.linearLConnectOk.setVisibility(View.GONE); //点击后3.5s发送消息 mContext.mHandler.sendEmptyMessageDelayed(mContext.m_nWTConnected, 3500L); } }); // 点击断开处理事件 viewHolder.linearLConnectOk.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { //断开指定wifi热点 wifiAdmin.disconnectWifi(wifiAdmin.getWifiInfo().getNetworkId()); //"断开连接"消失,进度条显示 viewHolder.textConnect.setVisibility(View.GONE); viewHolder.progressBConnecting.setVisibility(View.VISIBLE); viewHolder.linearLConnectOk.setVisibility(View.GONE); //点击后3.5s发送消息 mContext.mHandler.sendEmptyMessageDelayed(mContext.m_nWTConnected, 3500L); } }); //初始化布局 viewHolder.textConnect.setVisibility(View.GONE); viewHolder.progressBConnecting.setVisibility(View.GONE); viewHolder.linearLConnectOk.setVisibility(View.GONE); viewHolder.textVName.setText(localScanResult.SSID); //显示热点名称 // 正连接的wifi信息 WifiInfo localWifiInfo = wifiAdmin.getWifiInfo(); if (localWifiInfo != null ) { try { //正在连接 if ((localWifiInfo.getSSID() != null )&& (localWifiInfo.getSSID().equals(localScanResult.SSID))) { viewHolder.linearLConnectOk.setVisibility(View.VISIBLE); return convertView; } } catch (NullPointerException e) { e.printStackTrace(); return convertView; } viewHolder.textConnect.setVisibility(View.VISIBLE); } return convertView; } public final class ViewHolder { public LinearLayout linearLConnectOk; public ProgressBar progressBConnecting; public TextView textConnect; public TextView textVName; } } 点击连接,实现客户连接,此时他们将处于同一网段内,接下来就可以自己实现传输数据,操控等一系列操作了。 在下一篇中我会继实现一个基于wif热点的多人聊天室,希望能帮助到大家! 源码:http://down.51cto.com/data/1036876 本文转自zhf651555765 51CTO博客,原文链接:http://blog.51cto.com/smallwoniu/1338618,如需转载请自行联系原作者

优秀的个人博客,低调大师

零代码,两次发版:一份跑挂了的报告就是贡献

我这个项目上次被外部力量推动,是一位贡献者提交了完整的测量数据——一份 schema 校验通过的 energy.json,来自别人的 GPU。而昨天,另一位复现者把项目推得更远,方向却完全相反:**他的脚本从头到尾没跑起来过**。没提交数据,没提交代码。一天之内,他那条死掉的脚本、两条脚注、外加一句困惑,换来了**两次发布**,还堵上了我们以为已经修完的最后一个 bug 活路径。 ## 上午:一条六十秒就死掉的脚本 他是被请来做复现的,干净的 Ubuntu 22.04 沙箱,暂时没有 GPU。安全模型端到端都站住了:站点 stub 从钉死的 release 下载了 wrapper,SHA-256 校验**通过**。然后 bash 读到第一行就死了: 调试过 Windows 上写出来的 shell 脚本的人,都认识这行末尾那个看不见的字符。我们发布过的每一个 `*.sh`——wrapper、quickstart.sh 资产、站点 stub 本身——全都带着 CRLF 行尾,Linux bash 把 `pipefail\r` 当成了一个不存在的选项名。 **校验和之所以通过,是因为校验和本来就是对着 CRLF 版本算的:完整性和正确性是两个不同的性质,一对自洽的错误文件能畅通无阻地通过前者的检查。** 当天全层修复:仓库所有脚本转 LF、加了一条 CI 守卫(此后任何 CRLF shell 脚本直接 fail)、发布新的钉死 release run-v2(run-v1 保留作墓碑)、r4 资产重新发布——并且顺手抓到一个潜伏错误:SHA256SUMS.all 里 schema 那一行的哈希从来就没对上过 tag。全部记录在 changelog。 ## 下午:验证通过了,但更重要的是那句话 他带着 v2 报告回来,逐项验证修复——十项检查,全绿: - stub 行尾、是否钉住 run-v2 的校验和 —— 通过 - run-v2 哈希与 stub 硬编码值一致、`bash -n` 语法检查、`--dry-run` 输出执行计划 —— 通过 - r4 quickstart.sh:LF、校验和、语法 —— 通过 - SHA256SUMS.all 全部三行,从 tag 独立重算 —— 通过 - .gitattributes 规则与 CI CRLF 守卫在位 —— 通过 一份验证报告,恰恰是单人项目永远无法为自己生产的东西:报 bug 的人自己独立确认了 bug 已死。光是这一条,他这一天就值了。 但报告里还有一句,归在"两个小建议"下面: > "quickstart.sh 头部注释还写着 schema-1.3-r3" —— 他以为是个过时的示例注释,装饰性问题。 我核对了线上 r4 资产和仓库 main 分支:都写着 r4。我的第一诊断是**他那边下载到了缓存**。 **错了。** 他手上有一条能正常下载的路径——说明某处还在发 r3 的文件。顺着这条线索而不是把它归档为"用户环境问题",追到了真正的源头:**README 的 Quick start 下载命令还指向 r3 release——而 r3 的资产正是 CRLF 原版。** 任何人从 README 顶部照着复制粘贴,都会在一台和任何缓存都无关的机器上,原样复现他上午的 bug。 这里值得写下来的检查清单是:"我们修好了 bug"这句话通常会留下这个洞:**活在一堆发布产物里的 bug,在产物里被修好时,所有指向这些产物的文档也得逐一核对**——release 资产、脚本头注释、README、站点页面。四个地方,我们查了三个。 ## 晚上:三处小修,一处永久 他的两条实质建议都采纳了,连同 README 修复一起发布: 1. **Quick start 现在钉 r4**——通往 CRLF bug 的最后一条活路径关闭; 1. **quickstart.sh 和 README 写明 `sha256sum -c SHA256SUMS.all` 必须在哪里跑**——对应 tag 的 clone 根目录,不是 release 下载目录,因为清单里是仓库相对路径。这个坑他自己第一轮验证就踩过,而且他的诊断完全正确:记录为使用说明,而不是 bug; 1. **CONTRIBUTING 新增发版 checklist,第一条规则就是他的建议**:用用户的下载方式拿到发布字节,在干净的 Linux 容器里 dry-run——正是当初让 CRLF 溜出去的那个缺口。这条规则现在是永久的,它存在的理由就是他的 bug。 ## 什么算贡献 这部分值得说出来,因为它决定一个单人项目能吸引到什么。 到目前为止,他贡献的是:一次失败的运行、一条关于校验命令该在哪执行的脚注、一句归因错误但症状正确的困惑。没有代码,没有测量。在默认定义下——那个数 commit 和数据集的定义下——什么都不算。 **在默认定义下,这个项目最有价值的两次外部输入都是不可见的**:这一次,和八月的一次笔记本测量。前者从来没产出过一个数字;后者一个 run 产出了一个数字。它们的共同点是:项目之外的人**真的去做了那件事,并且如实报告了发生的一切**——包括那些看起来像自己失误的部分。一条死掉的脚本是信息。一个看起来像缓存的诡异下载是信息。大多数用户撞上任何一种都会直接离开,维护者永远不知道入口的漏斗是坏的。 对维护者,操作层面的教训同样短:**把每一份"跑不起来"的报告当成免费审计,把报告里每一个奇怪的细节当成线索对待,直到证明它不是。** 我一开始把他那句 r3 归档成了他那边的缓存问题。去查证的成本是五分钟。不查的代价,是每一个顺着 README 走进来的用户,在 r3 链接的余生里。 ## 做下一个——尤其是跑挂了的那位 他的 GPU 复现已经排上日程:energy.json 生成、schema 校验、overlay 叠加、与数据集 v1.1.0 的同配置比对。等它落地,那将是任何定义下都算数的贡献。 但它不需要落地才算数。**如果你跑了那条一键命令然后它死了——那本身就是报告。** 开一个 issue,写上你输入了什么、返回了什么,尤其是那些你以为是自己搞错的部分。仓库的 CONTRIBUTING 列了五种贡献方式,这篇笔记是第六种的证据:让这个项目在你面前坏掉,然后告诉一个人。 --- *注:本文涉及 EcoCompute 项目(LLM 推理能耗的实测数据与方法论,数据集见 Zenodo DOI 10.5281/zenodo.19647290)。MLCommons, MLPerf and MLCube are trademarks of MLCommons Association, used nominatively.*

优秀的个人博客,低调大师

MTA-STS 与 TLS-RPT:强化邮件传输安全的下一代协议

给域名加完一条 _mta-sts 的 TXT 记录,就以为传输加密从此锁死——结果策略文件根本没挂到 HTTPS 端点上,或 TXT 里的 id 与策略文件对不上,对端在 enforce 模式下取不到策略,邮件开始在队列里堆积,而日志里那行提示看起来又不像"证书错误",第一反应完全找错方向。资深一点的运维也会撞上另一面:策略紧急改完、DNS 也更新了,可对端还在按旧策略办事——因为策略文件早就被缓存在对方手里,调低 max_age 根本不能缩短那份已经生效的缓存寿命,撤回一条错策略比发布它难得多。 这两类故障指向同一件事:MTA-STS(RFC 8461)不是一条 DNS 记录,而是一套横跨 DNS 与 HTTPS 的双信道发布机制,外加一套由策略自己声明有效期、却无法被发布方召回的缓存结构。TLS-RPT(RFC 8460)则是配套的观测信道——它不堵任何洞,只把"洞在哪、有多大"报回来。下面按三组冲突拆开:双信道的不一致窗口、自引用缓存的不可召回性、以及与 DANE 之间那条信任锚的路线分歧。 # 一、MTA-STS 到底堵住了 STARTTLS 的哪个洞,又给自己开了什么口? 机会性 STARTTLS 的固有缺陷是:加密能力本身以明文在 SMTP 协商阶段声明——服务器在 EHLO 响应里给出"250 STARTTLS",客户端看到才决定升级。主动攻击者只要抹掉这行声明,会话就回退到明文投递,而双方都无法区分"对端不支持 STARTTLS"与"中间人删了 STARTTLS"。**MTA-STS 要堵的就是这个降级窗口:它让"必须加密"这件事本身以抗篡改的方式被获知,而不是依赖一次明文协商。**根据 RFC 8461 的定义,该协议的设计目的就是防止 TLS 降级与中间人攻击。 它落地为两条叠加防线。第一条:策略本体经 HTTPS 获取,而不是在明文 SMTP 会话里谈——攻击者抹掉 SMTP 层面的 STARTTLS 声明,抹不掉一个已经通过 DNS 预先获知、且经 HTTPS 取到的策略。第二条:在 TLS 握手之上额外要求 MX 主机证书通过 WebPKI(公共 CA 体系)验证,并与策略文件 mx 字段列出的主机名做身份比对。**"加密了"被升级为"加密给了正确的那个人",而不是"加密给了一个拿着未知证书的对端"。** 这里需要厘清一个定位:MTA-STS 不替换 STARTTLS。会话流程仍然是先明文建连、再升级 TLS,被强化的是"升级失败后能不能以明文继续"这个决策点。**它是叠加在 STARTTLS 之上的一层策略约束,不是替换传输层的新协议。**把它理解成"邮件加密方案"是职责混淆——它保护的是"发送方→接收方这一跳"的传输安全,不涉及邮件内容加密(那是 S/MIME 或 OpenPGP 的职责),也不涉及收件人之后的任何环节。 策略分三档:enforce、testing、none。enforce 是硬约束:TLS 或证书校验不通过,发送方不得明文投递,邮件留在队列里重试;testing 是过渡档:策略照常被评估、失败会被记录并可上报 TLS-RPT,但不阻止投递;none 是关闭强制但保留策略存在性。**testing 这一档的存在,本身就是对"直接上 enforce 风险"的官方承认——直接 enforce 的代价不是静默丢弃,而是邮件延迟与队列堆积,严重时对端反复重试最终退信。** none 与"根本没有发布策略"在行为上有一条容易被忽略的边界。none 模式下,发送方确实查询到了策略、也完成了获取,只是按策略指示不强制;而未发布策略的域,发送方从不发起这次查询,直接停留在机会性加密。两者对外的投递结果相似(都允许明文),但对运维可见性的影响完全不同:none 域仍会进入发送方的 TLS-RPT 统计口径,未发布域则不会。**把一条正在排障的策略改成 none,相当于"挂着观察档下线",而不是"彻底消失"。** 最后是防护前提:MTA-STS 只对声明了策略的域生效。对未部署该协议的域,发送方仍处于机会性加密状态——协议不会替那些域凭空变出安全性。**它的保护范围以"对端支持并正确实现该协议"为边界,越出这条线,行为退回到 STARTTLS 的原始水准。** # 二、一条策略要经过几次查询才能拿到?——DNS 与 HTTPS 双信道的发布机制 MTA-STS 的策略发布横跨两条完全独立的信道。信道一是 DNS:在 _mta-sts. 下放置一条 TXT 记录,仅声明"本域存在策略"与策略版本 id,其 TTL 通常配得较短,作用是让策略变更能被较快察觉。 ```plaintext 信道一:_mta-sts TXT 记录(示意) _mta-sts.example.com. 300 IN TXT "v=STSv1; id=20260116000000;" ``` 信道二是 HTTPS:策略本体发布在 https://mta-sts./.well-known/mta-sts.txt,承载模式、允许的 MX 主机列表与缓存时长。 ```javascript 信道二:mta-sts.txt 策略文件(示意) version: STSv1 mode: enforce mx: mail.example.com mx: *.example.com max_age: 604800 ``` 两条信道必须对得上:TXT 记录里的 id 与策略文件构成有效对应关系,id 不匹配会使策略整体被判无效;mx 字段列出的主机名是发送方验证对端证书身份时的比对依据。**一次策略获取被拆成两次独立查询——先查 DNS TXT 得知"有策略"及版本号,再经 HTTPS 取策略本体,再校验 id 一致性,最后才按 mode 与 mx 列表约束投递。** 为什么不直接把策略塞进 DNS TXT?TXT 记录在体积与表达能力上受限,而策略需要承载结构化字段(多个 MX、模式、有效期)。选择 HTTPS 是为了表达力与完整性,代价是引入了第二个可用性依赖。**这个取舍的成本在 HTTPS 端点取不到时立刻显形:DNS 通告得再好,mta-sts 子域证书过期或端点 503,策略依然拿不到。**打个比方,这套结构类似包管理器的清单文件与制品仓库——DNS TXT 是清单,声明"有什么版本";HTTPS 是仓库,提供实体;仓库挂了,清单写得再漂亮也白搭。 最容易排错排错方向的,是"DNS TXT 存在但 HTTPS 取不到"这个分支在 enforce 与 testing 下的不同行为。testing 模式下,取不到就是一条失败记录,投递照常进行。enforce 模式下则要分两种历史状态:发送方此前从未成功获取过该域策略时,它手里没有可执行的策略,不构成强制依据;发送方此前已缓存过一份有效策略时,按 RFC 8461,它继续执行那份旧缓存策略,而不是"因为这次取不到就放行明文"。**反直觉的结论在这里:enforce 模式下"这次取不到策略"不等于"可以明文投递"——对已有缓存的对端,它比"根本没有策略"更严格,邮件会继续卡在 TLS 约束之后重试。**这也是初级运维最常误判的一点:日志里看到的不是证书错误,而是策略获取失败,行为却像"证书错误"一样把邮件压住了。 双信道是 MTA-STS 最核心的结构特征,也是最主要的故障面。排查"策略不生效"时,必须先定位失败落在哪一段:DNS 记录未发布、记录格式错误(v 字段不是 STSv1、id 缺失)、HTTPS 端点不可达、id 与策略文件不匹配、策略文件字段不合法。**这五类原因的排查手段完全不同——前两类查 dig,中间两类查 curl 与 TLS,最后一类才是读策略文件本身。**把所有现象都归因为"证书问题",是这类故障排错的第一大效率杀手。 还有一条潜伏的依赖链必须点破:策略经 HTTPS 获取,而 HTTPS 本身依赖 WebPKI 信任链。**这意味着 MTA-STS 的信任根最终落在公共 CA 体系上——这一点在第五节与 DANE 对比时会成为关键分水岭。** # 三、为什么"改小了 max_age"也撤不回一条错策略?——自引用缓存与生效时序 max_age 写在策略文件里,由接收方策略自己声明,表示发送方可以缓存该策略的最长时间(单位秒)。示意值 604800 对应一周量级。**这是一个自引用结构:缓存时长由被缓存的对象自己规定。发布方对自己策略的缓存行为只有"声明权",没有"撤回权"。**它像一份自己规定了保质期的食品——生产商事后改标签上的日期,并不能缩短已经卖出去、正在顾客手里那批货的保质期。 策略内容变更时,协议给出的工程解法是另一套机制:必须同步更新 DNS TXT 记录里的 id。发送方通过观察到 id 发生变化,才决定重新去 HTTPS 拉取策略。**这构成一个两层缓存模型——用"短 TTL 的 DNS 存在性声明"去驱动"长缓存期的 HTTPS 策略内容"更新:DNS TTL 管"策略还在不在、版本变没变",max_age 管"策略本体缓存多久"。**两套时间参数服务于两个不同的缓存对象,不可混为一谈。 由此推出本节最硬的一个结论:紧急情况下把 max_age 从较大值改为极小值,对已经缓存了旧策略的对端不生效。**已经存在于对端缓存里的那份策略,其寿命由旧文件里写的旧 max_age 决定,新文件里的新数值要等对端重新获取之后才谈得上生效。**撤回一条错策略,只能靠两条路:一是等旧缓存自然到期,二是变更 id 触发对端重新获取——但重新获取拉到的是新策略,不是"撤回"动作。协议里不存在"通知所有对端扔掉缓存"的信令。 一次策略变更因此必须同时动两处:策略文件本体(含新 max_age)与 DNS TXT 记录里的 id。只改策略文件不改 id,对端观察不到版本变化,继续用缓存,新内容永远不生效;只改 id 不改文件内容,对端会重新拉取到相同内容,但这一次会刷新缓存时钟——相当于把"旧策略的剩余寿命"重新拉满。**只动一处的结果,要么是变更不传播,要么是错误策略的有效期被无谓地延长了一周。** 反过来,max_age 设得过短也有代价。重取频率上升,直接带来 HTTPS 端点查询负载上升、单次获取失败概率上升,以及"取不到策略"这个状态出现的频率上升。而在 enforce 模式下,"取不到策略"恰恰是上一节那个会压住邮件的严格分支。**max_age 不是越小越安全:它在"变更传播速度"与"获取失败暴露面"之间做权衡,缩短它等于把策略可用性的波动更频繁地传导到投递链路上。** 把时序边界画清楚:从"运维决定撤回策略"到"所有对端都不再执行旧策略"之间,存在一段无法消除的延迟窗口,它由旧缓存的剩余寿命决定。**运维能做的是把这段时间在设计要求内控制住(比如上线初期就配较短 max_age、保持 id 变更纪律),而不是指望任何一个参数把它消灭掉。** # 四、TLS 连接失败了,对端怎么知道?——TLS-RPT 的报告信道与日志识别 MTA-STS 能约束发送方,但接收方自己看不到"发往本域的投递在 TLS 环节失败了多少次、因为什么失败"——除非它自己主动探测。TLS-RPT(RFC 8460,SMTP TLS Reporting)解决的就是这个可见性缺口:让支持该协议的发送方把 TLS 连接情况以聚合报告的形式回传给接收方指定的地址。 报告接收地址同样走 DNS 发现,但那是一条独立于 _mta-sts 的记录——_smtp._tls. 的 TXT 记录,使用 v=TLSRPTv1 标签与 rua 字段指定报告回传地址(mailto 或 https URI)。 ```javascript 信道三:_smtp._tls TXT 报告接收地址记录(示意) _smtp._tls.example.com. 300 IN TXT "v=TLSRPTv1; rua=mailto:reports@example.com" ``` 三条信道至此必须分清:_mta-sts TXT 是策略存在性声明,/.well-known/mta-sts.txt 是策略本体,_smtp._tls TXT 是报告回传地址——三者是独立记录,可以只配前两个不配第三个。**这意味着"部署了 MTA-STS 但没有报告通道"是一个完全合法的状态,此时接收方的可见性缺口在于:它只能看到自己主动探测的结果,看不到第三方发送方视角下真实发生的失败分布。** 报告形态是聚合的,不是逐条告警。发送方按周期(RFC 8460 建议覆盖完整 UTC 日)把统计汇总成一份 JSON,经 gzip 压缩后作为邮件附件投递(媒体类型 application/tlsrpt+gzip),里面区分成功会话计数与失败会话计数。**聚合而非逐条,是由邮件传输这个场景的量级决定的:失败频次高到逐条通知会造成告警疲劳,而报告本身也要占用邮件信道,逐条发送等于把观测流量灌进被观测的链路。** 报告里的失败分类必须以 RFC 8460 的枚举为准,不能凭印象罗列。该协议把 result-type 分成三组,瞬态错误(网络繁忙、TCP 超时等)按 RFC 规定不要求上报;成功也不是一个 result-type,而是由独立的 total-successful-session-count 字段计数。具体枚举如下: |**分组**|**result-type**|**含义(据 RFC 8460)**| | --- | --- | --- | |协商失败|starttls-not-supported|接收 MX 不支持 STARTTLS| |协商失败|certificate-host-mismatch|证书身份不满足 MTA-STS 或 DANE 策略约束,如主机名不匹配 SAN| |协商失败|certificate-expired|证书已过期| |协商失败|certificate-not-trusted|一类统称,覆盖不受信 CA、名称约束、证书链错误等;应配合 failure-reason-code 给出细因| |协商失败|validation-failure|不属于上述类别的通用验证失败,同样配合 failure-reason-code| |DANE 策略失败|tlsa-invalid|关联 DANE 策略的 TLSA 记录校验失败,RRset 中无有效记录| |DANE 策略失败|dnssec-invalid|递归解析器未返回经 DNSSEC 验证的记录| |DANE 策略失败|dane-required|发送方被配置为强制 DANE,但目标 MX 无 DNSSEC 验证的 TLSA 记录| |MTA-STS 策略失败|sts-policy-fetch-error|获取 MTA-STS 策略失败,如策略主机不可达| |MTA-STS 策略失败|sts-policy-invalid|MTA-STS 策略整体校验错误| |MTA-STS 策略失败|sts-webpki-invalid|策略本身无法通过 PKIX(WebPKI)验证| 对照这张表排日志,才能分清两个常被混为一谈的层次:"MTA-STS 策略生效"指的是发送方查到了策略、id 校验通过、并按模式与 mx 列表做出了投递决策;"TLS 握手成功"指的是单次会话完成了加密。**前者是策略层结论,后者是传输层结论,两者同时成立才算一次合规投递;sts-policy-fetch-error 与 certificate-host-mismatch 对应的是完全不同的故障域,不能都归为"TLS 失败"。** 最后给 TLS-RPT 定个性:它是观测能力,不是防护能力。**报告机制本身不阻止任何降级、不拦截任何失败——把它当成防护措施,与把监控当成防火墙是同一类错误。**它还有一个自引用成本:报告本身是一封邮件,要通过 SMTP 投递;RFC 8460 专门规定,发送 MTA 在投递这些报告时不得遵守 MTA-STS 或 DANE TLSA 的失败判定,否则一个 TLS 配置坏掉的域会同时收不到失败报告——观测机制在被观测链路坏掉时会跟着失明。 # 五、MTA-STS 和 DANE 是同一条路吗?——两种信任锚的对比与共存 两者解决的是同一类问题:强化 SMTP 传输安全、防止降级、确认"对面确实是那个 MX"。把它们讲成"新与旧"或"二选一",是这条线上最常见的认知偏差。**真正的分歧不在时间先后,而在信任锚扎在哪里——是信公共 CA 体系,还是信 DNSSEC。** MTA-STS 依赖 WebPKI:MX 主机证书由公共 CA 体系验证,策略本体的 HTTPS 获取同样依赖 CA 信任链。DANE(据 RFC 6698 的规范)则把证书或公钥的指纹以 TLSA 记录形式发布在经 DNSSEC 签名的 DNS 中,把信任锚从 CA 体系整体移交给 DNSSEC。**前者相信"全球 CA 网络签出来的证书是对的",后者相信"DNSSEC 签名过的 DNS 记录是对的"——这是两套互不隶属的信任假设,不存在一方在协议层面"取代"另一方的说法。** 部署门槛因此落在不同的基础设施上。MTA-STS 的工作量在应用侧:发布一条 DNS TXT、提供一个 HTTPS 端点、维护策略文件并管理 id 变更。DANE 的前提是本域 DNS 已经启用 DNSSEC——这是一个更靠底层、牵涉面更广、对 DNS 服务商与解析链路都有要求的前提。**未启用 DNSSEC 的域,DANE 路线不可行;这一前提约束本身,就在很大程度上决定了两种方案的实际部署广度。** 失败面的结构也随之不同。MTA-STS 把可用性风险压在两个点上:HTTPS 端点可达性、以及公共 CA 体系的证书有效性;DNS 在它的架构里只承担"存在性声明"这一轻量角色。DANE 则把信任数据本身放进了 DNS——一旦 DNSSEC 链路或解析出现问题,TLSA 记录的可获得性直接受损。**两者把"信任失效"的风险分别押注在 Web 公钥基础设施与 DNSSEC 这两套不同的基础设施上,故障面不重叠。** 共存关系是明确的:两者不是替代,可以同时部署、叠加生效,发送方对同一目标可能同时具备两套可验证的信任信息。RFC 8460 的报告 schema 也为此预留了空间——一份报告的 policies 字段是数组,允许同时记录 MTA-STS 策略与 DANE TLSA 记录。**但两套信息同时在场时谁优先、如何组合,取决于具体 MTA 的实现,不同软件处理方式不完全一致——涉及这一层时只能说"取决于实现",不能给出统一的确定性结论。** 回到开头那两个坑,它们各自对应的处方其实已经藏在这组对比里。初级运维卡在"加了 TXT 就完事",缺的是对双信道第二次查询(HTTPS 端点、id 一致性)的检查;资深运维卡在"改了参数对方不听",缺的是对 max_age 自引用语义与 id 变更纪律的理解。**MTA-STS 的复杂度不在"配置三条记录",而在承认这三条记录分属两条会各自传播、各自缓存、各自失效的信道——排错的第一步永远是先问:这次失败落在哪一条信道上。**

优秀的个人博客,低调大师

"真·零代码侵入"工业+智慧城市物联网感知中台全栈能力拆解

让设备会说话,让视频会思考,让异常有人管 一个绕不开的现实 做物联网项目的人都熟悉这套流程: 买设备、接网关、写驱动、调协议、搭平台、做大屏、接告警、派工单…… 每一步都不难,难的是把它们串成一条线。 设备厂商各说各话,协议五花八门;视频系统和业务系统两套人马两张网;AI 算法买回来了,却不知道怎么接到现场;告警响个不停,真正需要处理的那一条反而被淹没。 结果就是:数据有了,价值没出来。 NexIoT 想做的是另一件事——把物联网项目里最琐碎、最重复、最容易掉链子的部分,收进一个平台里。下面这张能力地图,就是这个平台目前真实跑起来的东西。 一、设备接入:不挑协议,不怕杂牌 物联网项目最先撞上的墙,是"接不进来"。 NexIoT 的做法是把协议适配做成插件,现场有什么,就接什么。 通用网络协议:MQTT、TCP、UDP、HTTP,覆盖绝大多数自研设备和模组 工业协议:Modbus(TCP / RTU / RTU over TCP)、IEC104 电力规约、PLC 多品牌接入,支持主动轮询采集 视频协议:GB28181、ONVIF,以及海康 ISC / ISUP / SDK、大华 ICC / 云 / SDK、萤石、乐橙等主流厂商通道 第三方平台:OneNET 等公有云平台数据可直接接入 智能硬件:智能门锁等垂直品类已有现成通道 更关键的是接进来的方式足够省事:产品与物模型统一建模,属性、事件、服务三类能力一次定义,全平台复用;设备影子实时同步最新状态,断网补传、时序回溯都有据可查。 一句话总结:现场设备千奇百怪,接入方式只有一个。 二、工业采集与边缘计算:把能力放到现场去 工业现场和消费级物联网最大的区别在于——网络不一定好,设备不一定新,改造成本必须低。 NexIoT 提供两条路: 第一条,平台侧主动采集。 平台作为主站,按采集组、轮询命令主动去读 PLC、仪表、电表的寄存器,点位配置、字节序、缩放系数全部可视化配置,读回来的数据直接变成物模型属性。运行状态、成功率、平均时延、P95 时延一屏可见——采集健康度是能看见的,不是靠猜的。 三、视频能力:从"看得见"到"看得懂" 很多项目的视频系统,本质上只是一个播放器。 NexIoT 把视频做成了可调用的能力: 统一视频中心:不同厂商、不同协议、不同云平台的摄像头,汇到同一个目录里统一管理 实时预览与回放:支持云台控制、录像回放、多画面视频墙 分享播放:可生成分享链接,把画面直接给到客户、监理或外部协作方,不用为临时查看装一套系统 标准接入:GB28181-2022 已完整支持,新建项目可对接国标平台,存量项目可对接厂商私有平台 视频不再是一个孤立的系统,而是告警、AI、大屏共同的数据源。 三朵云视频平台托管。 乐橙云: 萤石云: 大华SDK直连 海康SDK直连 大华ICC GB28281 五、告警与工单:让异常真正有人管 这是整套平台里最有商业价值的一块。 设备接进来、视频看得见、AI 会识别——这些都只是"发现"。企业真正要的是"有人处理、处理完有记录、记录可追责"。 NexIoT 的统一告警中心,做的是发现到闭环的全链路: 第一层:告警怎么产生。 告警策略不再只是"超阈值就报警"。支持阈值比较、事件触发、设备离线 / 无数据、AND / OR 组合条件、持续时间、次数窗口、跨日生效计划、恢复回差。一次波动不会炸出一屏告警,真实异常不会被抖动掩盖。 第二层:告警怎么收敛。 同一异常由视频、传感器、流程重复上报怎么办?平台做事件合并与去重,多条告警可以合并成一张工单。值班人员看到的是一条需要处理的异常,不是十条重复通知。 第三层:告警怎么处置。 派单、接单、转派、处理反馈、验收、退回、取消、超时升级——完整工单流程。处理过程中的图片、视频、文件都能作为证据上传,每一步都有人员、时间、说明和证据的完整时间轴。 现场状态和处置状态是两条独立的维度:设备恢复了不代表工单闭环,工单关了也不代表异常消失。这才是能过审计的设计。 第四层:通知怎么送达。 微信服务号、钉钉、飞书、邮件、阿里云 / 腾讯云短信、阿里云 / 腾讯云语音、Webhook——十个渠道可选,模板与字段映射可视化配置。该找的人,一定找得到。 六、数据可视化:大屏自己搭,不用等排期 平台提供低代码可视化编辑器:折线、柱状、饼图、仪表盘、指标卡、时序表格、地图总览、设备列表、控制面板、SCADA 概览……组件库拖出来即用。 首页运营看板出厂即带:设备总量、在线率、消息量趋势、产品分布、设备类型分布、地图态势、产品排行。 此外还有专业 SCADA 组态能力(2D / 3D),面向工业现场的工艺流程图、设备状态图、控制操作面板。 管理层要的看板,现场要的组态,不用分开采购两套系统。 八、开放与集成:不让平台变成新的孤岛 再好的平台,如果数据出不去,也只是一个新的信息孤岛。 NexIoT 的开放能力: 应用管理:支持 HTTP 推送、JDBC、Kafka、MQTT、RocketMQ 五种方式把数据推给第三方系统 数据转换:推送前可配置字段转换规则,不用为了对接改一次代码 数据桥:支持连接管理、双向桥接,打通存量系统 开放 API:完整的对外接口体系,供自研系统调用 移动端与小程序:平台自带小程序端,现场巡检、消息接收、移动查看不用另做 App 平台负责连接,不负责绑架。 九、企业级底座:能交付,也能复用 面向做项目、做集成的团队,这一层决定了能不能规模化赚钱。 多租户隔离:租户数据、权限、配置完全隔离,一套平台支撑多个客户或多个项目 租户套餐:不同客户配不同能力包,按需开通,支撑商业化售卖 完整权限体系:部门、岗位、角色、菜单、数据权限,细到按钮级 运维可视化:服务器监控、缓存监控、在线用户、登录日志、操作日志、定时任务 OTA 升级:设备固件包管理与批量升级任务,设备网络里的设备也能远程升级 任务中心:凭据管理、任务定义、调度执行统一纳管 对象存储:告警证据、AI 样本、固件包统一存储与鉴权访问 这意味着:第一个项目沉淀下来的配置、模型、流程、模板,第二个项目可以直接复用。 这张能力地图意味着什么 把上面九块拼起来,可以看到 NexIoT 覆盖的是一条完整的价值链: 设备接入 → 数据采集 → 视频汇聚 → AI 识别 → 告警生成 → 工单闭环 → 数据外送 → 可视化呈现 → 多租户复用 对照到业务语言,就是: 对设备厂商:设备接得快,能接进更多项目 对集成商:一套平台反复用,项目毛利上得去 对工业企业:现场问题有人管、有记录、有依据 对管理者:数据看得见,责任说得 最后 物联网项目最贵的从来不是硬件,而是那些看不见的集成工作、那些没人负责的异常、那些每次都要从头再来的重复劳动。 NexIoT 试图把这些成本压下去。 让设备会说话,让视频会思考,让异常有人管——然后把省下来的时间,还给业务本身。 如果你正在做设备接入、视频汇聚、AI 算法落地或工业数字化项目,欢迎联系我们,聊聊你的场景。 💬 微信 / 企业微信:【outlookFil】 附:配图清单(供排版时核对) 编号 建议配图内容 位置 0 文章头图 标题下方 1 产品与物模型管理界面 第一章 2 设备接入与设备详情页 第一章 3 工业采集点位配置与运行状态 第二章 4 边缘网关统一纳管 第二章 5 视频中心统一管理界面 第三章 6 视频墙与云台控制 第三章 7 数据集与在线标注工作台 第四章 8 模型训练与推理验证 第四章 9 算法任务与运行监控 第四章 10 告警记录与策略配置 第五章 11 告警详情、证据、处置时间轴 第五章 12 工单闭环流程界面 第五章 13 通知渠道与模板配置 第五章 14 可视化流程编排编辑器 第六章 15 场景联动配置 第六章 16 平台首页运营看板 第七章 17 低代码可视化大屏编辑器 第七章 18 SCADA 组态(2D / 3D) 第七章 19 应用管理与数据推送配置 第八章 20 数据桥配置 第八章 21 移动端 / 小程序界面 第八章 22 多租户与租户套餐 第九章 23 权限体系与运维监控 第九章 24 OTA 升级与任务中心 第九章 25 能力全景图 总结章 26 结尾二维码 / 行动图 结尾 #nexiot

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册