java 网络编程

<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流!

 

1 网络编程概述 

计算机网络是指将地理位置不同的具备独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统、网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。网络编程就是用来实现网络互连的不同计算机上运行的程序间的数据交换。网络模型包括OSI参考模型和TCP/IP参考模型。

什么是网络编程?

有哪些网络模型?

什么是端口,什么是协议?

怎么进行数据封包和拆包,为什么要这么做?

什么是UDP?有哪些特点?适用于哪些情况

什么是TCP

什么是socket?什么特点?

网络模型:

OSI参考模型

TCP/IP参考模型

网络通讯要素:

IP地址

端口号

传输协议

两程序相连接的步骤:

1、 找到对方IP

2、 数据要发送到对象的指定应用程序上。为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识。为了称呼这些数字,叫做端口。这个端口是逻辑端口,不是网线的那个物理端口

3、 定义通信规则。这个通讯规则成为协议。国际组织定义了通用协议TCP/IP

IP地址最大是就255IP不够用一般都是公共分配一段为公共地址。IPV4不够用现在用IPv6.

端口表示,0~65535,不要太大了,0~1024一般都是被系统保留了。

公共协议一般有两种 TCP协议,UDP协议

网络参考模型:

数据从应用层向下走,每通过一层,那么都进行数据封装,走到物理物理层,通过物理设备进入另一台机器,到达主机至网络层,然后数据解包,解包到应用层,通过端口进入程序,让程序识别所接收的信息。

咱们网络编程都是在网际层和传输层之间操作。如果进行开发,那么是在应用层操作。基础的知识都是在底层。

|----传输层常见的协议是TCP协议(传输控制协议),UDP协议。

|----网际层常见的是IP

|----应用层里有HTTP协议,FTP协议。

IP地址 

InetAddress 类表示互联网协议 (IP) 地址。 IP 地址是 IP 使用的 32 位或 128 位无符号数字,它是一种低级协议,UDP和 TCP 协议都是在它的基础上构建的。

没有构造函数,有非静态方法。

|----i.toString() 获得主机的名字和地址。

|----i.getHostAddress()获得主机地址 返回值是inetaddress

|----i.getHostName() 获得主机名称

|----getByName(String host),根据主机ip地址或者域名获得主机名称

如果主机地址和网络名没有映射到网络上,那么去根据ip地址获取名称的时候,就无法获得名称

InetAddress类的使用:

public static InetAddress getLocalHost()获取本地主机IP地址对象

public static InetAddress getByName(String host)获取其他主机IP地址对象

public String getHostAddress()获取主机IP地址

public String getHostName()获取主机名字

 

package network;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class InetAddressDemo {
	public static void main(String[] args) throws UnknownHostException {
		// 获取本地主机IP对象并通过IP对象获得主机IP和名字
		InetAddress address1 = InetAddress.getLocalHost();
		System.out.println("ip1:" + address1.getHostAddress());
		System.out.println("name1:" + address1.getHostName());

		// 根据主机名获取IP地址对象
		InetAddress address2 = InetAddress.getByName("192.168.1.121");
		System.out.println("ip2:" + address2.getHostAddress());
		System.out.println("name2:" + address2.getHostName());
	}
}


 

UDPTCP的区别:

UDP 数据报包的发送和接收,将数据源和目的封装成数据包中

面向无连接协议,将数据源和目的封装成数据包中,不需要建立连接

数据大小有限制,大小限制在64k

不需要建立连接,传输速度快

不安全,数据不能保证一定被收到

如:发邮件,群发短信

TCP 数据的发送和接收

面向连接协议,在数据发送前,一定要有连接

数据大小没有限制

必须建立连接,速度会较慢

数据一定能够收到

如:打电话,聊qq

Socket概念 

Socket是为网络服务的一种机制。(socket其实是插座的意思)

通信的两段都有socket。(就像海运里面的港口,船每到一个地方先在港口停泊)

