Java基础——网络编程

一.网路编程知识

意义:过去我们IO流是把本地的文件传递而已,网络编程是把自己电脑的数据传递给另一台电脑,并且回馈信息。这是数据通讯

1.通讯三要素

1.1 IP地址

(1)获取对方主机IP

1.2 端口号

(1)数据发送到对方主机指定的应用程序上,为了区别主机上的应用程序,会定义一些数字标识(端口)来区分每一个应用程序。这个端口也被称为逻辑端口(只有相同的应用程序才能解析相应的数据,所以要发送给指定的应用程序)每一个网络程序都有一个端口号

1.3 传输协议

(1)定义通讯规则,这种规则被称为通信协议。两台主句必须按照同种协议才可以实现通信,协议不相同则不能通信。国际组织定义了通用协议TCP/IP

游戏通信举例:

<1>.魔兽世界:

a.为什么全世界的玩家都能通信?因为都全世界都在用通用的TCP/IP协议

b.为什么你能看见其他玩家?首先你向暴雪服务器发送信息,然后期服务器反馈给你信息,你解析即可得到其他玩家的位置。

c.为什么你能向好友发送信息?你们加好友就是获取了对方的IP地址,然后发送信息就是普通的数据通信原理

 

<2>.CS

CS局域网连接如何实现?获取对方IP,获取对方CS客户端端口,都有通用协议TCP/IP。如果几个人用自己的协议,如IPX/NetBIOS,那么其他使用TCP/IP协议的人是无法连如这个局域网的

2.重点知识

(1)子网掩码:把一块区域划分到一块相当于局域网当中,这片区域都会用一个外网IP地址。子网掩码是为了多人共用一个地址而出现的。

(2)IPV6:定义了足够多的IP地址,其中不但有数字而且加入了字母

(3)端口号:0~65575端口可任选,不过0~1024端口被系统所用或保留端口。应用程序端口后未设定,系统随机分配。web服务:80;Tomcat:8080;mysql:3306

(4)本地回环地址:127.0.0.1   主机名:localhost,网络主机名www.baidu.com,前边是万维网标识,baidu自定义主机名,com主机所属区域

(5)TCP——Transmission Control Protocol    UDP——User Datagram Protocol

(6)192.168.1.0不能用,这个地址代表一个网络地址,代表一个网络段;192.168.1.255代表广播地址,能够给该网络内所有存活主机发广播

3.参考模型

3.1 OSI参考模型( 7层)


3.2 TCP/IP参考模型( 4层)


(1)应用层:主要对应的——>JavaWeb(HTTP协议——Hypertext transfer protocol)

(2)传输层:(TCP/UDP)——>Java网络编程

(3)网际层:(IP)——>Java网络编程

(4)主机至网络层:计算机网络大学所学

二.Java网络编程

1.InetAddress类

注1:IP地址对象类

方法摘要
tatic InetAddressgetLocalHost() 
          返回本地主机。throw UnknownHostException()
static InetAddressgetByName(String host) 
          在给定主机名的情况下确定主机的 IP 地址。throw UnknownHostException()
static InetAddress[]getAllByName(String host) 
          在给定主机名的情况下,根据系统上配置的名称服务返回其 IP 地址所组成的数组。throw UnknownHostException()
 StringgetHostAddress() 
          返回 IP 地址字符串(以文本表现形式)。
 StringgetHostName() 
          获取此 IP 地址的主机名。

2.Socket

(1)科普:最早赛扬系列CPU,CPU要插在主板相应的插槽上,最早的插槽为Socket 370,这里370指的是CPU的帧角数,370的插槽能插370帧角的CPU

(2)这里的Socket是指软件的Socket,每个软件都是一个插槽(Socket)准备接受对方传过来的数据。所以想要通信,必须先要有插槽(Socket)

(3)通常所说网络通信及Socket通信,通信的两端必须都有Socket,数据通过IO流传输

3.传输协议

3.1 UDP协议

