网络编程

一、网络编程的一些基本概念
(一)网络通信的三个基本要素
1、通信协议:
    在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。
    理论上有7层,OSI参考模型。物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
    这个理论太过于精细,过了理论化,实际中没有真正实施,而是有另一个模型代替。
    实际发挥作用的是TCP/IP协议参考模型。
    这个模型是分为四层:物理层+数据链路层、网络层、传输层、应用层。
    TCP/IP协议参考模型的协议有很多,为什么用TCP和IP两个协议作为命名呢?
    因为TCP/IP协议是在最早定下来的(大家最早达成一致协议),并且这两个协议是最最重要的。

TCP:传输控制协议 (Transmission Control Protocol)。面向连接的,可靠的,基于字节流的网络协议。
     面向连接:如果通信双方要通信之前,必须是联通状态的。
            在传输数据之前,会进行“三次握手”来确保网络连接是正常的。
            TCP协议的程序是分为客户端(包括B和C)和服务器端。
            第一次握手:客户端先发起请求,会给服务器发一些状态码
                    比喻:问服务器,我能和你连接吗?  暗号:今晚约吗?
            第二次握手:服务器响应客户端,会给客户端返回状态码,并且加一些状态码信息
                    比喻:可以连接?    回复:你刚才问的是“今晚约吗?”,我回答“可以约”
                    服务器回复你刚才问的是“今晚约吗?”的意义,是让客户端确认它刚才给服务器的消息是正确传递的。
            第三次握手:客户端再次回应服务器。       回复:你刚才说的是“可以约”,我回答“老地方见”
                    客户端回复你刚才说的是“可以约”,让服务器确认它发给客户端的消息是正确传递的

            三次握手没问题之后,再发送真实的数据。

            在传输数据结束,会进行“四次挥手”来断开连接。
            第一次挥手:客户端先发起断开连接的请求。
                    服务器收到后,要关闭接收数据通道,表示服务器不再接收客户端发送的数据了。
            第二次挥手:服务器响应客户端,告诉客户端,它知道了要断开了,
                    但是要通知客户端,你别着急彻底断开,我给你的数据还没完,你要把剩下的数据接收完,再断开。
            第三次挥手:服务器把剩下的数据发送完了之后,告诉客户端,现在咱们可以彻底断开了。
            第四次挥手:客户端会告诉服务器,我准备好了,然后可以彻底断开了。
                    第四次挥手时,客户端在发送完最后的“拜拜”时,会等一会儿。
                    等待的意义是让服务器确实收到了最后的断开的消息。
                    如果对方没收到,TCP协议会让客户端重传,这个时候,客户端还发送一遍,再次等待。
    可靠的:如果中间丢包了,TCP会让对方重传。
UDP:用户数据报协议(User Datagram Protocol)非面向连接的,不可靠的,基于数据报的协议。
    非面向连接的:发送方不管接收方是否在线,都是直接发送消息。
    不可靠:如果丢包了,就丢了。
    优点:快
    适用于:实时会议,视频等


网络程序的结构:B/S,C/S结构
B:浏览器browser,它本质上也是客户端,只是统一的客户端
S:服务器端server
C:客户端   需要用户单独下载和安装的客户端程序


2、IP地址:消息(数据)从哪里来到哪里去,都需要通过IP地址标识
    IP地址是用来唯一的定位/标识一台主机用的。

    域名:为了方便用户记忆主机地址。
        www.atguigu.com  <=  域名解析器  =>IP地址

    本地回环IP:127.0.0.1

3、端口号
    同一台主机上同时有多个应用程序都在和网络进行通信,
    那么端口号就是用来唯一定位/标识一个应用程序的。

    mysql:3306
    tomcat:8080
    http:80
    。。。。

    公共端口:0-1023  1024个
    注册端口:被一些知名软件或应用程序注册
    动态/ 私有端口:49152~65535

  (二)Socket编程
