热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android网络编程TCP、UDP(三)——UDP实例:搜索局域网所有的设备

接上面的UDP,本篇主要讨论如何在局域网中搜索所有的设备,这个需求在物联网应用的比较多,也比较综合,特把名字加在标题中了。最


接上面的UDP,本篇主要讨论如何在局域网中搜索所有的设备,这个需求在物联网应用的比较多,也比较综合,特把名字加在标题中了。最后一块是网络编程的常见问题。


3.6 实例:在局域网内搜索设备

假设你家里安装了智能家居,所有的设备都是通过Wifi连接自己家里的局域网(至于这些设备没有界面操作,如何连接wifi?有一个比较流行的牛逼技术,叫SmartConfig)。现在这些设备连入到局域网了,那如何通过Android搜索到这些设备?

模拟主机效果图:
这里写图片描述

模拟设备效果图:
这里写图片描述


3.6.1 原理分析

这些设备在局域网内,肯定是通过DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)来获取内网IP的。也就是说每个设备的IP都不是固定的。而我们主要目的就是要获取这些设备的IP地址。

也许你说,把手机设置成一个固定的内网IP,然后让这些设备来连接这个固定IP。看上去OK啊,但万一这个IP被占用了,怎办?

每个设备的IP会变,但通信端口我们肯定可以固定。这就可以运用上面的UDP广播(或组播)技术。具体流程:


  1. 主机(Android手机)发送广播信息,并指定对方接收端口为devicePort;
  2. 自己的发送端口为系统分配的hostPort,封装在DatagramSocket中,开始监听此端口。防丢失,一共发三次,每次发送后就监听一段时间;
  3. 设备监听devicePort端口,收到信息后。首先解析数据验证是否是自己人(协议)发过来的,否,扔;是,则通过数据报获取对方的IP地址与端口hostPort;
  4. 设备通过获取到的IP地址与端口hostPort,给主机发送响应信息;
  5. 主机收到设备的响应,就可以知道设备的IP地址了。同时主机返回确认信息给设备,防止设备发给主机的响应信息丢失,毕竟是UDP;
  6. 有了IP地址,就可以为所欲为了,比如:建立安全连接TCP。

本解决方法有以下特点:


  • 灵活性高。主机使用系统自动分配端口,不用担心端口被其他软件占用;
  • 搜索迅速。使用了UDP广播,所有局域网内的设备几乎同时可以接受到信息;
  • 连接安全。为了避免UDP的不安全性,使用了类似TCP的三次握手;
  • 数据安全。加入了协议,提高了数据的安全性。

下面是广播实现的代码,当然也可以用组播来实现。组播因为组播地址的原因,可以进一步加强安全性,代码中把广播的网络那块改成组播就好了。(组播参考:Android网络编程TCP、UDP(二))


3.6.2 代码实现

主机——搜索类:

