day17_socket

1 概念

socket:网络编程
io流:在同一个电脑中实现 内存与硬盘上的文件之间的数据传输
socket:在网络中的两台电脑之间实现数据传输

ip

Internet Protocol:互联网传输协议
ip地址:互联网为每台电脑分配的一个用于唯一标识的字符串
ip分类:ipv4版本:4个1byte组成的字符串  格式:192.168.198.120  
       ipv6版本:8个2byte组成的字符串  格式:abcf.123a.234b.9876.1111.0.0.1.1
本地ip:localhost 、127.0.0.1

端口

port:逻辑:::逻辑端口
     为电脑安装的所有软件分配的一个编号:0-65535
     注意:10000一下的端口不要使用:::

socket

  • TCP
Transmission Control Protocol:是一种面向连接的(点对点)、可靠的、基于字节流的传输层通信协议
类似于:打固定电话
  • UDP
User Datagram Protocol:是一种无需连接的、不可靠的、基于报文流的传输层通信协议
类似于:发报机

2 TCP

客户端和服务器端

2.1 案例1

  • 客户端
package day17_xml_socket;
public class Demo03TcpClient {
    /*
     * 客户端:主动连接服务器端
     * 1 创建连接 并指定服务器的ip和端口
     * 2 获取socket中的输入流和输出流 来信息交互
     * 3 关闭连接Socket
     * 
     * socket的方法:
     *    1   Socket(String host, int port)  创建连接
     *        Socket(InetAddress address, int port) 
     *    2 InputStream  getInputStream();获取输入流
     *    3 OutputStream getOutputStream();获取输出流
     *    4 int  getPort();获取对方的端口
     *      InetAddress getInetAddress();获取对方的ip
     *      int  getLocalPort();获取自己的端口
     *      InetAddress getLocalInetAddress();获取自己的ip 
     *     5 关闭连接:void close() 
     *       
     * */
	public static void main(String[] args)throws Exception {
		//tcp的客户端
		//1 创建连接 并指定服务器的ip和端口
		//Socket socket=new Socket("localhost", 10086);
		Socket socket=new Socket(InetAddress.getByName("localhost"), 10086);
		//2 获取socket中的输入流和输出流 来信息交互
		InputStream in=socket.getInputStream();
		OutputStream out=socket.getOutputStream();
		byte[] arr=new byte[1024];
		int n=in.read(arr);
		System.out.println("客户端接收到信息是:"+new String(arr, 0,n));
		out.write("我是客户端!".getBytes());
		System.out.println("客户端:getPort="+socket.getPort());
		System.out.println("客户端:getInetAddress().getHostName()="+socket.getInetAddress().getHostName());
		System.out.println("客户端:getLocalPort="+socket.getLocalPort());
		System.out.println("客户端:getLocalAddress().getHostName()="+socket.getLocalAddress().getHostName());
		//3 关闭连接Socket
		socket.close();
		
	}
	//服务器端对客户端的信息进行大小写转换 并删除数字 后响应给客户端
	//如果通过console怎么实现
}
  • 服务器端
package day17_xml_socket;