无论基于TCP协议还是基于UDP协议的网络通信程序,都要用到Socket进行编程。
Socket(套接字):代表网络通信的一个端点。负责和网卡驱动程序交换,发送或接收消息。涉及到IP地址、端口号等内容。
分为两大类:
(1)流套接字:用于TCP协议编程
        SeverSocket:负责客户端和服务器端的连接用,即是服务器端用来等待和接收客户端连接用的,不负责传输数据
        Socket:客户端和服务器端传输数据用的
(2)数据报套接字:用于UDP协议编程
        DatagramSocket:既要负责连接,又要负责传输数据。

端口号:int类型的数字,范围0-65535之间
IP地址:
    (1)字符串:“127.0.0.1”
        不是所有的API都支持字符串类型
    (2)byte[]:
        {127,0,0,1}
        不是所有的API都支持byte[]类型
      (3)InetAddress类的对象

public class TestIPAndPort {
    public static void main(String[] args) throws Exception{
        //获取本地IP地址
        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println(localHost);//LAPTOP-AG8KORH9/192.168.131.1
                                    //LAPTOP-AG8KORH9/192.168.35.82

        //获取尚硅谷官网的ip地址
        InetAddress atguigu = InetAddress.getByName("www.atguigu.com");
        System.out.println(atguigu);

        byte[] ips = {(byte)192,(byte)168,35,82};
        InetAddress address = InetAddress.getByAddress(ips);
        /*
        192的二进制 00000000 00000000 00000000 11000000(int)
                    11000000(byte)
                    底层源码中是使用int处理的,会在11000000前面加24个0恢复原来
         ip地址的每一个数字是[0,255]      [00000000,11111111 ]
         */
    }
}

二、UDP:用户数据报协议(User Datagram Protocol)

接收方:(先运行才能收到消息)
第一步:准备一个Socket
    DatagramSocket ds = new DatagramSocket(8888);
    IP地址:默认是本机
第二步:准备数据报
    byte[] data = new byte[1024];
    DatagramPacket dp = new DatagramPacket(data, data.length);
第三步:接收数据
      ds.receive(dp);
第四步:处理数据
    数据在字节数组中,长度是dp.getLength()
第五步:关闭
    ds.close();

public class TestReceive {
    public static void main(String[] args)throws Exception {
//        第一步:准备一个Socket
        DatagramSocket ds = new DatagramSocket(8888);

//        第二步:准备数据报
        //DatagramPacket(byte[] buf, int length)
        byte[] data = new byte[1024];
        DatagramPacket dp = new DatagramPacket(data, data.length);

        //第三步:接收数据
        //通过socket,接收数据报
        ds.receive(dp);

        //第四步:处理数据
        int length = dp.getLength();//实际接收的数据的长度
        String str = new String(data, 0, length);
        System.out.println(str);

//        第五步:关闭
        ds.close();
    }
}

发送方:
步骤:
第一步:准备一个Socket
    DatagramSocket ds = new DatagramSocket();
    这边的端口号:随机分配
    IP地址:默认是本机
第二步:准备数据报
DatagramPacket(byte[] buf, int length)
          构造 DatagramPacket,用来接收长度为 length 的数据包。
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
          构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
DatagramPacket(byte[] buf, int offset, int length)
          构造 DatagramPacket,用来接收长度为 length 的包,在缓冲区中指定了偏移量。
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
          构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)
          构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。
DatagramPacket(byte[] buf, int length, SocketAddress address)
          构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。

第三步:发生数据
    ds.send(dp);
第四步:关闭
    ds.close();