import android.util.Log;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Set;/*** 设备搜索类* Created by zjun on 2016/9/3.*/
public abstract class DeviceSearcher extends Thread {private static final String TAG &#61; DeviceSearcher.class.getSimpleName();private static final int DEVICE_FIND_PORT &#61; 9000;private static final int RECEIVE_TIME_OUT &#61; 1500; // 接收超时时间private static final int RESPONSE_DEVICE_MAX &#61; 200; // 响应设备的最大个数&#xff0c;防止UDP广播攻击private static final byte PACKET_TYPE_FIND_DEVICE_REQ_10 &#61; 0x10; // 搜索请求private static final byte PACKET_TYPE_FIND_DEVICE_RSP_11 &#61; 0x11; // 搜索响应private static final byte PACKET_TYPE_FIND_DEVICE_CHK_12 &#61; 0x12; // 搜索确认private static final byte PACKET_DATA_TYPE_DEVICE_NAME_20 &#61; 0x20;private static final byte PACKET_DATA_TYPE_DEVICE_ROOM_21 &#61; 0x21;private DatagramSocket hostSocket;private Set mDeviceSet;private byte mPackType;private String mDeviceIP;DeviceSearcher() {mDeviceSet &#61; new HashSet<>();}&#64;Overridepublic void run() {try {onSearchStart();hostSocket &#61; new DatagramSocket();// 设置接收超时时间hostSocket.setSoTimeout(RECEIVE_TIME_OUT);byte[] sendData &#61; new byte[1024];InetAddress broadIP &#61; InetAddress.getByName("255.255.255.255");DatagramPacket sendPack &#61; new DatagramPacket(sendData, sendData.length, broadIP, DEVICE_FIND_PORT);for (int i &#61; 0; i <3; i&#43;&#43;) {// 发送搜索广播mPackType &#61; PACKET_TYPE_FIND_DEVICE_REQ_10;sendPack.setData(packData(i &#43; 1));hostSocket.send(sendPack);// 监听来信byte[] receData &#61; new byte[1024];DatagramPacket recePack &#61; new DatagramPacket(receData, receData.length);try {// 最多接收200个&#xff0c;或超时跳出循环int rspCount &#61; RESPONSE_DEVICE_MAX;while (rspCount-- > 0) {recePack.setData(receData);hostSocket.receive(recePack);if (recePack.getLength() > 0) {mDeviceIP &#61; recePack.getAddress().getHostAddress();if (parsePack(recePack)) {Log.i(TAG, "&#64;&#64;&#64;zjun: 设备上线&#xff1a;" &#43; mDeviceIP);// 发送一对一的确认信息。使用接收报&#xff0c;因为接收报中有对方的实际IP&#xff0c;发送报时广播IPmPackType &#61; PACKET_TYPE_FIND_DEVICE_CHK_12;recePack.setData(packData(rspCount)); // 注意&#xff1a;设置数据的同时&#xff0c;把recePack.getLength()也改变了hostSocket.send(recePack);}}}} catch (SocketTimeoutException e) {}Log.i(TAG, "&#64;&#64;&#64;zjun: 结束搜索" &#43; i);}onSearchFinish(mDeviceSet);} catch (IOException e) {e.printStackTrace();} finally {if (hostSocket !&#61; null) {hostSocket.close();}}}/*** 搜索开始时执行*/public abstract void onSearchStart();/*** 搜索结束后执行* &#64;param deviceSet 搜索到的设备集合*/public abstract void onSearchFinish(Set deviceSet);/*** 解析报文* 协议&#xff1a;$ &#43; packType(1) &#43; data(n)* data: 由n组数据&#xff0c;每组的组成结构type(1) &#43; length(4) &#43; data(length)* type类型中包含name、room类型&#xff0c;但name必须在最前面*/private boolean parsePack(DatagramPacket pack) {if (pack &#61;&#61; null || pack.getAddress() &#61;&#61; null) {return false;}String ip &#61; pack.getAddress().getHostAddress();int port &#61; pack.getPort();for (DeviceBean d : mDeviceSet) {if (d.getIp().equals(ip)) {return false;}}int dataLen &#61; pack.getLength();int offset &#61; 0;byte packType;byte type;int len;DeviceBean device &#61; null;if (dataLen <2) {return false;}byte[] data &#61; new byte[dataLen];System.arraycopy(pack.getData(), pack.getOffset(), data, 0, dataLen);if (data[offset&#43;&#43;] !&#61; &#39;$&#39;) {return false;}packType &#61; data[offset&#43;&#43;];if (packType !&#61; PACKET_TYPE_FIND_DEVICE_RSP_11) {return false;}while (offset &#43; 5 0xFF;len |&#61; (data[offset&#43;&#43;] <<8);len |&#61; (data[offset&#43;&#43;] <<16);len |&#61; (data[offset&#43;&#43;] <<24);if (offset &#43; len > dataLen) {break;}switch (type) {case PACKET_DATA_TYPE_DEVICE_NAME_20:String name &#61; new String(data, offset, len, Charset.forName("UTF-8"));device &#61; new DeviceBean();device.setName(name);device.setIp(ip);device.setPort(port);break;case PACKET_DATA_TYPE_DEVICE_ROOM_21:String room &#61; new String(data, offset, len, Charset.forName("UTF-8"));if (device !&#61; null) {device.setRoom(room);}break;default: break;}offset &#43;&#61; len;}if (device !&#61; null) {mDeviceSet.add(device);return true;}return false;}/*** 打包搜索报文* 协议&#xff1a;$ &#43; packType(1) &#43; sendSeq(4) &#43; [deviceIP(n<&#61;15)]* packType - 报文类型* sendSeq - 发送序列* deviceIP - 设备IP&#xff0c;仅确认时携带*/private byte[] packData(int seq) {byte[] data &#61; new byte[1024];int offset &#61; 0;data[offset&#43;&#43;] &#61; &#39;$&#39;;data[offset&#43;&#43;] &#61; mPackType;seq &#61; seq &#61;&#61; 3 ? 1 : &#43;&#43;seq; // can&#39;t use findSeq&#43;&#43;data[offset&#43;&#43;] &#61; (byte) seq;data[offset&#43;&#43;] &#61; (byte) (seq >> 8 );data[offset&#43;&#43;] &#61; (byte) (seq >> 16);data[offset&#43;&#43;] &#61; (byte) (seq >> 24);if (mPackType &#61;&#61; PACKET_TYPE_FIND_DEVICE_CHK_12) {byte[] ips &#61; mDeviceIP.getBytes(Charset.forName("UTF-8"));System.arraycopy(ips, 0, data, offset, ips.length);offset &#43;&#61; ips.length;}byte[] result &#61; new byte[offset];System.arraycopy(data, 0, result, 0, offset);return result;}/*** 设备Bean* 只要IP一样&#xff0c;则认为是同一个设备*/public static class DeviceBean{String ip; // IP地址int port; // 端口String name; // 设备名称String room; // 设备所在房间&#64;Overridepublic int hashCode() {return ip.hashCode();}&#64;Overridepublic boolean equals(Object o) {if (o instanceof DeviceBean) {return this.ip.equals(((DeviceBean)o).getIp());}return super.equals(o);}public String getIp() {return ip;}public void setIp(String ip) {this.ip &#61; ip;}public int getPort() {return port;}public void setPort(int port) {this.port &#61; port;}public String getName() {return name;}public void setName(String name) {this.name &#61; name;}public String getRoom() {return room;}public void setRoom(String room) {this.room &#61; room;}}
}

