java网络编程基础

JAVA网络编程


前言

本文讲的是一些基础的java网络编程用法,如有不足请指出。

1.网络通信

网络通信需要解决的问题

1.如何定位到网络上的一台主机或多台主机

2.定位到目标主机之后如何与其通信

网络编程中的要素

1.IP

2.端口号

网络通信协议

常见的网络协议
IP(Internetworking Protocol)网间网协议

IP协议是是网络层的主要协议,支持网间互联的数据报通信,它是无连接、不可靠的协议。IP 网络上的主机只能直接向本地网上的其他主

机发送数据包

TCP(Transport Control Protocol)传输控制协议

TCP协议是面向连接(需要在客户端和服务器之间建立连接)、保证高可靠性(数据无丢失、数据无失序、数据无错误、数据无重复到达,安全性可以得到保障)传输层协议。

*TCP全称传输控制协议,必须对数据的传输进行控制。
1.TCP连接过程:

第一次握手

客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态。

第二次握手

服务端收到连接请求报文段后,如果同意连接,则会发送一个应答,该应答中也会包含自身的数据通讯初始序号,发送完成后便进入 SYN-RECEIVED 状态。

第三次握手

当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。

*为什么要进行三次握手:防止出现失效的连接请求报文段被服务端接收的情况,从而产生错误。

2.TCP断开连接过程:

第一次挥手

若客户端 A 认为数据发送完成,则它需要向服务端 B 发送连接释放请求。

第二次挥手

B 收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明 A 到 B 的连接已经释放,不再接收 A 发的数据了。但是因为 TCP 连接是双向的,所以 B 仍旧可以发送数据给 A。

第三次挥手

B 如果此时还有没发完的数据会继续发送,完毕后会向 A 发送连接释放请求,然后 B 便进入 LAST-ACK 状态。

第四次挥手

A 收到释放请求后,向 B 发送确认应答,此时 A 进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有 B 的重发请求的话,就进入 CLOSED 状态。当 B 收到确认应答后,也便进入 CLOSED 状态。

3.TCP协议的特点

面向连接

面向连接,是指发送数据之前必须在两端建立连接。建立连接的方法是“三次握手”,这样能建立可靠的连接。建立连接,是为数据的可靠传输打下了基础。

仅支持单播传输

每条TCP传输连接只能有两个端点,只能进行点对点的数据传输,不支持多播和广播传输方式。

面向字节流

TCP不像UDP一样那样一个个报文独立地传输,而是在不保留报文边界的情况下以字节流方式进行传输。

可靠传输

对于可靠传输,判断丢包,误码靠的是TCP的段编号以及确认号。TCP为了保证报文传输的可靠,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据(假设丢失了)将会被重传。

提供拥塞控制

当网络出现拥塞的时候,TCP能够减小向网络注入数据的速率和数量,缓解拥塞

TCP提供全双工通信

TCP允许通信双方的应用程序在任何时候都能发送数据,因为TCP连接的两端都设有缓存,用来临时存放双向通信的数据。当然,TCP可以立即发送一个数据段,也可以缓存一段时间以便一次发送更多的数据段(最大的数据段大小取决于MSS)

UDP(User Datagram Protocol)用户数据报协议
1.无连接:

只知道对端的IP和端口号就可以发送,不需要实现建立连接。

2.不可靠:

没有确认机制, 没有重传机制。如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息。

3.面向数据报:

应用层交给UDP多长的报文, UDP原样发送既不会拆分,也不会合并。

4.UDP的缓冲区:

UDP存在接收缓冲区,但不存在发送缓冲区。

5.UDP是一种全双工通信协议。

UDP协议首部中有一个16位的大长度. 也就是说一个UDP能传输的报文长度是64K(包含UDP首部)。如果我们需要传输的数据超过64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装。

注:还有很多协议类型,目前写的网络编程只在传输层上做文章所以只列出这几种

网络模型

OSI七层网络模型对应部分网络协议
应用层HTTP,TFTP,FTP,NFS等
表示层Telnet,Rlogin等
会话层SMTP,DNS等
传输层TCP,UDP等
网络层IP,ICPM,ARP,RARP等
数据链路层FDDI,Ethernet等
物理层IEEE 802.1A,IEEE 802.2到IEEE 802.11
TCP/IP四层概念模型对应部分网络协议
应用层对应上图的应用层到会话层
传输层对应上图的传输层
网络层对应上图的网络层
数据链路层对应上图的数据链路层和物理层