网络通信其实就是socket间的通信。

数据就在两个socket间通过IO传输。(船只像IO,通过港口来传输货物)

UDP传输——发送端

使用socket最重要的是记住步骤流程,代码查阅API

需求:通过udp传输方式,将一段文字数据发送出去。

定义一个udp发送端。

思路:

1,建立updsocket服务。

2,提供数据,并将数据封装到数据包中。

3,通过socket服务的发送功能,将数据包发出去。

4,关闭资源。

UDP传输——接收端

需求:定义一个应用程序,用于接收udp协议传输的数据并处理的。

定义udp的接收端。

思路:

定义udpsocket服务。

通常会监听一个端口。其实就是给这个接收网络应用程序定义数字标识。 方便于明确哪些数据过来该应用程序可以处理。

定义一个数据包,因为要存储接收到的字节数据。

因为数据包对象中有更多功能可以提取字节数据中的不同数据信息。

通过socket服务的receive方法将收到的数据存入已定义好的数据包中。

通过数据包对象的特有功能。将这些不同的数据取出。打印在控制台上。

关闭资源。

例子:

package network;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

class UdpSend {
	public static void main(String[] args) throws Exception {// 1,创建udp服务。通过DatagramSocket对象。
		DatagramSocket ds = new DatagramSocket(8888);// 发送端如果不自定义,那么系统会随机使用。自定义以后就是发出时从8888端口出去,发送包通过10000端口到对方的10000端口。
		byte[] buf = "udp ge men lai le".getBytes();// 2,确定数据,并封装成数据包。
		DatagramPacket dp = new DatagramPacket(buf, 0, buf.length,
				InetAddress.getByName("127.0.0.1"), 10000);// 因为ip地址有可能错误,所以有可能会报异常
		ds.send(dp);// 3,通过socket服务,将已有的数据包发送出去。通过send方法。
		ds.close();// 4,关闭资源。
	}
}


 

 

package network;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

class UdpRece {
	public static void main(String[] args) throws Exception {
		DatagramSocket ds = new DatagramSocket(10000);// 1,创建udp
														// socket,建立端点。括号内为监听的端口。端口设置成别的软件中占用的端口也会出现错误
		// 上面这句话不能放到while语句里面,因为服务端建立一次就行了,如果每次接收都建立,都new一个新的服务端,会造成端口被占用,出错
		while (true)// 设置while函数,这样接收一次数据,就执行一次下面的代码。因为接收端是阻塞式的,所以可以使用while函数
		{
			byte[] buf = new byte[1024];
			DatagramPacket dp = new DatagramPacket(buf, buf.length);// 2,定义数据包。用于存储数据。
			ds.receive(dp);// 3,通过服务的receive方法将收到数据存入数据包中。
			// 注意这个解释函数是阻塞式方法,发出端没有信息发出的话,这里就接收不到信息
			String ip = dp.getAddress().getHostAddress();// 4,通过数据包的方法获取其中的数据。
			String data = new String(dp.getData(), 0, dp.getLength());
			int port = dp.getPort();
			System.out.println(ip + "::" + data + "::" + port);
		}
	}
}


 

4 简单聊天程序:

编写一个聊天程序。

有收数据的部分,和发数据的部分。这两部分需要同时执行。那就需要用到多线程技术。一个线程控制收,一个线程控制发。

因为收和发动作是不一致的,所以要定义两个run方法。而且这两个方法要封装到不同的类中。

 

package network;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

class Send implements Runnable {
	private DatagramSocket ds;// 私有化发送包,仅本类调用

	public Send(DatagramSocket ds)// 构造函数每次初始化调用这个发送包ds
	{
		this.ds = ds;
	}