主机——demo核心代码&#xff1a;

private List mDeviceList;
private void searchDevices_broadcast() {new DeviceSearcher() {&#64;Overridepublic void onSearchStart() {startSearch(); // 主要用于在UI上展示正在搜索}&#64;Overridepublic void onSearchFinish(Set deviceSet) {endSearch(); // 结束UI上的正在搜索mDeviceList.clear();mDeviceList.addAll(deviceSet);mHandler.sendEmptyMessage(0); // 在UI上更新设备列表}}.start();
}

设备——设备等待搜索类&#xff1a;

import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.util.Log;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;/*** 设备等待搜索类* Created by zjun on 2016/9/4.*/
public abstract class DeviceWaitingSearch extends Thread {private final String TAG &#61; DeviceWaitingSearch.class.getSimpleName();private static final int DEVICE_FIND_PORT &#61; 9000;private static final int RECEIVE_TIME_OUT &#61; 1500; // 接收超时时间&#xff0c;应小于等于主机的超时时间1500private static final int RESPONSE_DEVICE_MAX &#61; 200; // 响应设备的最大个数&#xff0c;防止UDP广播攻击private static final byte PACKET_TYPE_FIND_DEVICE_REQ_10 &#61; 0x10; // 搜索请求private static final byte PACKET_TYPE_FIND_DEVICE_RSP_11 &#61; 0x11; // 搜索响应private static final byte PACKET_TYPE_FIND_DEVICE_CHK_12 &#61; 0x12; // 搜索确认private static final byte PACKET_DATA_TYPE_DEVICE_NAME_20 &#61; 0x20;private static final byte PACKET_DATA_TYPE_DEVICE_ROOM_21 &#61; 0x21;private Context mContext;private String deviceName, deviceRoom;public DeviceWaitingSearch(Context context, String name, String room) {mContext &#61; context;deviceName &#61; name;deviceRoom &#61; room;}&#64;Overridepublic void run() {DatagramSocket socket &#61; null;try {socket &#61; new DatagramSocket(DEVICE_FIND_PORT);byte[] data &#61; new byte[1024];DatagramPacket pack &#61; new DatagramPacket(data, data.length);while (true) {// 等待主机的搜索socket.receive(pack);if (verifySearchData(pack)) {byte[] sendData &#61; packData();DatagramPacket sendPack &#61; new DatagramPacket(sendData, sendData.length, pack.getAddress(), pack.getPort());Log.i(TAG, "&#64;&#64;&#64;zjun: 给主机回复信息");socket.send(sendPack);Log.i(TAG, "&#64;&#64;&#64;zjun: 等待主机接收确认");socket.setSoTimeout(RECEIVE_TIME_OUT);try {socket.receive(pack);if (verifyCheckData(pack)) {Log.i(TAG, "&#64;&#64;&#64;zjun: 确认成功");onDeviceSearched((InetSocketAddress) pack.getSocketAddress());break;}} catch (SocketTimeoutException e) {}socket.setSoTimeout(0); // 连接超时还原成无穷大&#xff0c;阻塞式接收}}} catch (IOException e) {e.printStackTrace();} finally {if (socket !&#61; null) {socket.close();}}}/*** 当设备被发现时执行*/public abstract void onDeviceSearched(InetSocketAddress socketAddr);/*** 打包响应报文* 协议&#xff1a;$ &#43; packType(1) &#43; data(n)* data: 由n组数据&#xff0c;每组的组成结构type(1) &#43; length(4) &#43; data(length)* type类型中包含name、room类型&#xff0c;但name必须在最前面*/private byte[] packData() {byte[] data &#61; new byte[1024];int offset &#61; 0;data[offset&#43;&#43;] &#61; &#39;$&#39;;data[offset&#43;&#43;] &#61; PACKET_TYPE_FIND_DEVICE_RSP_11;byte[] temp &#61; getBytesFromType(PACKET_DATA_TYPE_DEVICE_NAME_20, deviceName);System.arraycopy(temp, 0, data, offset, temp.length);offset &#43;&#61; temp.length;temp &#61; getBytesFromType(PACKET_DATA_TYPE_DEVICE_ROOM_21, deviceRoom);System.arraycopy(temp, 0, data, offset, temp.length);offset &#43;&#61; temp.length;byte[] retVal &#61; new byte[offset];System.arraycopy(data, 0, retVal, 0, offset);return retVal;}private byte[] getBytesFromType(byte type, String val) {byte[] retVal &#61; new byte[0];if (val !&#61; null) {byte[] valBytes &#61; val.getBytes(Charset.forName("UTF-8"));retVal &#61; new byte[5 &#43; valBytes.length];retVal[0] &#61; type;retVal[1] &#61; (byte) valBytes.length;retVal[2] &#61; (byte) (valBytes.length >> 8 );retVal[3] &#61; (byte) (valBytes.length >> 16);retVal[4] &#61; (byte) (valBytes.length >> 24);System.arraycopy(valBytes, 0, retVal, 5, valBytes.length);}return retVal;}/*** 校验搜索数据* 协议&#xff1a;$ &#43; packType(1) &#43; sendSeq(4)* packType - 报文类型* sendSeq - 发送序列*/private boolean verifySearchData(DatagramPacket pack) {if (pack.getLength() !&#61; 6) {return false;}byte[] data &#61; pack.getData();int offset &#61; pack.getOffset();int sendSeq;if (data[offset&#43;&#43;] !&#61; &#39;$&#39; || data[offset&#43;&#43;] !&#61; PACKET_TYPE_FIND_DEVICE_REQ_10) {return false;}sendSeq &#61; data[offset&#43;&#43;] & 0xFF;sendSeq |&#61; (data[offset&#43;&#43;] <<8 );sendSeq |&#61; (data[offset&#43;&#43;] <<16);sendSeq |&#61; (data[offset&#43;&#43;] <<24);return sendSeq >&#61; 1 && sendSeq <&#61; 3;}/*** 校验确认数据* 协议&#xff1a;$ &#43; packType(1) &#43; sendSeq(4) &#43; deviceIP(n<&#61;15)* packType - 报文类型* sendSeq - 发送序列* deviceIP - 设备IP&#xff0c;仅确认时携带*/private boolean verifyCheckData(DatagramPacket pack) {if (pack.getLength() <6) {return false;}byte[] data &#61; pack.getData();int offset &#61; pack.getOffset();int sendSeq;if (data[offset&#43;&#43;] !&#61; &#39;$&#39; || data[offset&#43;&#43;] !&#61; PACKET_TYPE_FIND_DEVICE_CHK_12) {return false;}sendSeq &#61; data[offset&#43;&#43;] & 0xFF;sendSeq |&#61; (data[offset&#43;&#43;] <<8 );sendSeq |&#61; (data[offset&#43;&#43;] <<16);sendSeq |&#61; (data[offset&#43;&#43;] <<24);if (sendSeq <1 || sendSeq > RESPONSE_DEVICE_MAX) {return false;}String ip &#61; new String(data, offset, pack.getLength() - offset, Charset.forName("UTF-8"));Log.i(TAG, "&#64;&#64;&#64;zjun: ip from host&#61;" &#43; ip);return ip.equals(getOwnWifiIP());}/*** 获取本机在Wifi中的IP*/private String getOwnWifiIP() {WifiManager wm &#61; (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);if (!wm.isWifiEnabled()) {return "";}// 需加权限&#xff1a;android.permission.ACCESS_WIFI_STATEWifiInfo wifiInfo &#61; wm.getConnectionInfo();int ipInt &#61; wifiInfo.getIpAddress();String ipAddr &#61; int2Ip(ipInt);Log.i(TAG, "&#64;&#64;&#64;zjun: 本机IP&#61;" &#43; ipAddr);return int2Ip(ipInt);}/*** 把int表示的ip转换成字符串ip*/private String int2Ip(int i) {return String.format("%d.%d.%d.%d", i & 0xFF, (i >> 8) & 0xFF, (i >> 16) & 0xFF, (i >> 24) & 0xFF);}
}

设备——demo核心代码&#xff1a;

private void initData() {new DeviceWaitingSearch(this, "日灯光", "客厅"){&#64;Overridepublic void onDeviceSearched(InetSocketAddress socketAddr) {pushMsgToMain("已上线&#xff0c;搜索主机&#xff1a;" &#43; socketAddr.getAddress().getHostAddress() &#43; ":" &#43; socketAddr.getPort());}}.start();
}

四、常见问题


4.1 局域网内无法通信

因为用了电脑作为测试设备&#xff0c;包括Java工程和Android模拟器&#xff0c;之前就知道Java工程中要网络通信要关防火墙&#xff0c;但使用的时候&#xff0c;发现Android模拟器、C工程、和Socket网络工具都可以通信&#xff0c;就Java工程不行。

尝试了很多方法找原因&#xff0c;如在命令行执行下面的命令&#xff0c;然而无终而返&#xff1a;


  1. 查看局域网中其他运行的电脑&#xff1a;net view
  2. 路由追踪&#xff1a;tracert (ip)
    eg&#xff1a;tracert baidu.com
    tracert 192.168.1.10
  3. 显示当前TCP/IP网络连接&#xff1a;netstat

最后终于找到解决办法&#xff0c;在“防火墙”的“允许的应用”中需要设置权限。把“Java(TM) Platform SE binary”勾上&#xff0c;并把后面的“专用”和“公用”网络都勾选上&#xff1a;
这里写图片描述


4.2 局域网内只有有线连接的设备可以通信&#xff0c;无线设备却无法通信

其实问题详细情况是这样的&#xff1a;无线Wifi连接的设备不能与无线设备通信&#xff08;内网IP通信&#xff09;&#xff0c;只能与有线设备通信&#xff1b;而有线设备一切正常。

这问题也很郁闷&#xff0c;查了资料也没有找到解决办法。但个人感觉这问题肯定是路由器的问题&#xff0c;因为局域网的控制系统就是路由器。幸运的是&#xff0c;我有两个一模一样的路由器&#xff0c;另一个路由器应该的。然后两台路由器&#xff0c;分别连两台电脑&#xff0c;通过电脑对路由器界面进行对比&#xff08;英文是硬伤啊⊙﹏⊙b&#xff09;。

最后锁定了这个东西“Wireless Isolation within SSID”&#xff0c;就是连接SSID的设备都独立&#xff0c;无法进行局域网内通信。曾经手滑了一下&#xff0c;点成Enable。改回Disabled&#xff0c;兄弟间就别分开了&#xff1a;
这里写图片描述



本来一篇想搞定的&#xff0c;结果写了三篇&#xff0c;目录还是按原来一篇的来写&#xff0c;有点儿乱(^__^) ……

网络编程&#xff0c;终于赶在这个周末结束前告一段落。


推荐阅读
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文介绍了Java中Hashtable的clear()方法,该方法用于清除和移除指定Hashtable中的所有键。通过示例程序演示了clear()方法的使用。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
author-avatar
杨建谦461128
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有