IP(InetAddress)

可用windows系统可在cmd中用ipconfig查看

组成

IP地址由四段组成,每个字段是一个字节,8位,最大值是255,

IP地址由两部分组成,即网络地址主机地址。网络地址表示其属于互联网的哪一个网络,主机地址表示其属于该网络中的哪一台主机。

二者是主从关系。

作用

IP地址的四大类型标识的是网络中的某台主机。IPv4的地址长度为32位,共4个字节,但实际中我们用点分十进制记法。

本地主机:127.0.0.1(localhost)

ip地址的分类
1.网络号和主机号

分为A、B、C三类及特殊地址D、E。 全0和全1的都保留不用。

A类:(1.0.0.0-126.0.0.0)(默认子网掩码:255.0.0.0或 0xFF000000)第一个字节为网络号,后三个字节为主机号。该类IP地址的最前面

为“0”,所以地址的网络号取值于1~126之间。一般用于大型网络。

B类:(128.0.0.0-191.255.0.0)(默认子网掩码:255.255.0.0或0xFFFF0000)前两个字节为网络号,后两个字节为主机号。该类IP地址的

最前面为“10”,所以地址的网络号取值于128~191之间。一般用于中等规模网络。

C类:(192.0.0.0-223.255.255.0)(子网掩码:255.255.255.0或 0xFFFFFF00)前三个字节为网络号,最后一个字节为主机号。该类IP地

址的最前面为“110”,所以地址的网络号取值于192~223之间。一般用于小型网络。

D类:是多播地址。该类IP地址的最前面为“1110”,所以地址的网络号取值于224~239之间。一般用于多路广播用户[1] 。

E类:是保留地址。该类IP地址的最前面为“1111”,所以地址的网络号取值于240~255之间。

在IP地址3种主要类型里,各保留了3个区域作为私有地址,其地址范围如下:

A类地址:10.0.0.0~10.255.255.255

B类地址:172.16.0.0~172.31.255.255

C类地址:192.168.0.0~192.168.255.255

2.ipv4/ipv6

ipv4:IPv4协议具有32位(4字节)地址长度,Pv4地址是以小数表示的二进制数

ipv6:IPv6协议具有128位(16字节)地址长度,IPv6地址是以十六进制表示的二进制数。

3.公网-私网

公网(互联网):公有地址(Public address):由Inter NIC(因特网信息中心)负责。这些IP 地址分配给注册并向Inter NIc提出申请的组织机构,公有IP 全球唯一,通过它直接访问因特网(直接能上网)。

私网(局域网):私有地址(Private address):属于非注册地址,专门为组织机构内部使用。

域名

方便记忆ip

java中获取ip地址
package net;

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

public class study {
    public static void main(String[] args) throws UnknownHostException {
        //查询目标网页ip
        InetAddress byName = InetAddress.getByName("www.baidu.com");//www.baidu.com/36.152.44.95
        System.out.println(byName);
        //查询本机ip
        System.out.println(InetAddress.getLocalHost());//DESKTOP-T0E95V5/192.168.153.1
        System.out.println(InetAddress.getByName("localhost"));//localhost/127.0.0.1
        System.out.println(InetAddress.getByName("127.0.0.1"));///127.0.0.1
        //常用方法
        System.out.println(byName.getAddress());//[B@1b6d3586
        System.out.println(byName.getHostAddress());//36.152.44.96
        System.out.println(byName.getCanonicalHostName());//36.152.44.96
        System.out.println(byName.getHostName());//www.baidu.com
    }
}

端口

端口表示计算机上一个程序的进程

在网络技术中,端口(Port)大致有两种意思:一是物理意义上的端口,比如,ADSL Modem、集线器、交换机、路由器用于连接其他网络设备的接口,如RJ-45端口、SC端口等等。二是逻辑意义上的端口,一般是指TCP/IP协议中的端口,端口号的范围从0到65535,比如用于浏览网页服务的80端口,用于FTP服务的21端口等等。

我们这里将要介绍的就是逻辑意义上的端口。我们这里所说的端口,不是计算机硬件的I/O端口,而是软件形式上的概念.工具提供服务类型的不同,端口分为两种,一种是TCP端口,一种是UDP端口。计算机之间相互通信的时候,分为两种方式:一种是发送信息以后,可以确认信息是否到达,也就是有应答的方式,这种方式大多采用TCP协议;一种是发送以后就不管了,不去确认信息是否到达,这种方式大多采用UDP协议。对应这两种协议的服务提供的端口,也就分为TCP端口和UDP端口。