(1)特点:面向无连接无连接——网络通信有两端,你不用在意另外一端在与否,都向它发送数据包,没找到的话包则丢掉;通讯无需建立连接所以速度快,是不可靠协议。将数据及源和目的封装成数据包,数据大小限制在64K以下

(2)对应应用:QQ聊天、视频会议(凌波视频)

(3)原理:远程视频就算数据丢了也无所谓。可像下载就不建议使用这种协议,因为存在本地的数据必须保证数据的完整性

(4)模型:步话机(通过调节至共同频段来实现通话,对方没开的话你发数据对方无法收到)

3.1.1 DatagramSocket类

意义:用来发送和接收数据包的套接字

构造方法摘要
DatagramSocket() 
          构造数据报套接字并将其绑定到本地主机上任何可用的端口。
DatagramSocket(int port) 
          创建数据报套接字并将其绑定到本地主机上的指定端口。


方法摘要
 voidreceive(DatagramPacket p) 
          从此套接字接收数据报包。(阻塞式方法)
 voidsend(DatagramPacket p) 
          从此套接字发送数据报包。
 voidclose() 
          关闭此数据报套接字。

3.1.2 DatagramPacket类

意义:因为数据包是一个复杂事物,包括源端口,源地址,目的端口,目的地址,所有封装为对象

构造方法摘要
DatagramPacket(byte[] buf, int length) 
          构造 DatagramPacket,用来接收长度为 length 的数据包。(定义要接受的数据包)
DatagramPacket(byte[] buf, int length, InetAddress address, int port) 
          构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。(定义要发送的数据包)


方法摘要
 InetAddressgetAddress() 
          返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。
 byte[]getData() 
          返回数据缓冲区。
 intgetLength() 
          返回将要发送或接收到的数据的长度。
 intgetPort() 
          返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。

3.1.3 使用方法( 代码)
/*
需求:练习用Socket实现两台计算点间的的数据发送与接收
步骤:1.通过DatagramPacket来为数据打包
	  2.通过DatagramSocket来发送数据包
	  3.通过DatagramSocket来接收DatagramPacket数据包

*/

import java.net.*;

class SocketSendPractice
{
	public static void main(String[] args)throws Exception{
		//建立UDP的Socket的服务
		DatagramSocket ds = new DatagramSocket();
		
		//建立数据缓冲区
		byte[] buf = "聊天程序的第一步".getBytes();

		//获取本地主机的IP对象
		InetAddress ia = InetAddress.getLocalHost();

		//建立需要发送的数据包
		DatagramPacket dp = new DatagramPacket(buf,buf.length,ia,10000);
		
		//发送数据
		ds.send(dp);

		//关闭资源
		ds.close();
	}
}

class SocketReceivePractice
{
	public static void main(String[] args)throws Exception{
		//建立Socket服务
		DatagramSocket ds = new DatagramSocket(10000);

		//建立接收数据的缓冲区
		byte[] buf = new byte[1024];

		//建立接收数据的数据包
		DatagramPacket dp = new DatagramPacket(buf,buf.length);

		//接收数据
		ds.receive(dp);

		System.out.println(new String(dp.getData(),0,dp.getLength()));

		ds.close();


	}
}
3.1.4 键盘录入(UDP 代码)
import java.net.*;
import java.io.*;

/**
*键盘输出数据发送到指定PC,使用UDP传输
*/
class UdpSend
{
	public static void main(String[] args)throws Exception{//代码观看方便未处理异常
		//建立发送断Socket
		DatagramSocket ds = new DatagramSocket(8888);

		//获取键盘录入
		//BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
		BufferedInputStream bufis = new BufferedInputStream(System.in);
		
		//建立对方IP地址对象
		InetAddress ip = InetAddress.getLocalHost();

		//String line = "";
		int line;
		byte[] buf = new byte[1024];

		while((line=bufis.read(buf))!=-1){
			//buf = line.getBytes();
			//封装数据到包中
			DatagramPacket dp = new DatagramPacket(buf,line,ip,10000);
			ds.send(dp);
		}
		
		bufis.close();
		ds.close();
	}
}