public class Demo03TcpServer {
    /*
     * 服务器端:类似于接电话的一方
     * 1  安装一个固定电话 买个号
     * 2  等待 电话铃响了 接听
     * 3  接收和发送信息
     * 4  挂断电话
     * 
     * 
     * 注意:在互联网中接收和发送信息 必须开启端口
     * socket实现:
     * 1 创建一个ServerSocket开启服务 并指定开启的端口
     * 2 等待客户端的连接:获取socket::套接字:::传输通道
     * 3 获取socket中的输入流和输出流 来信息交互
     * 4 关闭服务ServerSocket
     * 
     * 
     * ServerScoket:
     *     1 开启服务:ServerSocket(int port) 
     *     2 等待和获取连接:Socket accept()  阻塞方法
     *     3 关闭服务:void close()  
     *  Socket:
     *     1 InputStream  getInputStream();获取输入流
     *     2 OutputStream getOutputStream();获取输出流
     * */
	public static void main(String[] args)throws Exception {
		//tcp的服务器端
		//1 创建一个ServerSocket开启服务 并指定开启的端口
		ServerSocket server=new ServerSocket(10086) ;
		//2 等待客户端的连接:获取socket::套接字:::传输通道
		System.out.println("10086等待连接。。。。");
		Socket socket=server.accept();
		//3 获取输入流和输出流 进行信息交互
		InputStream in=socket.getInputStream();
		OutputStream out=socket.getOutputStream();
		out.write("你是谁?".getBytes());
		byte[] arr=new byte[1024];
		int n=in.read(arr);
		System.out.println("10086接收到信息是:"+new String(arr, 0,n));
		System.out.println("服务器端:getPort="+socket.getPort());
		System.out.println("服务器端:getInetAddress().getHostName()="+socket.getInetAddress().getHostName());
		System.out.println("服务器端:getLocalPort="+socket.getLocalPort());
		System.out.println("服务器端:getLocalAddress().getHostName()="+socket.getLocalAddress().getHostName());
		//4关闭服务ServerSocket
		server.close();
	}
}

2.2 案例2:字符串转换

对客户端发送的信息 进行修改(大小写转换 删除数字) 后返回给客户端
  • 客户端
package day18_socket;
...
public class ZuoYe01Client {
    /*
     * 使用tcp实现:对客户端发送的信息 进行修改(大小写转换 删除数字) 后返回给客户端
     * 1 获取socket
     * 2 获取socket的输入流和输出流
     * 3 获取系统输入流:System.in
     *      循环获取System.in中的内容 message  把message通过socket的输出流 发送给服务器端
     *      再通过socket的输出入流获取 服务器端的反馈
     *      
     *      如果想停止 就发送%_886_%
     *  4 关闭流
     * */
	public static void main(String[] args) throws Exception{
		// 获取系统输入流
		BufferedReader bin=new BufferedReader(new InputStreamReader(System.in));
		// 获取socket
		Socket socket=new Socket("localhost", 10086);
		// 获取输入流和输出流
		InputStream in=socket.getInputStream();
		OutputStream out=socket.getOutputStream();
	     String clientIpPort=socket.getLocalAddress().getHostAddress()+"_"+socket.getLocalPort();
		 String serverIpPort=socket.getInetAddress().getHostAddress()+"_"+socket.getPort();
		
		 while(true) {
			 //获取键盘输入的内容
			 String message=bin.readLine();
			 //把键盘输入的内容 通过socket的输出流发送出去
			 out.write(message.getBytes());
			 //判断是不是特殊字符
			 if(message.equals("%_886_%")) {
				  System.out.println(clientIpPort+"结束对话 发出了886!");
				  break;
			 }
			 //接收服务器的反馈
			 byte[] arr=new byte[1024];
			 int n=in.read(arr);
			 System.out.println(clientIpPort+"接收到服务器("+serverIpPort+")的反馈:"+new String(arr, 0,n));			 
		 }
		 socket.close();
	}
}
  • 服务器端
