发表时间:2022-03-16来源:网络
首先理清一个概念:网络编程不等于网站编程,网络编程即使用套接字来达到进程间通信,现在一般称为TCP/IP编程。
计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
计算机网络按其覆盖的地理范围可分为如下3类:
局域网(LAN)。局域网是一种在小区域内使用的,由多台计算机组成的网络,覆盖范围通常局限在10 千米范围之内,属于一个单位或部门组建的小范围网。城域网(MAN)。城域网是作用范围在广域网与局域网之间的网络,其网络覆盖范围通常可以延伸到整个城市,借助通信光纤将多个局域网联通公用城市网络形成大型网络,使得不仅局域网内的资源可以共享,局域网之间的资源也可以共享。广域网(WAN) 广城网是一种远程网,涉及长距离的通信,覆盖范围可以是个国家或多个国家,甚至整个世界。由于广域网地理上的距离可以超过几千千米,所以信息衰减非常严重,这种网络一般要租用专线,通过接口信息处理协议和线路连接起来,构成网状结构,解决寻径问题。计算机网络中实现通信必须有一些约定,即通信协议;包括对速率,传输代码,代码结构,传输控制步骤,出错控制等制定的标准。常见的网络通信协议有:TCP/IP协议、IPX/SPX协议、NetBEUI协议等。
TCP/IP协议:传输控制协议/因特网互联协议(Transmission Control Protocol/Internet Protocal),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
为了使两个节点之间能进行对话,必须在他们之间建立通信工具(即接口),使彼此之间,能进行信息交换。接口包括两部分:
硬件装置:实现结点之间的信息传送软件装置:规定双方进行通信的约定协议由于结点之间联系很复杂,在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式就是层次方式,及同层间可以通信,上一层可以调用下一层,而与再下一层不发生关系。各层互不影响,利于系统的开发和扩展。
把用户应用程序作为最高层,把物理通信线路作为最底层,将其间的协议处理分为若干层,规定每层处理的任务,也规定每层的接口标准。
上图中,TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能。
我们编写的程序位于应用层,因此我们的程序是和TCP/UDP打交道的。
通信的协议还是比较复杂的,java.net包中包含的类和接口,它们提供低层次的通信细节,我们可以直接使用这些类和接口,来专注于网络程序开发,而不用考虑通信的细节。
java.net包中提供了两种常见的网络协议的支持:TCP和UDP
TCP是可靠的连接,TCP就像打电话,需要先打通对方电话,等待对方有回应后才会跟对方继续说话,也就是一定要确认可以发信息以后才会把信息发出去。TCP上传任何东西都是可靠的,只要两台机器上建立起了连接,在本机上发送的数据就一定能传到对方的机器上。UDP就好比发电报,发出去就完事了,对方有没有接收到它都不管,所以UDP是不可靠的。TCP传送数据虽然可靠,但传送得比较慢;UDP传送数据不可靠,但是传送得快。TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
三次握手完成,TCP客户端和服务器端成功地建立连接,可以开始传输数据了。
其次,TCP的客户端和服务端断开连接,需要四次挥手
你可以看到,每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手。这里一点需要注意是:主动关闭连接的,才有 TIME_WAIT 状态。
为什么挥手需要四次?
关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。参考
通俗理解:
三次握手由此,可以可靠地进行连接和断开。
IP协议:网络互连协议
每个人的电脑都有一个独一无二的IP地址,这样互相通信时就不会传错信息了。
IP地址根据版本可以分类为:IPv4和IPv6
IPv4IPv6地址长度IPv4协议具有32位(4字节)地址长度IPv6协议具有128位(16字节)地址长度格式IPv4 地址的文本格式为 nnn.nnn.nnn.nnn,其中 0 //InetAdress类表示IP地址 //获取本机IP InetAddress ip = InetAddress.getLocalHost();// ADMINISTRATOR/192.xxx.xxx.xxx System.out.println(ip); //获得主机名 System.out.println(ip.getHostName());// ADMINISTRATOR //获得IP地址 System.out.println(ip.getHostAddress());// 192.xxx.xxx.xxx //getLocalHost=getHostName+getHostAddress System.out.println(InetAddress.getByName("localhost")); } }案例演示2
import java.net.InetAddress; import java.net.UnknownHostException; public class TestIP2 { public static void main(String[] args) throws UnknownHostException { InetAddress inetAddress = InetAddress.getByName("www.baidu.com"); // 获取此 IP 地址的主机名。 System.out.println(inetAddress.getHostName()); //返回 IP 地址字符串(以文本表现形式)。 System.out.println(inetAddress.getHostAddress()); } }说到端口,则要引入一个类:InetSocketAddress
此类实现 IP 套接字地址(IP 地址 + 端口号)。
InetSocketAddress(InetAddress addr, int port)
根据 IP 地址和端口号创建套接字地址。
InetSocketAddress(int port)
创建套接字地址,其中 IP 地址为通配符地址,端口号为指定值。
InetSocketAddress(String hostname, int port)
根据主机名和端口号创建套接字地址。
InetAddress getAddress()
获取 InetAddress。
String getHostName()
获取 hostname。
int getPort()
获取端口号。
案例演示
import java.net.InetAddress; import java.net.InetSocketAddress; public class TestPort { public static void main(String[] args) { InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1",8082); System.out.println(inetSocketAddress); //返回主机名 System.out.println(inetSocketAddress.getHostName()); //获得InetSocketAddress的端口 System.out.println(inetSocketAddress.getPort()); //返回一个InetAddress对象(IP对象) InetAddress address = inetSocketAddress.getAddress(); System.out.println(address); } }网络编程也叫做Socket编程,即套接字编程。套接字指的是两台设备之间通讯的端点。
TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。
Socket 类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。
案例演示
Socket client = new Socket("127.0.0.1", 6666);public InputStream getInputStream() : 返回此套接字的输入流。
如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。关闭生成的InputStream也将关闭相关的Socket。public OutputStream getOutputStream() : 返回此套接字的输出流。
如果此Socket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。关闭生成的OutputStream也将关闭相关的Socket。public void close() :关闭此套接字。
一旦一个socket被关闭,它不可再使用。关闭此socket也将关闭相关的InputStream和OutputStream 。public void shutdownOutput() : 禁用此套接字的输出流。
任何先前写出的数据将被发送,随后终止输出流。ServerSocket类:这个类实现了服务器套接字,该对象等待通过网络的请求。
案例演示
ServerSocket server = new ServerSocket(6666);案例演示1
客户端:
import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; public class TCPClient { public static void main(String[] args){ Socket socket = null; OutputStream os = null; try { //1、创建Socket对象,它的第一个参数需要的是服务端的IP,第二个参数是服务端的端口 InetAddress inet = InetAddress.getByName("127.0.0.1"); socket = new Socket(inet,8090); //2、获取一个输出流,用于写出要发送的数据 os = socket.getOutputStream(); //3、写出数据 os.write("你好,我是客户端!".getBytes()); } catch (IOException e) { e.printStackTrace(); } finally {//4、释放资源 if(socket!=null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } if(os!=null){ try { os.close(); } catch (IOException e) { e.printStackTrace(); } } } } }服务端:
import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class TCPServer { public static void main(String[] args) { ServerSocket serverSocket = null; Socket socket = null; InputStream is = null; ByteArrayOutputStream baos = null; try { //1、创建服务端的ServerSocket,指明自己的端口号 serverSocket = new ServerSocket(8090); //2、调用accept接收到来自于客户端的socket socket = serverSocket.accept();//阻塞式监听,会一直等待客户端的接入 //3、获取socket的输入流 is = socket.getInputStream(); // 不建议这样写:因为如果我们发送的数据有汉字,用String的方式输出可能会截取汉字,产生乱码 // int len=0; // byte[] buffer = new byte[1024]; // while ((len=is.read(buffer))!=-1){ // String str = new String(buffer, 0, len); // System.out.println(str); // } //4、读取输入流中的数据 //ByteArrayOutputStream的好处是它可以根据数据的大小自动扩充 baos = new ByteArrayOutputStream(); int len=0; byte[] buffer = new byte[1024]; while ((len=is.read(buffer))!=-1){ baos.write(buffer,0,len); } System.out.println("收到了来自于客户端"+socket.getInetAddress().getHostName() +"的消息:"+baos.toString()); } catch (IOException e) { e.printStackTrace(); } finally {//5、关闭资源 if(serverSocket!=null){ try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } if(socket!=null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } if(is!=null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } if(baos!=null){ try { baos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
服务器是没有IO流的,服务器可以获取到请求的客户端对象socket
使用每个客户端socket中提供的IO流和客户端进行交互
服务器使用客户端的字节输入流读取客户端发送的数据
服务器使用客户端的字节输出流给客户端回写数据
案例演示2
服务端向客户端回写数据
客户端:
import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; public class TCPClient { public static void main(String[] args){ Socket socket = null; OutputStream os = null; ByteArrayOutputStream baos=null; InputStream is=null; try { //1、创建Socket对象,它的第一个参数需要的是服务端的IP,第二个参数是服务端的端口 InetAddress inet = InetAddress.getByName("127.0.0.1"); socket = new Socket(inet,8888); //2、获取一个输出流,用于写出要发送的数据 os = socket.getOutputStream(); //3、写出数据 os.write("你好,我是客户端!".getBytes()); //==========================解析回复================================== //4、首先必须通知服务器,我已经输出完毕了,不然服务端不知道什么时候输出完毕 //服务端的while循环会一直执行,会阻塞 socket.shutdownOutput(); ///5、获取输入流,用于读取服务端回复的数据 is = socket.getInputStream(); baos = new ByteArrayOutputStream(); int len=0; byte[] buffer = new byte[1024]; while ((len=is.read(buffer))!=-1){ baos.write(buffer,0,len); } System.out.println("收到了来自服务端的消息:"+baos.toString()); } catch (IOException e) { e.printStackTrace(); } finally {//6、释放资源 if(socket!=null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } if(os!=null){ try { os.close(); } catch (IOException e) { e.printStackTrace(); } } if(is!=null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } if (baos!=null){ try { baos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }服务端:
import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class TCPServer { public static void main(String[] args) { ServerSocket serverSocket = null; Socket socket = null; InputStream is = null; ByteArrayOutputStream baos = null; OutputStream os=null; try { //1、创建服务端的ServerSocket,指明自己的端口号 serverSocket = new ServerSocket(8888); //2、调用accept接收到来自于客户端的socket socket = serverSocket.accept();//阻塞式监听,会一直等待客户端接入 //3、获取socket的输入流 is = socket.getInputStream(); // 不建议这样写:因为如果我们发送的数据有汉字,用String的方式输出可能会截取汉字,产生乱码 // int len=0; // byte[] buffer = new byte[1024]; // while ((len=is.read(buffer))!=-1){ // String str = new String(buffer, 0, len); // System.out.println(str); // } //4、读取输入流中的数据 //ByteArrayOutputStream的好处是它可以根据数据的大小自动扩充 baos = new ByteArrayOutputStream(); int len=0; byte[] buffer = new byte[1024]; while ((len=is.read(buffer))!=-1){ baos.write(buffer,0,len); } System.out.println("收到了来自于客户端"+socket.getInetAddress().getHostName() +"的消息:"+baos.toString()); //===========================回复========================================== //5、获取一个输出流,写出回复给客户端 os = socket.getOutputStream(); //6、写出数据 os.write("你好,我是服务端".getBytes()); } catch (IOException e) { e.printStackTrace(); } finally {//7、关闭资源 if(serverSocket!=null){ try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } if(socket!=null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } if(is!=null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } if(baos!=null){ try { baos.close(); } catch (IOException e) { e.printStackTrace(); } } if(os!=null){ try { os.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
案例演示3
上传文件:客户端发送文件给服务端,服务端将文件保存在本地
客户端:
import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; public class TCPClient { public static void main(String[] args) { Socket socket = null; FileInputStream fis = null; OutputStream os = null; try { //1、创建Socket对象,它的第一个参数需要的是服务端的IP,第二个参数是服务端的端口 InetAddress inet = InetAddress.getByName("127.0.0.1"); socket = new Socket(inet, 8888); //2、创建一个文件输入流,读取要上传的文件 fis = new FileInputStream("D:/test/touxiang.jpg"); //3、获取一个输出流,用于写出要发送的数据 os = socket.getOutputStream(); byte[] buffer = new byte[1024]; int len=0; while((len=fis.read(buffer))!=-1){ //4、写出数据 os.write(buffer,0,len); } } catch (IOException e) { e.printStackTrace(); } finally {//5、释放资源 if(socket!=null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } if(fis!=null){ try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } if(os!=null){ try { os.close(); } catch (IOException e) { e.printStackTrace(); } } } } }服务端:
import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class TCPServer { public static void main(String[] args) { ServerSocket serverSocket = null; Socket socket = null; FileOutputStream fos = null; InputStream is = null; try { //1、创建服务端的ServerSocket,指明自己的端口号 serverSocket = new ServerSocket(8888); //2、调用accept接收到来自于客户端的socket socket = serverSocket.accept();//阻塞式监听,会一直等待客户端的接入 //3、创建一个文件输出流,用于将读取到的客户端上传的文件输出 fos = new FileOutputStream("touxiang.jpg"); //4、获取socket的输入流 is = socket.getInputStream(); byte[] buffer = new byte[1024]; int len=0; while((len=is.read(buffer))!=-1){ fos.write(buffer,0,len);//5、写出文件 } } catch (IOException e) { e.printStackTrace(); } finally {//6、释放资源 if(serverSocket!=null){ try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } if(socket!=null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } if(fos!=null){ try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } if(is!=null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } } }假设大海里有许多岛屿,客户端和服务端相当于其中的两座岛屿,“客户端”岛屿生产了一种农作物要运到“服务端”岛屿,所以“客户端”要知道“服务端”确切的地址(IP),不然就运错地方了,socket就相当于运输的船只,port就相当于“服务端”岛屿的某个港口。
从技术意义上来讲,只有TCP才会分Server和Client。对于UDP来说,严格意义上,并没有所谓的Server和Client。
java.net包给我们提供了两个类DatagramSocket(此类表示用于发送和接收数据报的套接字)和DatagramPacket(该类表示数据报的数据包。 )
protected DatagramSocket()构造数据报套接字并将其绑定到本地主机上的任何可用端口。
protected DatagramSocket(int port)构造数据报套接字并将其绑定到本地主机上的指定端口。
protected DatagramSocket(int port, InetAddress laddr)创建一个数据报套接字,绑定到指定的本地地址。
DatagramPacket(byte[] buf, int offset, int length)构造一个 DatagramPacket用于接收指定长度的数据报包到缓冲区中。
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)构造用于发送指定长度的数据报包到指定主机的指定端口号上。
byte[] getData() 返回数据报包中的数据。
InetAddress getAddress() 返回该数据报发送或接收数据报的计算机的IP地址。
int getLength() 返回要发送的数据的长度或接收到的数据的长度。
案例演示1
发送方:
接收方:
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; public class UDPReceiver { public static void main(String[] args) throws IOException { //1、创建一个socket,开放端口 DatagramSocket socket = new DatagramSocket(9090); byte[] buffer = new byte[1024]; //2、创建一个包接收数据 DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length); //3、接收数据 socket.receive(packet);//阻塞式接收 //将数据包转换为字符串输出 String msg = new String(packet.getData(), 0, packet.getLength()); System.out.println(msg); //4、释放资源 socket.close(); } }
注意:
如果是TCP中先启动客户端会报错:
而如果是UDP中先启动发送方不会报错,但会正常退出。
案例演示2
完成在线咨询功能,学生和老师在线一对一交流(多线程)
发送方:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; public class UDPSender implements Runnable{ //创建一个socket DatagramSocket socket=null; //创建一个流 用于录入键盘的数据 BufferedReader bfr=null; //发送数据目的地的IP private String toIP; //发送数据目的地的端口 private int toPort; public UDPSender(String toIP, int toPort) { this.toIP = toIP; this.toPort = toPort; try { socket=new DatagramSocket();//创建一个socket } catch (SocketException e) { e.printStackTrace(); } bfr=new BufferedReader(new InputStreamReader(System.in));//从键盘录入数据到流中 } @Override public void run() { while (true){//循环发送数据 try { String msg = bfr.readLine();//从流中读取数据 byte[] buffer = msg.getBytes(); InetAddress inet = InetAddress.getByName(toIP); DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length, inet, toPort); socket.send(packet); //如果发送了拜拜,则退出发送 if(msg.equals("拜拜")){ break; } } catch (IOException e) { e.printStackTrace(); } } //释放资源 if(socket!=null){ socket.close(); } if (bfr!=null){ try { bfr.close(); } catch (IOException e) { e.printStackTrace(); } } } }接收方:
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; public class UDPReceiver implements Runnable{ //创建一个socket DatagramSocket socket=null; //接收方自己所在的端口 private int fromPort; //数据发送者的姓名 private String msgFrom; public UDPReceiver(int fromPort,String msgFrom) { this.fromPort = fromPort; this.msgFrom=msgFrom; try { socket=new DatagramSocket(fromPort);//创建一个socket } catch (SocketException e) { e.printStackTrace(); } } @Override public void run() { while(true){//循环接收 try { byte[] buffer = new byte[1024 * 8]; DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length); socket.receive(packet); String msg = new String(packet.getData(), 0, packet.getLength()); System.out.println(msgFrom+":"+msg); if (msg.equals("拜拜")){//如果接收到的数据为拜拜,则退出接收 break; } } catch (IOException e) { e.printStackTrace(); } } //释放资源 socket.close(); } }学生线程:
public class Student { public static void main(String[] args) { new Thread(new UDPSender("127.0.0.1",8888)).start(); new Thread(new UDPReceiver(7777,"老师")).start(); } }老师线程:
public class Teacher { public static void main(String[] args) { new Thread(new UDPSender("127.0.0.1",7777)).start(); new Thread(new UDPReceiver(8888,"学生")).start(); } }学生的窗口:
老师的窗口:
URL(Uniform Resource Locator):统一资源定位符,它表示Internet上某一资源的地址。
通过URL我们可以访问Internet上的各种网络资源,比如最常见的www,ftp站点。浏览器通过解析给定的URL可以在网络上查找相应的文件或其他资源。
URI=URL+URN
URI:Uniform Resource Identifier ,统一资源标志符。URL:Uniform Resource Locator,统一资源定位符。URN:Uniform Resource Name,统一资源命名。网络三大基石:HTML,HTTP,URL
URL的基本结构由5部分组成:
://:/#片段名?参数列表
例如:
http://localhost:8080/index.jsp#a?username=Tom&password=123456
java.net包下
URL(String spec)根据 String 表示形式创建 URL 对象。
URL(String protocol, String host, int port, String file) 根据指定协议名、主机名、端口号和文件名创建 URL 对象。
URL(String protocol, String host, String file) 根据指定的协议名、主机名和文件名创建 URL。
String getProtocol()获取此 URL的协议名称。
String getHost() 获取此 URL 的主机名。
int getPort() 获取此 URL 的端口号。
String getPath() 获取此 URL 的文件路径。
String getFile()获取此 URL 的文件名。
String getQuery()获取此 URL的查询部分。
URLConnection openConnection() 返回一个URLConnection实例,表示与URL引用的远程对象的URL 。
案例演示1
import java.net.MalformedURLException; import java.net.URL; public class Test { public static void main(String[] args) throws MalformedURLException { URL url = new URL("http://localhost:8080/index.jsp?username=Tom&password=123456"); System.out.println(url.getProtocol());//获取协议名 System.out.println(url.getHost());//获取主机名 System.out.println(url.getPort());//获取端口号 System.out.println(url.getPath());//获取文件路径 System.out.println(url.getFile());//获取文件名 System.out.println(url.getQuery());//获取查询名 } }
案例演示2
URL下载网络资源
上一篇:初识B/S结构编程技术
下一篇:数据结构中的编程技术
CI框架连接数据库配置操作以及多数据库操作
asp 简单读取数据表并列出来 ASP如何快速从数据库读取大量数据
C语言关键字及其解释介绍 C语言32个关键字详解
C语言中sizeof是什么意思 c语言里sizeof怎样用法详解
最简单的asp登陆界面代码 asp登陆界面源代码详细介绍
PHP中的魔术方法 :__construct, __destruct , __call, __callStatic,__get, __set, __isset, __unset , __sleep,
PHP中include和require区别之我见
PHP中的(++i)前缀自增 和 (i++)后缀自增
将视频设置为Android手机开机动画的教程
php递归返回值的问题
掌上小满app(又名OKKI)下载v6.24.2 安卓版
108.1M |商务办公
星巴克中国官方版app下载v10.9.3 安卓版
138.0M |生活服务
abc reading app手机版下载v7.3.35 安卓最新版本
218.9M |学习教育
智慧联想摄像头app(更名智享家)下载v4.1.6.2 安卓最新版本
211.4M |生活服务
星通货主app下载v902 安卓版
142.5M |生活服务
火花思维官方版下载v2.1.1 安卓手机版
208.6M |学习教育
火山小视频极速版2025(改名抖音火山版)下载v33.4.0 安卓官方正版
248.5M |影音播放
泰州通app下载v2.2.2 安卓版
126.9M |商务办公
2014-09-05
2022-03-17
2014-09-05
2014-09-05
2015-07-05
2022-03-21
2014-09-05
2014-09-05
2014-09-05
2022-03-20