class UdpReceive
{
	public static void main(String[] args)throws Exception{
		//建立发送断Socket
		DatagramSocket ds = new DatagramSocket(10000);

		//建立接收数据的包
		byte[] buf = new byte[1024];
		DatagramPacket dp = new DatagramPacket(buf,buf.length);

		while(true){
			ds.receive(dp);
			System.out.print(dp.getAddress().getHostName()+":"+dp.getPort()+"......"+new String(dp.getData(),0,dp.getLength()));
		}
		//ds.close();
	}
}
3.1.5 仿QQ的聊天GUI界面实现( 代码)
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.text.*;
import java.util.*;

/**
*实现主机间的聊天并且用GUI实现窗口
*1.ChattingFrame类是界面实现类,Send是发送数据线程类,Receive是接收数据线程类
*2.界面类构建时就对开启接受线程,实现实时接收数据的功能
*3.没输入一条数据都会建立一个线程发送对象来发送数据包,并且分别向本机与对方机发送数据包
*/

class NewChattingFrameTest	
{
	public static void main(String[] args)throws Exception{
		DatagramSocket dsSend = new DatagramSocket();
		DatagramSocket dsReceive = new DatagramSocket(10000);

		new ChattingFrame(dsSend,dsReceive);
	}
}

class ChattingFrame
{
	private Frame f;
	private TextArea taSend,taReceive;
	private Panel pSend,pReceive;
	private Button b;
	private DatagramSocket dsSend,dsReceive;

	//这个窗体是为了传送接收信息的,构造时需要发送和接收数据的包
	ChattingFrame(DatagramSocket dsSend,DatagramSocket dsReceive){
		this.dsSend = dsSend;
		this.dsReceive = dsReceive;
		init();

		Receive r = new Receive(dsReceive,taReceive);
		new Thread(r).start();
	}

	public void init(){
		f = new Frame("聊天程序");
		
		f.setBounds(400,200,500,420);
		f.setLayout(new BorderLayout(10,10));

		//建立上下主面板
		pSend = new Panel();
		pSend.setPreferredSize(new Dimension(300,100));
		pReceive = new Panel();
		pReceive.setPreferredSize(new Dimension(300,300));
		
		//布局下边的发送面板内的内容
		pSend.setLayout(new BorderLayout(10,10));
		taSend = new TextArea();
		taSend.setPreferredSize(new Dimension(250,60));
		b = new Button("发送");
		b.setPreferredSize(new Dimension(30,20));
		pSend.add(taSend,BorderLayout.CENTER);
		pSend.add(b,BorderLayout.EAST);

		//布局上边接收面板的内容
		pReceive.setLayout(new BorderLayout());
		taReceive = new TextArea();
		taReceive.setEditable(false); 
		pReceive.add(taReceive);

		//把主面板添加进窗体内
		f.add(pSend,BorderLayout.SOUTH);
		f.add(pReceive,BorderLayout.CENTER);
		
		myEvent();
		f.setVisible(true);
	}

	public void myEvent(){
		f.addWindowListener(new WindowAdapter(){
			public void windowClosing(WindowEvent e){
				System.exit(0);
			}
		});

		//添加按钮监听,点击按钮发送信息
		b.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){
				//建立发送线程并发送数据
				new Thread(new Send(dsSend,taSend.getText())).start();
				//清空文本区域
				taSend.setText("");
			}
		});
		
		//添加键盘监听,点击回车发送数据
		taSend.addKeyListener(new KeyAdapter(){
			public void keyPressed(KeyEvent e){
				if(e.getKeyCode()==KeyEvent.VK_ENTER && !e.isControlDown()){
					e.consume();
					new Thread(new Send(dsSend,taSend.getText())).start();
					taSend.setText("");
				}
			}
		});
	}
}

class Send implements Runnable
{
	private DatagramSocket ds;
	private String taSend;
	
	//构造时需要接收要发送的数据和发送数据的包
	Send(DatagramSocket ds,String taSend){
		this.ds = ds;
		this.taSend = taSend;
	}