package day18_socket;
...
public class ZuoYe01Server {
    /*
     * 使用tcp实现:对客户端发送的信息 进行修改(大小写转换 删除数字) 后返回给客户端
     * 1 创建serversocket 开启服务 指定端口
     * 2 等待连接 获取socket
     * 3 获取输入流和输出流
     * 4  通过输入流获取客户端传递的信息 对信息转换 再发送给客户端
     * 5  如果接收到%_886_% 说明 结束对话
     * 6  关闭服务
     * */
	public static void main(String[] args)throws Exception {
		  ServerSocket server=new ServerSocket(10086);
		  Socket socket=server.accept();
		  InputStream in=socket.getInputStream();
		  OutputStream out=socket.getOutputStream();
		  //把字节流转化为字符流
		  InputStreamReader ir=new InputStreamReader(in);
		  String serverIpPort=socket.getLocalAddress().getHostAddress()+"_"+socket.getLocalPort();
		  String cilentIpPort=socket.getInetAddress().getHostAddress()+"_"+socket.getPort();
		  char[] arr=new char[1024];
		  int n;
		  while(true) {
			  n=ir.read(arr);
			  String line=new String(arr, 0,n);
			  if(line.equals("%_886_%")) {
				  System.out.println(serverIpPort+"与"+cilentIpPort+"的对话结束!");
				  break;
			  }
			  System.out.println(serverIpPort+"接收到"+cilentIpPort+"的信息:"+line);
			  //字符串转换
			  line=change(line);
			  out.write(line.getBytes());
		  }
		  

	}
	private static String change(String str) {
		StringBuilder stb=new StringBuilder(str);
		for (int i = 0; i <stb.length(); i++) {
			char c=stb.charAt(i);
			if(Character.isUpperCase(c)) {
				stb.setCharAt(i, Character.toLowerCase(c));
			}else if(Character.isLowerCase(c)) {
				stb.setCharAt(i, Character.toUpperCase(c));
			}else if(Character.isDigit(c)) {
				stb.deleteCharAt(i);i--;
			}
		}
		return stb.toString();
	}
}

2.3 案例3:文件上传

  • 客户端
package day18_socket;
...
public class ZuoYe02Client {
    //实现文件的上传::
	/*
	 * 1 获取socket
	 * 2 获取socket的输入流和输出流
	 * 3 通过输出流发送文件的名字给服务器端
	 * 4 循环的读源文件的信息 把读取的信息 通过socket的输出流 不断的发送给服务器端
	 * 5 怎么告诉服务器 文件上传完毕???socket.shutdownOutput(); 对方等待输入流 可以读到-1
	 * 6 关闭socket
	 * 
	 * */
	public static void main(String[] args) throws Exception{
		Socket socket=new Socket("localhost",10086);
		File file=new File("C:\\Users\\Administrator\\Desktop\\img\\0.jpeg");
		InputStream in=socket.getInputStream();
		OutputStream out=socket.getOutputStream();
		String clientIpPort=socket.getLocalAddress().getHostAddress()+"_"+socket.getLocalPort();
		String serverIpPort=socket.getInetAddress().getHostAddress()+"_"+socket.getPort();
		//先把文件名字发给服务器
		out.write(file.getName().getBytes());
		//循环读源文件
		//创建源文件对应的输入流
		FileInputStream fin=new FileInputStream(file);
		byte[] arr=new byte[1024];
		int n;
		while((n=fin.read(arr))!=-1) {
			//把读取的信息通过socket的输出流 发给服务器
			out.write(arr, 0, n);
		}
		//关闭socket的输出流
		socket.shutdownOutput();
		//接收服务器的反馈
		n=in.read(arr);
		System.out.println("客户端接收到服务器的反馈信息:"+new String(arr,0,n));
		socket.close();
		fin.close();
	}
}
  • 服务器端
package day18_socket;
...
public class ZuoYe02Server {

	public static void main(String[] args) throws Exception{
		//开启服务
		ServerSocket server=new ServerSocket(10086);
		//获取连接
		Socket socket=server.accept();
		//获取输入流和输出流
		InputStream in=socket.getInputStream();
		OutputStream out=socket.getOutputStream();
		String serverIpPort=socket.getLocalAddress().getHostAddress()+"_"+socket.getLocalPort();
		String clientIpPort=socket.getInetAddress().getHostAddress()+"_"+socket.getPort();
		//接收文件名
		byte[] arr=new byte[1024];
		int n=in.read(arr);
		String fileName=new String(arr,0,n);//file_xxx.jpg    dir_111
		//创建输出流 往目的文件中写
		FileOutputStream fout=new FileOutputStream("src/day18_socket/"+fileName);
		//接收客户端发送的文件的信息
		while(true) {
			//从socket中读取信息
			n=in.read(arr);
			//判断输入流是否关闭:::客户端关闭输出流  服务器端的输入流就能到达末尾  就能读到-1
			if(n==-1) {
				out.write((clientIpPort+"你发送的文件"+fileName+":已接收完毕!").getBytes());
				break;
			}
			//把读取的信息写道目的文件中
			fout.write(arr, 0, n);
		}
		//关闭服务
		socket.close();
		fout.close();
	}
}