	public void run()// 多线程覆写run函数
	{
		try {// 新建一个读取流缓冲区,用来记录键盘输入信息
			BufferedReader bufr = new BufferedReader(new InputStreamReader(
					System.in));
			String line = null;
			while ((line = bufr.readLine()) != null)// 遍历输入的信息
			{
				byte[] buf = line.getBytes();// 创建数组,接收输入的信息
				DatagramPacket dp = new DatagramPacket(buf, buf.length,
						InetAddress.getByName("192.168.1.255"), 10002);
				ds.send(dp);// 将接收的信息通过包10002端口发送到指定ip地址
				if ("886".equals(line))// 如果输入886字符串那么结束程序
					break;
			}
		} catch (Exception e)// 捕捉到相应异常就进行处理
		{
			throw new RuntimeException("发送端失败");
		}
	}
}

class Rece implements Runnable {
	private DatagramSocket ds;

	public Rece(DatagramSocket ds)// 构造函数,默认返回数据包ds
	{
		this.ds = ds;
	}

	public void run() {
		try {
			while (true)// 接收端,每次执行完继续执行
			{
				byte[] buf = new byte[1024];// 创建数组
				DatagramPacket dp = new DatagramPacket(buf, buf.length);// 将buf打包做成dp
				ds.receive(dp);// ds接收dp中的数据
				String ip = dp.getAddress().getHostAddress();// 接收主机ip地址
				String data = new String(dp.getData(), 0, dp.getLength());// 接收主机的数据
				if ("886".equals(data))// 如果接收的数据中有886就退出
				{
					System.out.println(ip + "....离开聊天室");
					break;
				}
				System.out.println(ip + ":" + data);// 没有就打印ip地址和接收的数据
			}
		} catch (Exception e) {
			throw new RuntimeException("接收端失败");
		}
	}
}

class ChatDemo {
	public static void main(String[] args) throws Exception {
		DatagramSocket sendSocket = new DatagramSocket();
		DatagramSocket receSocket = new DatagramSocket(10002);// 创建2个数据包对象,第二个默认是10002接口
		new Thread(new Send(sendSocket)).start();// 新建send类对象,接收sendSocket,并开启线程
		new Thread(new Rece(receSocket)).start();// 新建Rece类对象,接收receSocket,并开启线程
	}
}


 

TCP传输 

1 tcp分客户端和服务端。

客户端对应的对象是Socket,服务端对应的对象是ServerSocket

需求应用:给服务端发送给一个文本数据。

客户端:

通过查阅socket对象,发现在该对象建立时,就可以去连接指定主机。

因为tcp是面向连接的,所以在建立socket服务时,就要有服务端存在,并且连接成功。只有形成通路后,才能在该通道进行数据的传输。

特点:当socket通路连接成功后,内部就有流,直接接收流数据即可

步骤:1,创建Socket服务。并指定要连接的主机和端口。(客户端步骤只有一步)

服务端:

建立服务端的socket服务。ServerSocket();并监听一个端口。

获取连接过来的客户端对象。通过ServerSokcet的 accept方法。没有连接就会等,所以这个方法阻塞式的。

客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流来读取发过来的数据。并打印在控制台。

关闭服务端。(可选)

例子:

package network;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

class TcpClient {
	public static void main(String[] args) throws Exception {
		Socket s = new Socket("127.0.0.1", 10003);// 创建客户端的socket服务。指定目的主机和端口.
		OutputStream out = s.getOutputStream();// 为了发送数据,应该获取socket流中的输出流。
		out.write("TCP,我来啦".getBytes());
		s.close();
	}
}

class TcpServer {
	public static void main(String[] args) throws Exception {
		ServerSocket ss = new ServerSocket(10003);// 建立服务端socket服务。并监听一个端口。
		Socket s = ss.accept();// 通过accept方法获取连接过来的客户端对象。该方法是阻塞式的
		String ip = s.getInetAddress().getHostAddress();// 获得ip地址
		System.out.println(ip + "....conneted");
		InputStream in = s.getInputStream();// 获取客户端发送过来的数据,那么要使用客户端对象的读取流来读取数据。
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		System.out.println(new String(buf, 0, len));
		s.close();
		ss.close();// 这个客户端只服务一次
	}
}


 