	public void run(){
		try
		{
			if(taSend==null)
				return;
			byte[] buf = taSend.getBytes();
			
			if(buf.length!=0){
				DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("10.23.12.86"),10000);//发送给指定IP
				DatagramPacket owndp = new DatagramPacket(buf,buf.length,InetAddress.getLocalHost(),10000);//发送给本机

				ds.send(dp);
				ds.send(owndp);
			}
			else
				return;
		}
		catch (UnknownHostException e)
		{
			throw new RuntimeException("未知主机");
		}
		catch(Exception e)
		{
			throw new RuntimeException("IO异常");
		}
	}
}

class Receive implements Runnable
{
	private DatagramSocket ds;
	private TextArea taReceive;
	
	//构造时需要接收数据的包和输出接收数据的窗体组件TextArea
	Receive(DatagramSocket ds,TextArea taReceive){
		this.ds = ds;
		this.taReceive = taReceive;
	}

	public void run(){
		//为实现边输入边接收数据的功能,接收程序必须一直执行,不能停止,所以定义无限循环
		while(true){
			try
			{
				byte[] buf = new byte[1024];
				
				DatagramPacket dp = new DatagramPacket(buf,buf.length);

				ds.receive(dp);

				int length = dp.getLength();
				//接收的数据以字符串形式存储
				String content = new String(dp.getData(),0,length);
				//获取现在的时间以字符串形式存储
				SimpleDateFormat sdf = new SimpleDateFormat("H:m:s");
				String date = sdf.format(new Date());
				//排列出要显示的信息样式保存在一个字符串当中
				String textReceive = dp.getAddress().getHostName()+" "+date+" \r\n"+content+"\r\n\r\n";
				//把定义好的字符串添加进taRecieve组件
				taReceive.append(textReceive);
			}
			catch (Exception e)
			{
				throw new RuntimeException("接收出现错误");
			}
		}
	}
}

3.2 TCP协议

(1)特点:面向连接,面向连接——只有对方在才发送数据,否则不发送;通讯需建立连接。可实现大数据的传输,通过三次握手完成连接,是可靠协议。必须建立连接,速率稍低。三次握手——老师问你在吗?1次;我回答我在。2次;老师说我知道你在了。3次。通过这三次握手可完成TCP通道的建立

(2)模型:电话(建立通话连接,通话数据在通道中来回传输)

3.2.1 Socket类

意义:实现了客户端的套接字。建立对象时,通路一建立就会有一个Socket流,其中既有输入流也有输出流,可以用她们对数据进行操作

构造方法摘要
Socket() 
          通过系统默认类型的 SocketImpl 创建未连接套接字
Socket(InetAddress address, int port) 
          创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
Socket(String host, int port) 
          创建一个流套接字并将其连接到指定主机上的指定端口号。

方法摘要
 voidconnect(SocketAddress endpoint) 
          将此套接字连接到服务器。
 InetAddressgetInetAddress() 
          返回套接字连接的地址。
 intgetPort() 
          返回此套接字连接到的远程端口。
 InputStreamgetInputStream() 
          返回此套接字的输入流。
 OutputStreamgetOutputStream() 
          返回此套接字的输出流。
 voidshutdownInput() 
          此套接字的输入流置于“流的末尾”。
 voidshutdownOutput() 
          禁用此套接字的输出流。
 voidclose() 
          关闭此套接字。

3.2.2 ServerSocket类

意义:服务端会获取每一个客户端Socket的对象,并且通过这个对象的输入、输出流来向相应客户端发送数据。获取客户端对象通过accept()方法完成

构造方法摘要
ServerSocket(int port) 
          创建绑定到特定端口的服务器套接字。
ServerSocket(int port, int backlog) 
          利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。(指的是连接到服务器中的客户端的最大个数)


方法摘要
 Socketaccept() 
          侦听并接受到此套接字的连接。(阻塞式方法)
 voidclose() 
          关闭此套接字。(可选操作,服务器一般不用关闭)

