JAVA中的Socket一对一聊天程序

1.网络编程简要概述:

    网络编程实质实质就是两个(或多个)设备(例如计算机)之间的数据传输。而要实现两台计算机通过互联网连接进行数据传输,必输要满足计算机网络的5层协议(物理层,数据链路层,网络层,运输层,应用层);当然有划分可能不同,但现在大家比较容易接受理解的是五层模型。而其中前三层物理层,数据链路层以及网络层,作为java程序员暂时是不能直接对他进行控制的。
1. 运输层主要有两种协议TCP和UDP,TCP通过握手协议进行可靠的连接,UDP则是不可靠连接。 
2. 应用层主要就是应用层直接和应用程序接口并提供常见的网络应用服务,主要协议有HTTP,FTP,DNS等。 
3. IP地址:用与标记一台计算机的身份证,此去需要注意的是网络地址转换技术NAT的存在,我们的个人计算机基本上都是专用网络IP(192.168..),这个IP只能本地电脑访问。只有当我们有公网IP后,才能使自己的电脑被世界所有连接在互联网中的电脑访问到。 
4. 服务端口:计算机通过不同的服务端口来区分不同网络服务,就像通过进程号区分不同的进程一样;常见服务的端口,HTTP(80),FTP(21). 
5. URL:统一资源定位符。只能定位互联网资源。

URL基本格式:
protocol://hostname:port/resourcename#anchor
protocol:使用的协议,可以是http,ftp,news,telnet等
hostname:主机名
port:端口号,可选
resourcename:资源名,主机上能访问到的目录或文件
anchor:标记,可选,指定文件中的有特定标记的位置
如:
http://localhost:8080/HttpSer/index.html
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

下面主要通过UDP和socket编程来讲解网络编程;

2. UDP编程介绍:

  1. 简介: 
    UDP(User Datagram Protocol),中文意思是用户数据报协议,方式类似于发短信息,是一种物美价廉的通讯方式,使用该种方式无需建立专用的虚拟连接,由于无需建立专用的连接,所以对于服务器的压力要比TCP小很多。是一种尽可能可靠传输的协议。主要用于视频电话等对信息准确传输性不高追求速度的应用程序。
  2. 主要类的讲解: 
    DatagramSocket: 
            DatagramSocket类实现“网络连接”,包括客户端网络连接和服务器端网络连接。虽然UDP方式的网络通讯不需要建立专用的网络连接,但是毕竟还是需要发送和接收数据,DatagramSocket实现的就是发送数据时的发射器,以及接收数据时的监听器的角色 
    DatagramPacket: 
            DatagramPacket类实现对于网络中传输的数据封装,也就是说,该类的对象代表网络中交换的数据。在UDP方式的网络编程中,无论是需要发送的数据还是需要接收的数据,都必须被处理成DatagramPacket类型的对象,该对象中包含发送到的地址、发送到的端口号以及发送的内容等。
  3. 一个简单的udp通信: 
    客户端:
        DatagramSocket ds = null;
            //定义一个UDP来发送数据
            ds = new DatagramSocket();
            //假设发送的数据是个字符串
            String hello = "hello world";
            //定义一个UDP的数据发送包来发送数据,inetSocketAddress表示要接收的地址
            DatagramPacket dp = new DatagramPacket(hello.getBytes()
                        ,hello.getBytes().length,new InetSocketAddress("127.0.0.1", 9999));
            for(int i=0;i<10;i++) {
    //数据发送
                ds.send(dp);
    //线程睡眠1s
                Thread.sleep(1000);
            }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

服务端:

DatagramSocket ds = null;
        //UDP接受端连接
        ds = new DatagramSocket(9999);
        //定义将UDP的数据包接收到什么地方
        byte[] buf = new byte[1024];
        //定义UDP的数据接收包
        DatagramPacket dp = new DatagramPacket(buf, buf.length);
        while(true) {
            //接收数据包
            ds.receive(dp);
  //将数据转换输出:
            String str = new String(dp.getData(),0,dp.getLength());
            System.out.println(str);
        }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3.socket编程介绍:

  1. socket简介: 
    Socket是TCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定。 
    而TCP是Tranfer Control Protocol的 简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送 或接收操作。
  2. 主要的类介绍: 
    1.建立服务器类:ServerSocket 
    可以用服务器需要使用的端口号作为参数来创建服务器对象: 
    ServerSocket serverSocket = new ServerSocket(8888) 
    这条语句创建了一个服务器对象,这个服务器使用8888号端口。当一个客户端程序建立一个Socket连接,所连接的端口号为8888时,服务器对象server便响应这个连接,并且server.accept()方法会创建一个Socket对象。服务器端便可以利用这个Socket对象与客户进行通讯。