需求应用:

客户端给服务端发送数据,服务端收到后,给客户端反馈信息。

客户端:

建立socket服务。指定要连接主机和端口。

获取socket流中的输出流。将数据写到该流中。通过网络发送给服务端。

获取socket流中的输入流,将服务端反馈的数据获取到,并打印。

关闭客户端资源。

例子:

package network;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

class TcpClient2 {
	public static void main(String[] args) throws Exception {
		Socket s = new Socket("192.168.1.254", 10004);
		OutputStream out = s.getOutputStream();
		out.write("服务端,你好".getBytes());// 客户端护肤服务端
		InputStream in = s.getInputStream();// 接收读取流
		byte[] buf = new byte[1024];
		int len = in.read(buf);// 这里read的是socket流,socket流是阻塞式的,如果服务器端没有发出信息,这么读不到就不会向下执行
		System.out.println(new String(buf, 0, len));
		s.close();
	}
}

class TcpServer2 {
	public static void main(String[] args) throws Exception {
		ServerSocket ss = new ServerSocket(10004);
		Socket s = ss.accept();
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip + "....connected");
		InputStream in = s.getInputStream();
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		System.out.println(new String(buf, 0, len));
		OutputStream out = s.getOutputStream();
		Thread.sleep(5000);// 沉睡5秒,会发现这里不写入,上面的客户端不会读到的
		out.write("哥们收到,你也好".getBytes());
		s.close();
		ss.close();
	}
}


 

 

需求应用:建立一个文本转换服务器。

客户端给服务端发送文本,服务单会将文本转成大写在返回给客户端。而且客户度可以不断的进行文本转换。当客户端输入over时,转换结束。

客户端:

既然是操作设备上的数据,那么就可以使用io技术,并按照io的操作规律来思考。

源:键盘录入。

目的:网络设备,网络输出流。

而且操作的是文本数据。可以选择字符流。

步骤

建立服务。

获取键盘录入。

将数据发给服务端。

后去服务端返回的大写数据。

结束,关资源。

都是文本数据,可以使用字符流进行操作,同时提高效率,加入缓冲。

代码中现象:客户端和服务端都在莫名的等待。为什么呢?

因为客户端和服务端都有阻塞式方法。这些方法么没有读到结束标记。那么就一直等

而导致两端,都在等待。阻塞式socket流,只要都等待,就会出现这样的情况

例子:

package network;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

class TransClient {
	public static void main(String[] args) throws Exception {
		Socket s = new Socket("192.168.1.254", 10005);
		// 定义读取键盘数据的流对象。
		BufferedReader bufr = new BufferedReader(new InputStreamReader(
				System.in));
		// 定义目的,将数据写入到socket输出流。发给服务端。
		// BufferedWriter bufOut =
		// new BufferedWriter(newOutputStreamWriter(s.getOutputStream()));
		PrintWriter out = new PrintWriter(s.getOutputStream(), true);
		// 可以定义字符流,也可以制定目的流,目的流可以传输字符也可以传输字节,并且能自动刷新
		// 再定义一个socket读取流,读取服务端返回的大写信息。
		BufferedReader bufIn = // 注意这三个流,一个是记录键盘的,一个是发送,一个接受服务器的。
		new BufferedReader(new InputStreamReader(s.getInputStream()));
		String line = null;
		while ((line = bufr.readLine()) != null) {
			if ("over".equals(line))
				break;
			out.println(line);
			// bufOut.write(line);
			// bufOut.newLine();//如果用字符流,需要换行刷新,服务器端才能接收到信息
			// bufOut.flush();//如果不用目的流,那么必须换行,换行后一定要刷新不换行服务器接受不到,会卡在那里。因为socket流是阻塞式的。
			String str = bufIn.readLine();// 接收服务器端数据
			System.out.println("server:" + str);
		}
		bufr.close();// 用到了缓冲区,所以要关闭
		s.close();// 客户端要关闭
	}
}