3.2.3 代码练习(键盘录入)
注1:如果使用缓冲写入流写入数据,注意刷新并且刷新行。
注2:Socket流服务端与客户端都暂停,主要考虑网络读取流在客户端与服务端的read方法,都是阻塞式方法
3.2.4 代码练习 (上传文件)
注1:两边都在等的情况下,可定义时间戳作为标记,就是使用System.currentTimeMills(),输入流在最初传入这个时间到服务器,服务器可以接受这个数据,当再次收到这个数据时既可跳出循环
注2:两边都挺的情况下,可通过shutOutputStream()向服务端传入结束标记,服务端read方法既可退出
import java.io.*;
import java.net.*;

class Client
{
	public static void main(String[] args)throws Exception{
		//指定服务器地址、端口
		Socket s = new Socket(InetAddress.getLocalHost(),10000);
		
		BufferedReader bufr = new BufferedReader(new FileReader("d:\\java.java"));

		//获取Socket的输入、输出流对象并且封装为高效率
		BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
		PrintStream out = new PrintStream(s.getOutputStream(),true);
		
		//向服务器写入文件数据
		String line = null;
		while((line=bufr.readLine())!=null){
			out.println(line);
		}
		out.shutDownOutput();
		String str = in.readLine();
		
		System.out.println(str);
		s.close();
	}
}

class Server
{
	public static void main(String[] args)throws Exception{
		//建立服务器Socket对象
		ServerSocket ss = new ServerSocket(10000);+

		Socket s = ss.accept();

		//获取Socket的输入、输出流对象并且封装为高效率
		BufferedReader in  = new BufferedReader(new InputStreamReader(s.getInputStream()));
		PrintStream out = new PrintStream(s.getOutputStream(),true);

		//把读取到的数据写入到本地文件中
		PrintWriter pw = new PrintWriter(new FileWriter("E:\\哈哈.java"),true);
		String line = null;
		while((line=in.readLine())!=null){
			if("over".equals(line))
				break;
			pw.println(line);
		}
		
		//Thread.sleep(10000);
		out.println("传输成功");
		s.close();
	}
}
3.2.5 代码练习(多线程上传图片)
注1:一定要实现并发访问服务端,因为单线程将导致每一个客户都必须等待前面的客户服务完才可以访问。所以服务端应将每个客户端封装到不同线程中,这样可同时处理多个客户端请求
注2:客户端与服务端读写方法最好一致,复制文件才能保证没错
import java.io.*;
import java.net.*;

class TcpClient
{
	public static void main(String[] args)throws Exception{
		File file = new File("1.jpg");

		Socket s = new Socket(InetAddress.getLocalHost(),10000);

		BufferedInputStream bufis = new BufferedInputStream(new FileInputStream(file));

		BufferedInputStream bufin = new BufferedInputStream(s.getInputStream());

		OutputStream bufout = s.getOutputStream();//注意客户端和服务端尽量统一写入和发出的流

		byte[] buf = new byte[1024];

		int len;
		while((len=bufis.read(buf))!=-1){
			bufout.write(buf,0,len);
		}

		s.shutdownOutput();

		len = bufin.read(buf);

		System.out.println(new String(buf,0,len));

		bufis.close();
		s.close();
	}
}

class TcpServer
{
	public static void main(String[] args)throws Exception{
		ServerSocket ss = new ServerSocket(10000);

		while(true){
			Socket s = ss.accept();

			new Thread(new ThreadServer(s)).start();//每个用户都会创建多个线程
		}

		//ss.close();

	}
}

class ThreadServer implements Runnable
{
	private Socket s;
	ThreadServer(Socket s){
		this. s = s;
	}

	public void run(){
		String ip = null;
		try{
			int count = 1;
			File file = new File("上传文件(1).jgp");

			while(file.exists())
				file = new File("上传文件("+(++count)+").jgp");//用户创建多个文件

			ip = InetAddress.getLocalHost().getHostName();
			System.out.println(ip+"......conect.......");

			InputStream bufin = s.getInputStream();

			PrintWriter out = new PrintWriter(new OutputStreamWriter(s.getOutputStream()),true);

			FileOutputStream fos = new FileOutputStream(file);

			byte[] buf = new byte[1024];
			
			int len;
			while((len=bufin.read(buf))!=-1){
				fos.write(buf,0,len);
			}
			
			out.println("上传完毕");

			fos.close();
			s.close();
		}catch (Exception e){
			throw new RuntimeException(ip);
		}
		
	}
}
3.2.6 InetSocketAddress类--->实现SocketAddress接口