windows环境下可以在cmd中输入

netstat -an
netstat -ano|findstr "特定端口" #查看指定端口
tasklist|findstr "特定端口" #查看指定端口进程
Ctrl + Shift + Esc #任务管理器

查看端口状态

如果输入:netstat -an,提示:不是内部或外部命令,也不是可运行的程序或批处理文件。 提示不是内部或外部命令的原因是:cmd当前操作不在系统文件夹system32下,那么只需输入:cd c:/WINDOWS/system32 ;即可将当前操作路径切换到Windows 操作系统的系统文件夹下。 然后再输入netstat -an,即可解决。

网络端口的分类
(1)公认端口(Well Known Ports)

0~1023,它们紧密绑定于一些服务,通常这些端口的通讯明确表明了某种服务的协议,如:80端口对应与HTTP通信,21端口绑定

与FTP服务,25端口绑定于SMTP服务,135端口绑定与RPC(远程过程调用)服务。

(2)注册端口(Registered Ports)

1024~49151,它们松散的绑定于一些服务,也就是说有许多服务绑定于这些端口,这些端口同样用于其他许多目的,如:许多系统

处理端口从1024开始

(3)动态和/或私有端口(Dyanmic and /or Private Ports)

49152~65535,理论上,不应为服务分配这些端口,通常机器从1024开始分配动态端口。例外:SUN的RPC端口从32768开始。

java代码

package net;

import java.net.InetSocketAddress;

public class port {
    public static void main(String[] args) {
        InetSocketAddress inetSocketAddress2 = new InetSocketAddress("localhost", 8080);
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8080);
        System.out.println(inetSocketAddress);///127.0.0.1:8080
        System.out.println(inetSocketAddress2);//localhost/127.0.0.1:8080
        System.out.println(inetSocketAddress.getAddress());///127.0.0.1
        System.out.println(inetSocketAddress.getPort());//8080
        System.out.println(inetSocketAddress.getHostName());//127.0.0.1
    }

}

TCP编程

基础过程
1.客户端

1.通过socket连接服务器

2.发送消息

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;

public class client {
    public static void main(String[] args) throws IOException {
        OutputStream os =null;
        ByteArrayOutputStream byteArrayOutputStream =null;
        try {
            InetAddress serverip = InetAddress.getByName("127.0.0.1");
            int port = 9999;
            //创建一个socket连接
            Socket socket = new Socket(serverip,port);
            //发送消息,用I/O流
            os = socket.getOutputStream();
            os.write("hello 你好".getBytes(StandardCharsets.UTF_8));
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (os!=null)os.close();

        }
    }
}

2.服务端

1.建立服务端口 ServerSocket

2.等待用户连接,accpet

3.接收用户的消息

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        //提升作用域
        ServerSocket serverSocket=null;
        Socket socket =null;
        InputStream is =null;
        ByteArrayOutputStream byteArrayOutputStream =null;
        try {
            serverSocket = new ServerSocket(9999);
            //等待客户端连接
            socket = serverSocket.accept();
            //读取消息
            is = socket.getInputStream();
            /*容易乱码
            byte[] buffer = new byte[1024];
            int len;
            while((len=is.read(buffer))!=-1){
                String msg = new String(buffer,0,len);
                System.out.println(msg);
            }
             */
            byteArrayOutputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while((len=is.read(buffer))!=-1){
                byteArrayOutputStream.write(buffer,0,len);
            }
            System.out.println(byteArrayOutputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //关闭资源
            if(byteArrayOutputStream!=null) byteArrayOutputStream.close();
            if(is!=null) is.close();
            if(socket!=null) socket.close();
            if(serverSocket!=null) serverSocket.close();
        }
    }
}
实现文件上传
1.客户端
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

public class Fclient {
    public static void main(String[] args) throws IOException {
        //1.创建socket连接
        Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9999);
        //2.创建输出流
        OutputStream os = socket.getOutputStream();
        //3.读取文件
        FileInputStream fie = new FileInputStream(new File("E:\\javasestudy\\src\\example.png"));
        //4.写入文件
        byte[] buffer = new byte[1024];
        int len;
        while((len=fie.read(buffer))!=-1){
            os.write(buffer,0,len);
        }