class TransServer {
	public static void main(String[] args) throws Exception {
		ServerSocket ss = new ServerSocket(10005);
		Socket s = ss.accept();// 利用accept函数可以连接客户端
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip + "....connected");// 测试,如果连接成功那么就打印ip地址
		// 读取socket读取流中的数据。
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(
				s.getInputStream()));
		// 目的。socket输出流。将大写数据写入到socket输出流,并发送给客户端。
		// BufferedWriter bufOut =
		// new BufferedWriter(newOutputStreamWriter(s.getOutputStream()));
		PrintWriter out = new PrintWriter(s.getOutputStream(), true);
		String line = null;
		while ((line = bufIn.readLine()) != null) {
			System.out.println(line);
			out.println(line.toUpperCase());
			// bufOut.write(line.toUpperCase());//返回大写,并且通过写入流,写入到输出流中
			// bufOut.newLine();//换行,不换行客户端也接收不到停止的命令
			// bufOut.flush();//有缓冲区,哟啊刷新
		}
		s.close();// 关闭客户端。当客户端关闭时,服务器端也关闭,因为客户端自动在socket流结尾添加一个标识符,告诉已经结束,所以服务器段跳出程序,执行下一行
		ss.close();
	}
}


 

 

应用需求:

上传图片

客户端:

服务端点。

读取客户端已有的图片数据。

通过socket 输出流将数据发给服务端。

读取服务端反馈信息。

关闭。

上传字节,字符,拷贝文件,上传图片,都是用的IO流中的知识,注意灵活掌握

客户端并发上传图片:

如果只允许一个客户端连接,那么这个服务端有个局限性。当A客户端连接上以后。被服务端获取到。服务端执行具体流程。这时B客户端连接,只有等待。因为服务端还没有处理完A客户端的请求,还有循环回来执行下次accept方法。所以暂时获取不到B客户端对象。

(服务器要想接受多个客户端的操作,必须允许多个客户端进行连接,所以要建立多线程传输。)那么为了可以让多个客户端同时并发访问服务端。那么服务端最好就是将每个客户端封装到一个单独的线程中,这样,就可以同时处理多个客户端请求。

如何定义线程呢?

只要明确了每一个客户端要在服务端执行的代码即可。将该代码存入run方法中。

例子:

package network;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

class PicClient {
	public static void main(String[] args) throws Exception {
		if (args.length != 1)// 客户端上传限制,如果args字符串数组的不为1,那么提示出错。
		{// 只能上传一个文件
			System.out.println("请选择一个jpg格式的图片");
			return;
		}
		File file = new File(args[0]);// 建立一个file类对象,接收args[0]的第一个文件
		if (!(file.exists() && file.isFile()))// 如果文件不存在或者不是文件
		{
			System.out.println("该文件有问题,要么补存在,要么不是文件");
			return;
		}
		if (!file.getName().endsWith(".jpg"))// 后缀名必须是JPG
		{
			System.out.println("图片格式错误,请重新选择");
			return;
		}
		if (file.length() > 1024 * 1024 * 5)// 大小不能超过5M
		{
			System.out.println("文件过大,没安好心");
			return;
		}
		Socket s = new Socket("192.168.1.254", 10007);// 建立Socket端
		FileInputStream fis = new FileInputStream(file);// 文件写入流
		OutputStream out = s.getOutputStream();// 输出流
		byte[] buf = new byte[1024];// 建立数组
		int len = 0;
		while ((len = fis.read(buf)) != -1) {
			out.write(buf, 0, len);// 将文件写入到数组中
		}
		// 告诉服务端数据已写完,调用shutdownOutput()
		s.shutdownOutput();
		InputStream in = s.getInputStream();
		byte[] bufIn = new byte[1024];
		int num = in.read(bufIn);
		System.out.println(new String(bufIn, 0, num));
		fis.close();
		s.close();
	}
}