//进行监听,当客户端没数据发送过来时,停在这里;
    Socket s=serverSocket.accept();
 
 
  • 1
  • 2

2.通信类:Socket 
服务器端和客户端可以通过Socket类进行通信: 
客户端建立socket类过程:

//建立socket客户端:第一个参数:ip地址;第二个参数:发送的端口,假如没有服务器,会停在这里,然后扔出异常;
    Socket socket=new Socket("192.168.1.105", 8888);
 
 
  • 1
  • 2

通过socket类可以获得输入输出流进行通信:

//获得socket的输出流
PrintWriter out=new PrintWriter(socket.getOutputStream());
            //获得socket的输入流
BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
简单的socket通信一对一
服务端:
package onetoone;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.*;
import java.net.*;
import java.util.Calendar;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder;

public class ChatServer extends JFrame{
 private static final long serialVersionUID = 1L;
 private static int port=8000;//默认端口
 
 static String displayInfo="Visy:本程序的聊天记录是保存在内存变量中的,所以注意:"+
      "\n1、退出后内容会丢失,重要信息请复制到其他地方保存\n2、"+
        "聊天记录过多时请及时点击“清除记录”清除,以免内存不足\n----------------------------"+
      "------------------------------------------------------\n\n";
 private static JTextArea jtaContent=new JTextArea("");
 public static JButton jbtSend=new JButton("发送");
 private static JTextArea jtaChatRecord=new JTextArea("");
    private JLabel jlbHostIp=new JLabel("本机IP:");
    private JLabel jlbOpenPort=new JLabel("开放端口:");
    public static JLabel jlbIsStart;
    private JLabel jtfIp=new JLabel();
    private JTextField jtfPort=new JTextField(5);
    private static JButton jbtStart;
    private JButton jbtClear;
 
    static DataOutputStream outToClient;
    static DataInputStream inputFromClient;
   
    static PrintWriter pw;
 
    ChatServer(){
   //界面布局----------------------------------------
  JPanel panelTop=new JPanel(new FlowLayout());
  panelTop.add(jlbHostIp);
  String hostIp;
  try {
   hostIp = InetAddress.getLocalHost().getHostAddress().toString();//本机IP
   jtfIp.setText(hostIp);
  } catch (UnknownHostException e) {
   jtfIp.setText("获取失败");
  }
  jtfIp.setForeground(Color.orange);
  panelTop.add(jtfIp);
  panelTop.add(jlbOpenPort);
  jtfPort.setText(""+port);jtfPort.setForeground(Color.orange);panelTop.add(jtfPort);
  jbtStart=new JButton("启动");jbtStart.setBackground(Color.green);panelTop.add(jbtStart);
  jlbIsStart=new JLabel("[尚未启动]");jlbIsStart.setForeground(Color.RED);panelTop.add(jlbIsStart);
  jbtClear=new JButton(" 清除记录");jbtClear.setBackground(Color.YELLOW);panelTop.add(jbtClear);
  panelTop.setBorder(new LineBorder(Color.BLACK));
  
  JPanel panel1=new JPanel(new BorderLayout());
  panel1.add(jtaContent,BorderLayout.CENTER);
  jbtSend.setEnabled(false);
  jbtSend.setBackground(Color.YELLOW);jbtSend.setMnemonic(KeyEvent.VK_ENTER);panel1.add(jbtSend,BorderLayout.EAST);
  panel1.setBorder(new LineBorder(Color.BLACK));
  
  JPanel panel2=new JPanel(new BorderLayout());
  //把定义的JTextArea放到JScrollPane里面去
  JScrollPane scrollPanel = new JScrollPane(jtaChatRecord);
  //分别设置水平和垂直滚动条自动出现
  scrollPanel.setHorizontalScrollBarPolicy( JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
  scrollPanel.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
  
  panel2.add(scrollPanel,BorderLayout.CENTER);
  panel2.setBorder(new TitledBorder("聊天记录"));
  
  JPanel panelDown=new JPanel(new BorderLayout());
  panelDown.add(panel1,BorderLayout.SOUTH);
  panelDown.add(panel2,BorderLayout.CENTER);
  
  add(panelTop,BorderLayout.NORTH);
  add(panelDown,BorderLayout.CENTER);
  
  setTitle("服务端");
  setSize(500,390);
  setLocationRelativeTo(null);
  setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  setVisible(true);
  //--------------------------------------------
  
  
  //启动监听服务
  jbtStart.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent e) {
       port=Integer.parseInt(jtfPort.getText());//获取自定义端口号
    //由于服务线程中设计死循环,所以单独做一个线程处理
       if(port<1024){
     JOptionPane.showMessageDialog(null,"1~1024是系统预留端口,请选择1025~65535号端口!");
    }else{
     Runnable serverRun=new StartServer(jlbIsStart,port,inputFromClient,outToClient,jtaChatRecord);
     Thread serverThread=new Thread(serverRun);
     serverThread.start();
    }
   }
  });
  //发送消息事件
  jbtSend.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent e) {
    String sendInfo=jtaContent.getText();//获取输入
    String sendMsg="我:"+sendInfo+"    "+getCurrentTime()+"\n";//附加信息
    displayInfo=displayInfo+sendMsg;//拼接信息,用变量存储聊天记录
    jtaChatRecord.setText(displayInfo);//在窗口中显示聊天记录
    openSendThread(sendInfo);
    jtaContent.setText("");//清空输入框
   }
  });
  //清空变量的数据和窗口显示
  jbtClear.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent e) {
    displayInfo="";
    jtaChatRecord.setText("");
   }
  });
 }
 
 //获取系统当前时间
 public static String getCurrentTime(){
  Calendar ca = Calendar.getInstance();    
  int y= ca.get(Calendar.YEAR);//获取年份  
  int m=ca.get(Calendar.MONTH)+1;//获取月份    
  int d=ca.get(Calendar.DATE);//获取日     
  int h=ca.get(Calendar.HOUR_OF_DAY);//小时 
  int min=ca.get(Calendar.MINUTE);//分   
  int s=ca.get(Calendar.SECOND);//秒 
  return "["+y+"/"+m+"/"+d+" "+h+":"+min+":"+s+"]";
 }
 
 //启动发送消息的线程
 public static void openSendThread(String sendMsg){
  Runnable sendRun=new sendMsgToClient(sendMsg);
  Thread sendThread=new Thread(sendRun);
  sendThread.start();
 }
 
 //启动窗口程序
 public static void main(String[] args) {
  new ChatServer();
 }
}