        //5.通知服务器我已经传输完了
        socket.shutdownOutput();

        //6.确定服务端接收完毕了,才断开连接
        InputStream is = socket.getInputStream();
        //String
        ByteArrayOutputStream to = new ByteArrayOutputStream();
        byte[] buffer2 = new byte[2048];
        int len2;
        while((len2= is.read(buffer2))!=-1){
            to.write(buffer2,0,len2);
        }
        System.out.println(to);
        //关闭资源
        to.close();
        is.close();;
        fie.close();
        socket.close();
    }
}
2.服务端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class Fserver {
    public static void main(String[] args) throws IOException {
        //1.创建服务
        ServerSocket serverSocket = new ServerSocket(9999);
        //2.监听客户端连接
        Socket socket = serverSocket.accept();
        //3.获取输入流
        InputStream is = socket.getInputStream();
        //4.文件输出
        FileOutputStream fos = new FileOutputStream(new File("recive.png"));
        byte[] buffer=new byte[1024];
        int len;
        while((len= is.read(buffer))!=-1){
            fos.write(buffer,0,len);
        }
        //5.通知服务端我接收完毕了
        OutputStream res = socket.getOutputStream();
        res.write("接收完毕,可以断开".getBytes(StandardCharsets.UTF_8));
        //6.结束关闭
        fos.close();
        is.close();
        socket.close();
        serverSocket.close();
    }
}

Tomcat

概念:

Tomcat 服务器是一个开源的轻量级Web应用服务器,在中小型系统和并发量小的场合下被普遍使用,是开发和调试Servlet、JSP 程序的首选。

网站参考

Apache Tomcat® - 欢迎!

转换

原来的基础上,我们的客户端和服务端都是自己手动书写的

客户端C,服务端S

现在我们可以将客户端与服务端替代

客户端(浏览器)B,服务端(Tomcat)S

环境部署

环境布置完后后续可以使用进行测试开发。

UDP编程

单向发送接收
客户端
import java.net.*;
import java.nio.charset.StandardCharsets;

public class Uclient {
    public static void main(String[] args) throws IOException {
        //建立一个socket
        DatagramSocket socket = new DatagramSocket();

        //建立包
        String msg = "你好";
        InetAddress localhost = InetAddress.getByName("localhost");
        int port = 9999;
        DatagramPacket packet = new DatagramPacket(msg.getBytes(StandardCharsets.UTF_8), 0, msg.getBytes(StandardCharsets.UTF_8).length, localhost, port);

        //发送包
        socket.send(packet);

        //关闭流
        socket.close();
    }
}
服务器
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Arrays;

//等待客户端发送
public class Uservice {
    public static void main(String[] args) throws IOException {
        //开放端口
        DatagramSocket socket = new DatagramSocket(9999);

        //接收数据包
        byte[] buffer = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);//接收
        socket.receive(packet);//阻塞接收
        System.out.println(new String(packet.getData(),0, packet.getLength()));

        //关闭连接
        socket.close();
    }

}
连续发送接收
发送方
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;