public class TestSend {
    public static void main(String[] args)throws Exception {
        //第一步:准备一个Socket
        DatagramSocket ds = new DatagramSocket();

        //第二步:准备数据报
        //DatagramPacket(byte[] buf, int length, InetAddress address, int port)
        String data = "明天终于可以休息了";
        byte[] buf = data.getBytes();//默认使用平台的编码方式UTF-8进行编码
        //发给自己
        // InetAddress address = InetAddress.getLocalHost();//接收方现在也是本机
        /*
        发给同桌:

         */
        byte[] ips = {(byte)192,(byte)168,35,82};
        InetAddress address = InetAddress.getByAddress(ips);
        /*
        192的二进制 00000000 00000000 00000000 11000000(int)
                    11000000(byte)
                    底层源码中是使用int处理的,会在11000000前面加24个0恢复原来
         ip地址的每一个数字是[0,255]      [00000000,11111111 ]
         */
        DatagramPacket dp = new DatagramPacket(buf,buf.length, address, 8888);//8888接收方的端口号


        //第三步:发生数据
        //通过socket发生数据报
        ds.send(dp);
        System.out.println("发送完毕");

//        第四步:关闭
        ds.close();
    }
}

三、TCP:传输控制协议 (Transmission Control Protocol)

服务器端:
第一步:开启服务器,注册监听的端口号
    ServerSocket server = new ServerSocket(8888);
第二步:监听并接收客户端的连接  ,如果成功了,就会有一个Socket对象

第三步:可以接收也可以发送
    假设我这里是先演示接收一个消息

第四步:关闭

public class Server1{
    public static void main(String[] args)throws Exception {
//        第一步:开启服务器,注册监听的端口号
        ServerSocket server = new ServerSocket(8888);

        //第二步:监听并接收客户端的连接
        Socket socket = server.accept();
        System.out.println("一个客户端连接成功了");
        System.out.println("这个客户端的IP地址:" + socket.getInetAddress().getHostAddress());

  /*      第三步:可以接收也可以发送
                假设我这里是先演示接收一个消息*/
        //接收消息需要输入流
        InputStream inputStream = socket.getInputStream();
        byte[] data = new byte[1024];
        int len;
        while((len = inputStream.read(data)) != -1){
            System.out.println(new String(data,0,len));
        }

        //第四步:关闭
        inputStream.close();
        socket.close();
        server.close();
    }
}
public class Server2{
    public static void main(String[] args)throws Exception {
//        第一步:开启服务器,注册监听的端口号
        ServerSocket server = new ServerSocket(8888);

        //第二步:监听并接收客户端的连接
        Socket socket = server.accept();
        System.out.println("一个客户端连接成功了");
        System.out.println("这个客户端的IP地址:" + socket.getInetAddress().getHostAddress());

  /*      第三步:可以接收也可以发送*/
        //(1)接收消息需要输入流
        InputStream inputStream = socket.getInputStream();
        byte[] data = new byte[1024];
        int len;
        while((len = inputStream.read(data)) != -1){
            System.out.println(new String(data,0,len));
        }

        //(2)返回消息,使用输出流
        OutputStream outputStream = socket.getOutputStream();
        String str = "hi";
        outputStream.write(str.getBytes());


        //第四步:关闭
        outputStream.close();
        inputStream.close();
        socket.close();
        server.close();
    }
}
/*
TCP编程的服务器端:
    可以同时接收多个客户端的连接
    可以接收多次的客户端的消息,每次接收完客户端发送的消息后,反转消息内容返回
 */
public class Server3 {
    public static void main(String[] args)throws Exception {
        //第一步:开启服务器,并且注册端口号
        ServerSocket server = new ServerSocket(8888);

        int count = 0;
        while(true) {
            //第二步:接收客户端的连接
            Socket socket = server.accept();
            System.out.println("第" + ++count + "个客户端连接,它的IP是:" + socket.getInetAddress());

            //每接收一个客户端,就分配一个线程给他单独通信
            MessageThread thread = new MessageThread(socket);
            thread.start();
        }
//        server.close();//服务器不关了
    }
}

class MessageThread extends Thread{
    private Socket socket;

    public MessageThread(Socket socket) {
        this.socket = socket;
    }