//用于接收信息的线程
class GetMsgFromClient implements Runnable{
 private DataInputStream in;
 private JTextArea jtaChatRecord;
 
 public GetMsgFromClient(DataInputStream in,JTextArea jtaChatRecord){
  this.in=in;
  this.jtaChatRecord=jtaChatRecord;
 }
 @Override
 public void run() {
  try {
   BufferedReader br=new BufferedReader(new InputStreamReader(in));
   String msg = "";
   while ((msg = br.readLine()) != null) {
    String getMsg="对方:"+msg+"    "+ChatServer.getCurrentTime()+"\n";
    ChatServer.displayInfo=ChatServer.displayInfo+getMsg;
    jtaChatRecord.setText(ChatServer.displayInfo);
   }
  } catch (SocketException e) {
   JOptionPane.showMessageDialog(null,"连接断开!");
   ChatServer.jlbIsStart.setForeground(Color.RED);
   ChatServer.jlbIsStart.setText("[连接断开]");
   ChatServer.jbtSend.setEnabled(false);
  }catch (IOException e) {
   e.printStackTrace();
  }
 }
}
//用于发送信息的线程
class sendMsgToClient implements Runnable{
 //DataOutputStream out;
 String  sendMsg;
 
 public sendMsgToClient(String sendMsg){
  //this.out=out;
  this.sendMsg=sendMsg;
 }
 @Override
 public void run() {
  //System.out.println("S"+sendMsg);
	 ChatServer.pw.println(sendMsg);
  ChatServer.pw.flush();
 }
}
//监听线程,避免阻死UI线程,所以这里单独作为一个线程
class StartServer implements Runnable{
 JLabel jlbIsStart;
 int port;
 DataInputStream inputFromClient;
 DataOutputStream outToClient;
 JTextArea jtaChatRecord;
 
 StartServer(JLabel jlbIsStart,int port,DataInputStream inputFromClient,
 DataOutputStream outToClient,JTextArea jtaChatRecord){
  this.jlbIsStart=jlbIsStart;
  this.port=port;
  this.inputFromClient=inputFromClient;
  this.outToClient=outToClient;
  this.jtaChatRecord=jtaChatRecord;
 }
 