public class Sender {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket(8888);
        //准备数据
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        while(true){
            String data = reader.readLine();
            byte[] datas = data.getBytes();
            DatagramPacket packet = new DatagramPacket(datas,0,datas.length,new InetSocketAddress("localhost",9999));
            socket.send(packet);
            if (data.equals("bye".trim())){
                break;
            }
        }
        socket.close();
    }
}
接收方
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class Recive {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket(9999);
        while(true){
            //准备接收包
            byte[] container = new byte[1024];
            DatagramPacket packet = new DatagramPacket(container,0,container.length);
            socket.receive(packet);
            //断开连接
            byte[] data = packet.getData();
            String reciveData = new String(data,0,packet.getLength());

            System.out.println(reciveData);

            if(reciveData.equals("bye".trim())){
                break;
            }
        }
        socket.close();
    }
}
模拟聊天
线程一(控制接收)
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class Talkrecive implements Runnable{
    DatagramSocket socket=null;
    private int port;
    private String fromstr;
    public Talkrecive(int port,String fromstr) throws SocketException {
        this.fromstr=fromstr;
        this.port=port;
        socket = new DatagramSocket(port);
    }
    @Override
    public void run() {
        while(true){
            //准备接收包
            try {
                byte[] container = new byte[1024];
                DatagramPacket packet = new DatagramPacket(container,0,container.length);
                socket.receive(packet);
                //断开连接
                byte[] data = packet.getData();
                String reciveData = new String(data,0,packet.getLength());

                System.out.println(fromstr+":"+reciveData);

                if(reciveData.equals("bye".trim())){
                    break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        socket.close();
    }
}

线程二(控制发送)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;

public class Talksend implements Runnable{
    DatagramSocket socket=null;
    BufferedReader reader=null;

    private int fromport;
    private String toip;
    private int toport;

    public Talksend(int fromport,String toip,int toport) throws SocketException {
        this.fromport=fromport;
        this.toip=toip;
        this.toport=toport;

        socket = new DatagramSocket(fromport);
        reader = new BufferedReader(new InputStreamReader(System.in));
    }
    @Override
    public void run() {
        while(true){
            try {
                String data = null;
                data = reader.readLine();
                byte[] datas = data.getBytes();
                DatagramPacket packet = new DatagramPacket(datas,0,datas.length,new InetSocketAddress(this.toip,this.toport));
                socket.send(packet);
                if (data.equals("bye".trim())){
                    break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
对象1
import java.net.SocketException;

public class Talk1 {
    public static void main(String[] args) throws SocketException {
        //开启线程
        new Thread(new Talksend(7777,"Localhost",9999)).start();
        new Thread(new Talkrecive(8888,"one")).start();
    }
}

对象2
import java.net.SocketException;

public class Talk2 {
    public static void main(String[] args) throws SocketException {
        //开启线程
        new Thread(new Talksend(5555,"localhost",8888)).start();
        new Thread(new Talkrecive(9999,"two")).start();
    }
}

最后目标图式如下

在这里插入图片描述

URL

概念

统一资源定位符:定位资源所用(Uniform Resource Locator)

组成

URL由三部分组成:资源类型、存放资源的主机域名、资源文件名。

URL的一般语法格式为:

protocol :// hostname[:port] / path / [;parameters][?query]#fragment

protocol:协议

hostname:IP地址

port:端口

path:资源路径

parameters:参数

query:查询

可选,用于给动态网页(如使用CGI、ISAPI、PHP/JSP/ASP/ASP。NET等技术制作的网页)传递参数,可有多个参数,用“&”符号隔开,每个参数的名和值用“=”符号隔开。

fragment:信息片断

信息片断,字符串,用于指定网络资源中的片断。例如一个网页中有多个名词解释,可使用fragment直接定位到某一名词解释。

Windows主机不区分域名大小写,Linux和Unix区分。

import java.net.MalformedURLException;
import java.net.URL;

public class url {

    public static void main(String[] args) throws MalformedURLException {
        URL url = new URL("http://localhost:8080/helloworld/index.jsp?username=bluestone&passworld=123");
        System.out.println(url.getProtocol());//http
        System.out.println(url.getAuthority());//localhost:8080
        System.out.println(url.getPort());//8080
        System.out.println(url.getPath());///helloworld/index.jsp
        System.out.println(url.getFile());///helloworld/index.jsp?username=bluestone&passworld=123
        System.out.println(url.getQuery());//username=bluestone&passworld=123
    }
}
下载实现(本地)

1.首先创建一个待下载文件。hello.txt

在这里插入图片描述

2.将待下载的文件拖如tomcat的网页资源中。

在这里插入图片描述

3.启动tomcat

4.编写代码

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class url {

    public static void main(String[] args) throws IOException {
        //下载地址
        URL url = new URL("http://localhost:8080/hello.txt");
        //连接这个资源
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        InputStream inputStream = urlConnection.getInputStream();
        FileOutputStream fos = new FileOutputStream("finish.txt");
        byte[] buffer = new byte[1024];
        int len;
        while ((len=inputStream.read(buffer))!=-1){
            fos.write(buffer,0,len);
        }
        fos.close();
        inputStream.close();
        urlConnection.disconnect();//断开连接
    }
}

5.成功下载网页资源

在这里插入图片描述

下载实现(联网)

edge中键入F12打开开发者工具平台,筛选出你想要下载的页面资源,复制他的url地址,再代码中替换,即可下载。

在这里插入图片描述

总结

讲述了一些位于网络层和传输层的基本协议在java中如何编程使用,涉及一些计算机网络的基础知识,希望对您有帮助。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值