2.4 案例4:文件夹上传

  • 客户端
package day18_socket;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

public class ZuoYe03Client {
    //实现文件的上传::
	/*
	 * 1 获取socket
	 * 2 获取socket的输入流和输出流
	 * 3 通过输出流发送文件的名字给服务器端
	 * 4 循环的读源文件的信息 把读取的信息 通过socket的输出流 不断的发送给服务器端
	 * 5 怎么告诉服务器 文件上传完毕???
	 * 6 关闭socket
	 * 
	 * */
	static String pathYuanDir;
	public static void main(String[] args) throws Exception{
		File file=new File("C:\\Users\\Administrator\\Desktop\\java43课堂记录");
		pathYuanDir=file.getParent();
		System.out.println();
		Socket socket=new Socket("localhost",10086);
		InputStream in=socket.getInputStream();
		OutputStream out=socket.getOutputStream();
		
		sendFile(file,out);//C:\\Users\\Administrator\\Desktop
		
		//关闭socket的输出流
		socket.shutdownOutput();
		//接收服务器的反馈
		byte[] arr=new byte[1024];
		int n;
		n=in.read(arr);
		System.out.println("客户端接收到服务器的反馈信息:"+new String(arr,0,n));
		socket.close();
	}
	
	//把参数文件 通过out发送给服务器
	private static void sendFile(File file,OutputStream out)throws Exception {
		if(file.isFile()) {
			//先把文件名字发给服务器
			String fileName=file.getAbsolutePath().replace(pathYuanDir+"\\", "");
			System.out.println("文件:::="+fileName);
			out.write(("%#@$!~file_"+fileName).getBytes());
			//循环读源文件
			//创建源文件对应的输入流
			FileInputStream fin=new FileInputStream(file);
			byte[] arr=new byte[1024];
			int n;
			while((n=fin.read(arr))!=-1) {
				//把读取的信息通过socket的输出流 发给服务器
				out.write(arr, 0, n);
			}
			fin.close();
			return ;
		}
		//遍历文件夹
		File[] zis=file.listFiles();
		for (File zi : zis) {
			sendFile(zi,out);
		}
	}
}
  • 服务器端
package day18_socket;
...
public class ZuoYe03Server {

	public static void main(String[] args) throws Exception{
		String muDiPath="C:\\Users\\Administrator\\Desktop\\aa";
		//开启服务
		ServerSocket server=new ServerSocket(10086);
		//获取连接
		Socket socket=server.accept();
		//获取输入流和输出流
		InputStream in=socket.getInputStream();
		OutputStream out=socket.getOutputStream();
		String serverIpPort=socket.getLocalAddress().getHostAddress()+"_"+socket.getLocalPort();
		String clientIpPort=socket.getInetAddress().getHostAddress()+"_"+socket.getPort();
		//循环的接收信息
		byte[] arr=new byte[1024];
		int n;
		FileOutputStream fout=null;
		while((n=in.read(arr))!=-1) {
			//获取读取的信息
			String line=new String(arr,0,n);
			//判断是不是名字
			if(line.startsWith("%#@$!~file_")) {//是文件名
				  if(fout!=null) {
					  fout.close();
				  }
				  line=line.replace("%#@$!~file_", "");
				  System.out.println("转换后的文件名:line="+line);
				  //文件名: aa\\bb\\1.xtx
				  File file=new File(muDiPath,line);
				  File dir=file.getParentFile();
				  //如果目的文件的文件夹不存在 就创建
				  if(!dir.exists()) {
					  dir.mkdirs();
				  }
				  System.out.println("文件="+file.getAbsolutePath());
				  fout=new FileOutputStream(file);	  
			}else {//文件内容
				  fout.write(arr, 0, n);
			}
		}
		out.write((clientIpPort+"你发送的文件夹:已接收完毕!").getBytes());
		//关闭服务
		socket.close();
	}
}