 @Override
 public void run() {
  try {
   
   jlbIsStart.setForeground(Color.BLUE);
   jlbIsStart.setText("[正在启动]");
   @SuppressWarnings("resource")
   ServerSocket ssocket=new ServerSocket(port);
   jlbIsStart.setForeground(Color.BLACK);
   jlbIsStart.setText("[等待连接]");
   
   while(true){
   Socket socket=ssocket.accept();
   jlbIsStart.setForeground(Color.GREEN);
   jlbIsStart.setText("[连接成功]");
   ChatServer.jbtSend.setEnabled(true);
   
   //获得数据输入流
   inputFromClient=new DataInputStream(socket.getInputStream());
   
   //获得数据输出流
   outToClient=new DataOutputStream(socket.getOutputStream());
   ChatServer.pw=new PrintWriter(outToClient);
   
   Runnable getRun=new GetMsgFromClient(inputFromClient,jtaChatRecord);
   Thread getThread=new Thread(getRun);
   getThread.start();
   
   }
   
  }catch (BindException e) {
   JOptionPane.showMessageDialog(null,"端口被占用!请换个端口试试");
   jlbIsStart.setText("[端口占用]");
  }catch (IOException e) {
   e.printStackTrace();
  }
 }
}

//客户端(Client)源码:


package onetoone;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.*;
import java.net.*;
import java.util.Calendar;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder;


public class ChatClient extends JFrame{
 private static final long serialVersionUID = 1L;
 
 private static String ipSite="127.0.0.1";
 private static int port=8000;
 
    static String displayInfo="Visy:本程序的聊天记录是保存在内存变量中的,所以注意:"+
    "\n1、退出后内容会丢失,重要信息请复制到其他地方保存\n2、"+
      "聊天记录过多时请及时点击“清除记录”清除,以免内存不足\n----------------------------"+
    "------------------------------------------------------\n\n";
 
 private static JTextArea jtaContent=new JTextArea("");
    static JButton jbtSend=new JButton("发送");
 private static JTextArea jtaFromServer=new JTextArea("");
    private JLabel jlbIp=new JLabel("对方IP:");
    private JLabel jlbPort=new JLabel("端口:");
    public static JLabel jlbIsCon;
    private JTextField jtfIp=new JTextField(8);
    private JTextField jtfPort=new JTextField(5);
    public static JButton jbtCon;
    private JButton jbtClear;
 