class PicThread implements Runnable {
	private Socket s;

	PicThread(Socket s) {
		this.s = s;
	}

	public void run() {
		int count = 1;
		String ip = s.getInetAddress().getHostAddress();
		try {
			System.out.println(ip + "....connected");
			InputStream in = s.getInputStream();
			File dir = new File("d:\\pic");
			File file = new File(dir, ip + "(" + (count) + ")" + ".jpg");
			while (file.exists())
				file = new File(dir, ip + "(" + (count++) + ")" + ".jpg");
			FileOutputStream fos = new FileOutputStream(file);
			byte[] buf = new byte[1024];
			int len = 0;
			while ((len = in.read(buf)) != -1) {
				fos.write(buf, 0, len);
			}
			OutputStream out = s.getOutputStream();
			out.write("上传成功".getBytes());
			fos.close();
			s.close();
		} catch (Exception e) {
			throw new RuntimeException(ip + "上传失败");
		}
	}
}

class PicServer {
	public static void main(String[] args) throws Exception {
		ServerSocket ss = new ServerSocket(10007);
		while (true) {
			Socket s = ss.accept();
			new Thread(new PicThread(s)).start();
		}
	}
}


 

 

TCP客户端并发上传文件

服务端:

这个服务端有个局限性。当A客户端连接上以后。被服务端获取到。服务端执行具体流程。这时B客户端连接,只有等待。因为服务端还没有处理完A客户端的请求,还有循环回来执行下次accept方法。所以暂时获取不到B客户端对象。

那么为了可以让多个客户端同时并发访问服务端。那么服务端最好就是将每个客户端封装到一个单独的线程中,这样,就可以同时处理多个客户端请求。

如何定义线程,让多个客户端同时上传呢?

只要明确了每一个客户端要在服务端执行的代码即可。将该代码存入run方法中。同时在主线程中开启新线程,当每个客户端上传文件,都会开辟一个新新线程

知识点简单总结:

1. UDP

|-- 面向无连接

|-- 非安全协议

|-- 效率高

|-- 传输限制大64K

2. TCP

|-- 面向连接

|-- 安全协议

|-- 效率低

|-- 传输大数据

3. UDP案例--多线程群聊,写一遍

|-- 发送端

|-- 建立DatagramSocket服务

|-- 构造方法 DatagramSocket();

|-- 封装数据包 DatagramPacket(字节数组,0,长度,发送目的IP,端口号)

|-- DatagramSocket服务的方法 send(数据包)

|-- 关闭资源

|-- 接收端

|-- 建立DatagramSocket服务

|-- 构造方法 DatagramSocket(10000);

|-- 定义字节数组,存储接收的数据

|-- 将数组封装到数据包中DatagramPacket(数组,长度)

|-- DatagramSocket服务的receive(数据包)

|-- 获取数据

|-- 关闭资源

4. TCP 案例 图片上传 写一遍

|-- 客户端

|-- 建立、Socket服务

|-- 通过Socket建立IO

|-- 客户端想服务端发送消息, OutputStream out = s.getOutputStream

|-- 客户端接收服务端发的消息 InputStream in = s.getInputStream

|-- 服务端

|-- 建立、ServerSocket服务

|-- 获取客户端连接对象 Socket s = ss.accept()

|-- 通过客户端对象获取IO

|-- 服务端想客户端发送消息, OutputStream out = s.getOutputStream

|-- 服务端获取客户端消息 InputStream in = s.getInputStream

|-- Socket对象 shutdownOutput 告诉服务端客户端没有数据了

5. TCP任务

|-- 将现有的程序进行改造,变成一个服务器可以同时服务多个客户端的上传,利用多线程

简单总结:

对于UDPTCP的区别,需要重点掌握.在实现两种通信的时候,需要大量地用到IO输入输出流,可见其重要性.不管是UDP还是TCP,不论发送端还是接收端,都需要建立socket服务。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值