注1:此类与InetAddress类不同之处在于封装了IP地址与端口号

用法:Socket类有空参构造函数,可以通过connect(SocketAddress sa)这个方法关联服务端地址

4. URL类

构造方法摘要
URL(String spec) 
          根据 String 表示形式创建 URL 对象。
URL(String protocol, String host, int port, String file) 
          根据指定 protocolhostport 号和 file 创建 URL 对象。

方法摘要
 booleanequals(Object obj) 
          比较此 URL 是否等于另一个对象。
 StringgetFile() 
          获取此 URL 的文件名。
 StringgetHost() 
          获取此 URL 的主机名(如果适用)。
 StringgetPath() 
          获取此 URL 的路径部分。
 intgetPort() 
          获取此 URL 的端口号。
 StringgetProtocol() 
          获取此 URL 的协议名称。
 StringgetQuery() 
          获取此 URL 的查询部分。(获取地址参数信息)
 URLConnectionopenConnection() 
          返回一个 URLConnection 对象,它表示到 URL 所引用的远程对象的连接。(会连接URL指定主机,返回连接对象)

5.URLConnection类

意义:这是连接对象,已经实现Socket客户端与服务端之间的连接,无需建立Socket对象。并且次对象内部封装了HTTP协议,能够解析http响应头信息

注1:能够解析获取http响应头数据

方法摘要
 InputStreamgetInputStream() 
          返回从此打开的连接读取的输入流。
 OutputStreamgetOutputStream() 
          返回写入到此连接的输出流。

6. 浏览器与服务器原理

6.1 服务器:自定义

浏览器输入 【localhost:端口号】即可访问自定义服务器,在此程序中浏览器会接收到【你好】字样并显示在浏览器中
重点:服务器只是在做着把本地主机的html文件代码发送到客户端而已
import java.net.*;
import java.io.*;

/**
*客户端:浏览器
*服务端:自定义
*/

class java
{
	public static void main(String[] args)throws Exception{
		ServerSocket ss = new ServerSocket(10000);

		Socket s = ss.accept();

		BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));

		PrintStream pw = new PrintStream(s.getOutputStream());
		
		String line = null;
		while((line=bufr.readLine())!=null){//这里必须注释掉才能退出read()方法,因为客户端浏览器write()方法未添加结束标记
			System.out.println(line);
		}

		pw.println("欢迎");

		s.close();
	}
}
这个程序接收到浏览器发送来的数据为:(打印在控制台上的数据)

GET / HTTP/1.1
Host: localhost:10000
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36 SE 2.X MetaSr 1.0
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8

这是HTTP请求消息头,是客户端浏览器发送过来的请求

6.2 浏览器:自定义

注1:此程序会收到服务器发送回来的【http应答消息头】和【请求文件的源数据】

import java.net.*;
import java.io.*;

/**
*客户端:自定义浏览器
*服务端:Tomcat服务器
*/

class Client
{
	public static void main(String[] args)throws Exception{
		//向Tomcat服务器发送数据
		Socket s = new Socket(InetAddress.getLocalHost(),80);

		PrintWriter pw = new PrintWriter(s.getOutputStream());

		//向Tomcat服务器发送Http请求消息头
		pw.println("GET /myWeb/doing.html HTTP/1.1");
		pw.println("Accept: */*");
		pw.println("Accept-Language: zh-CN,zh;q=0.8");
		pw.println("Host: localhost");
		pw.println("Connection: closed");

		pw.println();
		pw.println();

		BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
		
		String line = null;
		System.out.println(188);
		while((line=bufr.readLine())!=null){
			System.out.println(188);
			System.out.println(line);//打印Tomcat返回的数据
		}
		s.close();
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值