    static DataOutputStream outputToServer;
    static DataInputStream inputFromServer;
    ChatClient() {
      //界面布局----------------------------------------
   JPanel panelTop=new JPanel(new FlowLayout());
   panelTop.add(jlbIp);
   jtfIp.setText(ipSite);panelTop.add(jtfIp);
   panelTop.add(jlbPort);
   jtfPort.setText(""+port);panelTop.add(jtfPort);
   jbtCon=new JButton("连接");jbtCon.setBackground(Color.green);panelTop.add(jbtCon);
   jlbIsCon=new JLabel("  [尚未连接]");jlbIsCon.setForeground(Color.RED);panelTop.add(jlbIsCon);
   jbtClear=new JButton(" 清除记录");jbtClear.setBackground(Color.YELLOW);panelTop.add(jbtClear);
   panelTop.setBorder(new LineBorder(Color.BLACK));
   
   JPanel panel1=new JPanel(new BorderLayout());
   panel1.add(jtaContent,BorderLayout.CENTER);
   jbtSend.setEnabled(false);
   jbtSend.setBackground(Color.YELLOW);jbtSend.setMnemonic(KeyEvent.VK_ENTER);panel1.add(jbtSend,BorderLayout.EAST);
   panel1.setBorder(new LineBorder(Color.BLACK));
   
   JPanel panel2=new JPanel(new BorderLayout());
   //把定义的JTextArea放到JScrollPane里面去
   JScrollPane scrollPanel = new JScrollPane(jtaFromServer);
   //分别设置水平和垂直滚动条自动出现
   scrollPanel.setHorizontalScrollBarPolicy( JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
   scrollPanel.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
   
   panel2.add(scrollPanel,BorderLayout.CENTER);
   panel2.setBorder(new TitledBorder("聊天记录"));
   
   JPanel panelDown=new JPanel(new BorderLayout());
   panelDown.add(panel1,BorderLayout.SOUTH);
   panelDown.add(panel2,BorderLayout.CENTER);
   
   add(panelTop,BorderLayout.NORTH);
   add(panelDown,BorderLayout.CENTER);
   
   setTitle("客户端");
   setSize(500,390);
   setLocationRelativeTo(null);
   setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   setVisible(true);
   //--------------------------------------------
   //启动和服务端的连接
   jbtCon.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
     ipSite=jtfIp.getText();
        port=Integer.parseInt(jtfPort.getText());
     connectServer();
    }
   });
   //发送消息
   jbtSend.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
     String sendInfo=jtaContent.getText();
     String sendMsg="我:"+sendInfo+"    "+getCurrentTime()+"\n";
     displayInfo=displayInfo+sendMsg;
     jtaFromServer.setText(displayInfo);
     openSendThread(sendInfo);
     jtaContent.setText("");
    }
   });
   //清屏和聊天数据
   jbtClear.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
     displayInfo="";
     jtaFromServer.setText("");
    }
   });
 }
 //获取系统当前时间
 public static  String getCurrentTime(){
  Calendar ca = Calendar.getInstance();    
  int y= ca.get(Calendar.YEAR);//获取年份  
  int m=ca.get(Calendar.MONTH)+1;//获取月份    
  int d=ca.get(Calendar.DATE);//获取日     
  int h=ca.get(Calendar.HOUR_OF_DAY);//小时 
  int min=ca.get(Calendar.MINUTE);//分   
  int s=ca.get(Calendar.SECOND);//秒 
  return "["+y+"/"+m+"/"+d+" "+h+":"+min+":"+s+"]";
 }
 
 public static void connectServer(){
       try {
         jlbIsCon.setForeground(Color.BLUE);
         jlbIsCon.setText("  [尝试连接]");
    
         //通过IP地址和端口号获得一个socket
   @SuppressWarnings("resource")
   Socket socket=new Socket(ipSite,port);
   jlbIsCon.setForeground(Color.GREEN);
   jlbIsCon.setText("  [连接成功]");
   jbtSend.setEnabled(true);
   jbtCon.setText("刷新");
   //System.out.println("客户端已启动###");
   
   //获得数据输入流
   inputFromServer=new DataInputStream(socket.getInputStream());
   
   //获得数据输出流
   outputToServer=new DataOutputStream(socket.getOutputStream());
   
   Runnable getRun=new GetMsgFromServer(inputFromServer,jtaFromServer);
   Thread getThread=new Thread(getRun);
   getThread.start();
   
  }catch (Exception e) {
   JOptionPane.showMessageDialog(null, "连接服务器失败!");
   jbtSend.setEnabled(false);
   jlbIsCon.setForeground(Color.RED);
         jlbIsCon.setText("  [连接失败]");
         jbtCon.setText("连接");
  }
 }
 
 public void openSendThread(String sendMsg){
  Runnable sendRun=new SendMsgToServer(outputToServer,sendMsg);
  Thread sendThread=new Thread(sendRun);
  sendThread.start();
 }
 
 //启动客户端窗口
 public static void main(String[] args) {
     new ChatClient();
 }
}

//接收服务端信息的线程
class GetMsgFromServer implements Runnable{
 private DataInputStream in;
 private JTextArea jtaInput;
 
 public GetMsgFromServer(DataInputStream in,JTextArea jtaFromServer){
  this.in=in;
  this.jtaInput=jtaFromServer;
 }
 
 @Override
 public void run() {
  try {
            BufferedReader br=new BufferedReader(new InputStreamReader(in));
   String msg="";
   while ((msg = br.readLine()) != null) {
    String getMsg="对方:"+msg+"    "+ChatClient.getCurrentTime()+"\n";
    ChatClient.displayInfo=ChatClient.displayInfo+getMsg;
    jtaInput.setText(ChatClient.displayInfo);
   }
  } catch (IOException e) {
   JOptionPane.showMessageDialog(null,"连接断开!");
   ChatClient.jbtSend.setEnabled(false);
   ChatClient.jlbIsCon.setForeground(Color.RED);
   ChatClient.jlbIsCon.setText("  [连接掉线]");
   ChatClient.jbtCon.setText("连接");
  }
 }
}
//发送信息到服务端的线程
class SendMsgToServer implements Runnable{
 private DataOutputStream out;
 private String  sendMsg;
 public SendMsgToServer(DataOutputStream out,String sendMsg){
  this.out=out;
        this.sendMsg=sendMsg;
 }
 
 public void run() {
  PrintWriter pw=new PrintWriter(out, true);
  pw.println(sendMsg);
 }
}










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值