    public void run(){
        try(
                //第三步:接收客户端发送的词语
                //因为本例中使用的是纯文本消息,所以可以使用按行读的方式
                InputStream inputStream = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(inputStream);
                BufferedReader br = new BufferedReader(isr);

                OutputStream outputStream = socket.getOutputStream();
                //为了对方能够按行读,我们这里按行写
                PrintStream ps = new PrintStream(outputStream);
        ) {
            String line;
            while ((line = br.readLine()) != null) {
                //第四步:返回给客户端
                StringBuilder s = new StringBuilder(line);
                ps.println(s.reverse());
            }
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            //第五步:端开连接
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}
/*
TCP编程服务器端:
    同时接收多个客户端上传文件,上传后的文件保存在服务器的upload文件夹下。
    每次接收完一个客户端的文件之后,返回一句话“接收xx文件完毕”
 */
public class Server4 {
    public static void main(String[] args)throws Exception {
        //第一步:开启服务器
        ServerSocket server = new ServerSocket(8888);

        //第二步:接收客户端连接
        while(true){
            Socket socket = server.accept();

            //每一个客户端分配一个线程
            FileUploadThread thread = new FileUploadThread(socket);
            thread.start();
        }
    }
}
class FileUploadThread extends Thread{
    private Socket socket;

    public FileUploadThread(Socket socket) {
        this.socket = socket;
    }

    public void run(){
        FileOutputStream fos = null;
        //第三步:准备接收文件的名字(含后缀名),文件内容
        //每次接收完一个客户端的文件之后,返回一句话“接收xx文件完毕”
        try(
                InputStream inputStream = socket.getInputStream();//负责从网络(客户端)接收数据
                DataInputStream dis = new DataInputStream(inputStream);
                /*
                使用DataInputStream的目的,是因为这个IO流有readUTF()读字符串的方法,
                又有read(byte[] data)读字节内容方法
                 */
                OutputStream outputStream = socket.getOutputStream();//负责返回给客户端结果
                PrintStream ps = new PrintStream(outputStream);
                //PrintStream也是一种输出流,因为它有println方法,可以在输出xxx内容之后换行,即可以实现按行输出
                //至于它的内容输出到哪里,要看它的构造器如何指定
                //PrintStream ps = new PrintStream(outputStream);  ps.println(xx)的数据xx就输出到outputStream流
                //PrintStream ps2 = new PrintStream(new File("文件名"));  ps.println(xx)的数据xx就输出到文件中
                
                ){

            //(1)先接收文件名
            String fileName = dis.readUTF();
            //(2)避免文件的重名问题
            //可以使用A:随机数,B:时间戳,C:IP地址加时间戳,D:UUID等工具类生成唯一的一串数字 E:时间戳+原来的文件名
            //这里演示时间戳的方式
            long time = System.currentTimeMillis();
            String newFileName = time+fileName;
            //(3)准备输出文件内容的FileOutputStream流
            fos = new FileOutputStream("upload/" + newFileName);//负责把接收的文件内容,存储到服务的upload文件夹的某个文件中

            //(4)从网络中接收文件内容,并输出到服务的newFileName文件中
            byte[] data = new byte[1024];
            int len;
            while((len = dis.read(data)) != -1){
                fos.write(data,0,len);
            }

            //(5)返回结果
            ps.println(fileName + "文件接收完毕!");

        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                if(fos!=null){
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
//实现群聊,提醒上线、下线、掉线
public class Server5 {
    private static ArrayList<Socket> online = new ArrayList<>();

    public static void main(String[] args)throws Exception {
        //第一步:开启服务器
        ServerSocket server = new ServerSocket(8888);

        //第二步:接收客户端连接
        while(true){
            Socket socket = server.accept();

            //每连接一个客户端,需要把它添加online集合中
            online.add(socket);

            //每一个客户端分配一个线程
            MessageHandler thread = new MessageHandler(socket);
            thread.start();
        }
    }
    static class MessageHandler extends Thread{
        private Socket socket;//接收消息
        private String ip;

        public MessageHandler(Socket socket) {
            this.socket = socket;
            this.ip = socket.getInetAddress().getHostAddress();
        }

        public void run(){
            sendMessageToOthers(ip + "上线了");
            try(
                    InputStream inputStream = socket.getInputStream();
                    InputStreamReader isr = new InputStreamReader(inputStream);
                    BufferedReader br = new BufferedReader(isr);

                    ){

                String line;
                while((line = br.readLine()) != null){
                    if("bye".equalsIgnoreCase(line)){
                        PrintStream printStream = new PrintStream(socket.getOutputStream());
                        printStream.println("bye");//给自己返回bye,用于接收客户端的接收线程
                    }else {
                        sendMessageToOthers(ip + "说:" + line);
                    }
                }
            }catch (IOException e){
                e.printStackTrace();
                sendMessageToOthers(ip + "掉线了");
                //从online移除
                online.remove(socket);
            }finally{
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                sendMessageToOthers(ip + "下线了");
                //从online移除
                online.remove(socket);
            }
        }

        public void sendMessageToOthers(String content){
            //遍历online集合,可以拿的所有客户端的socket,可以给它们返回消息
            Iterator<Socket> iterator = online.iterator();
            while (iterator.hasNext()){
                Socket s = iterator.next();
                try {
                    if(!s.equals(socket)){//不要给自己发
                        PrintStream ps = new PrintStream(s.getOutputStream());
                        ps.println(content);
                        //这里不能关闭ps和s,因为这个客户端还要用呢
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    //s客户端发送消息失败,说明s客户端已经“掉线”等异常,
                    //应该从online中把它删除
                    iterator.remove();
                }
            }
        }
    }
}

客户端:
如果服务器端没有开,或者你写的IP地址和端口号错,会遇到
    java.net.ConnectException: Connection refused: connect  拒绝连接,连接失败


第一步:主动连接服务器,需要指定服务器端的IP地址和端口号

第二步:可以接收也可以发送
    这里演示发送一个消息
第三步:关闭

public class Client1 {
    public static void main(String[] args) throws Exception{
//        第一步:主动连接服务器,需要指定服务器端的IP地址和端口号
        Socket socket = new Socket("127.0.0.1",8888);

//        第二步:可以接收也可以发送
        //发送消息需要输出流
        OutputStream outputStream = socket.getOutputStream();
        String str = "hello";
        outputStream.write(str.getBytes());

//        第三步:关闭
        outputStream.close();
        socket.close();
    }
}
public class Client2 {
    public static void main(String[] args) throws Exception{
        //第一步:主动连接服务器,需要指定服务器端的IP地址和端口号
        Socket socket = new Socket("127.0.0.1",8888);

//        第二步:可以接收也可以发送
        //(1)发送消息需要输出流
        OutputStream outputStream = socket.getOutputStream();
        String str = "hello";
        outputStream.write(str.getBytes());
//        outputStream.close();//错误的
        socket.shutdownOutput();//半关闭,关闭输出通道,但是不关闭输入通道

        //(2)接收返回的消息
        System.out.println("服务器返回的消息是:");
        InputStream inputStream = socket.getInputStream();
        byte[] data = new byte[1024];
        int len;
        while((len = inputStream.read(data)) != -1){
            System.out.println(new String(data,0,len));
        }

//        第三步:关闭
        outputStream.close();
        inputStream.close();
        socket.close();
    }
}
TCP编程的客户端:
    可以从键盘输入单词/成语等,给服务器发过去,并接收服务器反转后的内容。
    输入一个发送一个,直到从键盘输入stop为止。
 */
public class Client3 {
    public static void main(String[] args) throws Exception{
        //第一步:连接服务器,需要指定服务器的IP地址和端口号
        Socket socket = new Socket("127.0.0.1",8888);

        OutputStream outputStream = socket.getOutputStream();
        //为了对方能够按行读,我们这里按行写
        PrintStream ps = new PrintStream(outputStream);

        //因为本例中使用的是纯文本消息,所以可以使用按行读的方式
        InputStream inputStream = socket.getInputStream();
        InputStreamReader isr = new InputStreamReader(inputStream);
        BufferedReader br = new BufferedReader(isr);

        //第二步:从键盘输入单词、成语等,直到输入stop为止
        Scanner input = new Scanner(System.in);
        while(true){
            System.out.print("输入单词或成语等:");
            String word = input.next();

            if("stop".equalsIgnoreCase(word)){
                break;
            }

            //第三步:给服务器发送过程
            ps.println(word);

            //第四步:接收服务器返回的消息
            System.out.println("服务器返回的消息是:" + br.readLine()) ;
        }

        //第五步关闭
        ps.close();
        outputStream.close();
        br.close();
        isr.close();
        inputStream.close();
        socket.close();
    }
}
/*
TCP编程客户端:
    从键盘输入要上传的文件的具体路径,上传文件,并接收服务器反馈的消息
 */
public class Client4 {
    public static void main(String[] args)throws Exception {
        //第一步:连接服务器
        Socket socket = new Socket("127.0.0.1", 8888);

        //第二步,从键盘输入要上传的文件的具体路径
        Scanner input = new Scanner(System.in);
        System.out.print("请输入要上传的文件的路径:");
        String filePath = input.nextLine();
        //D:\Download\img\美女\15.jpg
        File file = new File(filePath);

        //第三步:给服务器上传文件名,文件内容
        OutputStream outputStream = socket.getOutputStream();
        //为了区别文件名和文件内容,使用DataOutputStream
        DataOutputStream dos = new DataOutputStream(outputStream);

        //输出文件名
        dos.writeUTF(file.getName());

        //输出文件内容
        FileInputStream fis = new FileInputStream(filePath);
        byte[] data = new byte[1024];
        int len;
        while((len = fis.read(data)) != -1){
            dos.write(data,0,len);
        }
        //半关闭
        socket.shutdownOutput();

        //第四步:接收服务器反馈的消息
        InputStream inputStream = socket.getInputStream();
        InputStreamReader isr = new InputStreamReader(inputStream);
        BufferedReader br = new BufferedReader(isr);

        System.out.println(br.readLine());

        //第五步关闭
        br.close();
        isr.close();
        inputStream.close();
        fis.close();
        dos.close();
        outputStream.close();
        input.close();
        socket.close();

    }
}
//实现群聊,提醒上线、下线、掉线
public class Client5 {
    public static void main(String[] args) throws Exception{
        //第一步:连接服务器
        Socket socket = new Socket("127.0.0.1", 8888);

        SendThread send = new SendThread(socket);
        send.start();

        ReceiveThread receiveThread = new ReceiveThread(socket);
        receiveThread.start();

        send.join();
        receiveThread.setFlag(false);
        receiveThread.join();

        socket.close();//socket关闭时,所有的IO流自动关闭了
    }
}

class SendThread extends Thread{
    private Socket socket;

    public SendThread(Socket socket) {
        this.socket = socket;
    }

    public void run(){
        Scanner input = null;
        PrintStream ps = null;
        try {
             input = new Scanner(System.in);
            ps = new PrintStream(socket.getOutputStream());
            while(true){
                System.out.print("请输入要发送的消息内容:");
                String message = input.next();

                ps.println(message);

                if("bye".equalsIgnoreCase(message)){
                    break;
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
//            ps.close();//在这里不能关闭IO流
            input.close();
        }
    }
}
class ReceiveThread extends Thread{
    private Socket socket;
    private boolean flag = true;

    public ReceiveThread(Socket socket) {
        this.socket = socket;
    }

    public void run(){
        try(
                InputStream inputStream = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(inputStream);
                BufferedReader br = new BufferedReader(isr);
                ){

            while(flag){
                String line =br.readLine();
                System.out.println("接收的消息:" + line);
            }
        }catch(IOException e){
            e.printStackTrace();
        }
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值