2.5 案例5:双方互发信息

双方互发信息 接收和发送互不干扰
  • 线程类:接收信息
//读的线程::接收对方发送的信息
class ReadThread extends Thread{
	Socket socket;
	private BufferedReader bin;
	public ReadThread(Socket socket) {
		this.socket=socket;
		try {
			bin=new BufferedReader(new InputStreamReader(socket.getInputStream()));
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
  
	public void run() {
		String me=socket.getLocalAddress().getHostAddress()+"_"+socket.getLocalPort();
		String him=socket.getInetAddress().getHostAddress()+"_"+socket.getPort();
		try {
			while(true) {
				String line=bin.readLine();
				System.out.println(me+" 接收到 "+him+" 的信息:"+line);
				if(line.equals("%886%")) {
					System.out.println(him+"结束聊天!");
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
  • 线程类:发送信息
//写的线程::给对方发送信息
class WriteThread extends Thread{
	Socket socket;
	private BufferedWriter bout;
	public WriteThread(Socket socket) {
		try {
			bout=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
    
	public void run() {
		//获取系统输入流 转化为高效字符流
		BufferedReader bin=new BufferedReader(new InputStreamReader(System.in));
		try {
			//一边读取console中的信息 一遍把读取的信息通过out写出去
			while(true) {
				String line=bin.readLine();
				bout.write(line);
				bout.flush();
				bout.newLine();
				bout.flush();
				//判断是否道末尾
				if(line.equals("&886&")) {
					break;
				}
				//socket.shutdownOutput();
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
  • 客户端
Socket socket=new Socket("localhost",10086);
//必须把读和写成两个线程
new ReadThread(socket).start();
new WriteThread(socket).start();
  • 服务器端
ServerSocket server=new ServerSocket(10086);
//等待连接
Socket socket=server.accept();
//通过socket的接收 和socket的发送需要同时进行
//必须把读和写成两个线程
new ReadThread(socket).start();
new WriteThread(socket).start();

2.6 案例6:群聊

所有的客户端把信息发给服务器  服务器把接收的所有信息转发给所有的客户端
  • 客户端发送的线程
//写的线程::给对方发送信息
class WriteThread2Client extends Thread{
	Socket socket;
	private BufferedWriter bout;
	public WriteThread2Client(Socket socket) {
		try {
			bout=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
  
	public void run() {
		//获取系统输入流 转化为高效字符流
		BufferedReader bin=new BufferedReader(new InputStreamReader(System.in));
		try {
			//一边读取console中的信息 一遍把读取的信息通过out写出去
			while(true) {
				String line=bin.readLine();
				bout.write(line);
				bout.flush();
				bout.newLine();
				bout.flush();
				//判断是否道末尾
				if(line.equals("&886&")) {
					break;
				}
				//socket.shutdownOutput();
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
  • 客户端接收的线程
//读的线程::接收对方发送的信息
class ReadThread2Client extends Thread{
	Socket socket;
	private BufferedReader bin;
	public ReadThread2Client(Socket socket) {
		this.socket=socket;
		try {
			bin=new BufferedReader(new InputStreamReader(socket.getInputStream()));
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public void run() {
		String me=socket.getLocalAddress().getHostAddress()+"_"+socket.getLocalPort();
		String him=socket.getInetAddress().getHostAddress()+"_"+socket.getPort();
		try {
			while(true) {
				String line=bin.readLine();
				System.out.println(me+" 接收到 "+him+" 的信息:"+line);
				if(line.equals("%886%")) {
					System.out.println(him+"结束聊天!");
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
  • 服务器端的线程
服务器需要给每个连接的客户端创建一个线程 用于给读取客户端交互
class SocketThread extends Thread{
	Socket socket;
	// Set<BufferedWriter> outSet;
	SocketThread(Socket socket){
		this.socket=socket;
	}
	public void run() {
		try {
			//获取socket的输入流
			BufferedReader bin=new BufferedReader(new InputStreamReader(socket.getInputStream()));
			String client=socket.getInetAddress().getHostAddress()+"_"+socket.getPort();
			while(true) {
				String line=bin.readLine();
					line=client+"说:"+line;
					for (BufferedWriter bout : MyChat02Server.outSet) {
						bout.write(line);
						bout.newLine();
						bout.flush();
					}
				
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
  • 客户端1
Socket socket=new Socket("localhost",10086,InetAddress.getByName("localhost"),50001);
//必须把读和写成两个线程
new ReadThread2Client(socket).start();
new WriteThread2Client(socket).start();
  • 客户端2
Socket socket=new Socket("localhost",10086,InetAddress.getByName("localhost"),50002);
//必须把读和写成两个线程
new ReadThread2Client(socket).start();
new WriteThread2Client(socket).start();
  • 客户端3
Socket socket=new Socket("localhost",10086,InetAddress.getByName("localhost"),50003);
//必须把读和写成两个线程
new ReadThread2Client(socket).start();
new WriteThread2Client(socket).start();
  • 服务器端
public class MyChat02Server {
    //创建一个set 装对所有客户端的输出流
    static  Set<BufferedWriter> outSet=new HashSet<BufferedWriter>();
    public static void main(String[] args) throws Exception{
        ServerSocket server=new ServerSocket(10086);
        while(true) {
            //等待连接
            Socket socket=server.accept();
            outSet.add(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
            //每个socket因该对应一个线程:线程任务 获取信息   通过socketSet中的所有输出流发出去
            new SocketThread(socket).start();
        }
    }
}

2.7 案例7:群聊+单聊

只需要更改服务器端即可:当接收道信息后 对学校进行解析 判断是群聊 还是单聊
如果以@@@@####开头 就是单聊 从中获取接收方的ip+port 然后把信息单独发给他
package day18_socket;
...
public class MyChat03Server {
	  //创建一个set 装对所有客户端的输出流
	static Map<String, BufferedWriter> ipOutMap=new HashMap<>();
	public static void main(String[] args) throws Exception{
		  ServerSocket server=new ServerSocket(10086);
		  while(true) {
			//等待连接
			  Socket socket=server.accept();
			  String client=socket.getInetAddress().getHostAddress()+"_"+socket.getPort();
			  ipOutMap.put(client,new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
			  //每个socket因该对应一个线程:线程任务 获取信息   通过socketSet中的所有输出流发出去
			  new SocketThread3(socket).start();
		  }
	}
}
class SocketThread3 extends Thread{
	Socket socket;
	SocketThread3(Socket socket){
		this.socket=socket;
	}
	public void run() {
		try {
			//获取socket的输入流
			BufferedReader bin=new BufferedReader(new InputStreamReader(socket.getInputStream()));
			String client=socket.getInetAddress().getHostAddress()+"_"+socket.getPort();
			while(true) {
				String line=bin.readLine();
				if(line.equals("show me clients!")) {
					//获取当前用户的输出流
					BufferedWriter out=MyChat03Server.ipOutMap.get(client);
					out.write(MyChat03Server.ipOutMap.keySet().toString());
					out.newLine();
					out.flush();
				}else if(line.startsWith("@@@@####")) {//是私聊::私聊格式:@@@@####127.0.0.1_50002:xxxx
					String ip_port=line.substring(8, line.indexOf(":"));
					//获取对方的输出流
					BufferedWriter out=MyChat03Server.ipOutMap.get(ip_port);
					//给对方发送信息:
					line=client+"私聊的信息:"+line.substring(line.indexOf(":")+1);
					out.write(line);
					out.newLine();
					out.flush();
				}else {
					line=client+"说:"+line;
					for (BufferedWriter bout : MyChat03Server.ipOutMap.values()) {
						bout.write(line);
						bout.newLine();
						bout.flush();
					}
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}

3 udp


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值