计算机网络知识整理(一)

1. 概述

  • TCP/IP四层协议栈:应用层,传输层,网络层,网络接口层。
  • 因特网五层协议栈:应用层,传输层,网络层,链路层,物理层。
  • OSI七层协议栈参考模型:应用层,表示层,会话层,传输层,网络层,链路层,物理层。

问:为什么OSI模型有7层而因特网只有5层?
《自顶向下》中有这样一段:

表示层的作用是使通信和应用程序能够解释交换数据的含义。这些服务包括数据压缩,数据加密以及数据描述。会话层提供了数据交换定界和同步功能,包括了建立检查点和恢复方案。……这些层次提供的服务不重要吗?如果一个应用程序需要这些服务之一,将怎样呢?对这两个问题的回答是相同的:这留给应用程序开发者处理,应用程序开发者决定一个服务是否重要,如果该服务重要,应用程序开发者就应该在应用程序中构建该功能。

这一段就很好地回答了这个问题。


2. 应用层

2.1 HTTP

2.1.1 HTTP报文

请求报文

GET /dir/page.html HTTP/1.1
Host: www.someschool.edu
Connection: close
user-agent: Mozilla'
Accept-language: fr
空行
data data data ...(如果有)

常用Method

  • get: 根据某些关键字从服务器获取信息
  • post: 向服务器提交信息
  • head: 获取头部信息,如:缓存
  • put: 更新资源:put+完整资源
  • patch: 更新资源:patch+要更新的字段
  • delete: 删除资源
  • options: 允许客户端查看服务器的性能。
  • track: 回显服务器收到的请求

问:get和post有什么区别?
我认为,get和post最根本的区别是语义的区别 ,其他的所有区别都源于语义。

上文说过,get的语义是“根据某些关键字获取信息”,而post是“提交信息”。

因此出现了第一个区别:

  • get的内容在url中对用户可见(本就是用户提供的关键字,干嘛要藏起来?),而post不可见。

而很多区别又是这第一个区别导致的,如网上经常提到的

  • get只接受ASCII字符,post没有限制
  • get可以加书签,会留下历史记录,post不会
  • get的内容有长度限制(受限于url的长度),post没有长度限制

还有一个网上经常提到的区别:

  • get是幂等的,post不是。

HTTP/1.1中对幂等性的定义是:一次和多次请求某一个资源对于资源本身应该具有同样的结果。

从语义的角度理解,查询(get)某个资源很多次结果当然应该相同,而提交(post)某个资源很多次,服务器收到了多份资源,自然会产生不同的结果。

因此,get和post的最主要区别在于语义,其他的区别只是HTTP协议根据语义做出的优化调整。


 
响应报文

HTTP/1.1 200 OK
DATE: TUE, 09...
SERVER: Apache/2.2.3(CentOS)
Last-Modified:TUE, 09...
Content-Length: 6821
Content-Type: text/html
空行
data data data ...

状态码
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误

  • 100 (Continue): 客户端应当继续发送请求。
  • 101 (Switching Protocols): 通知客户端采用不同的协议来完成这个请求。
  • 200 (OK): 请求已成功,请求所希望的响应头或数据体将随此响应返回。
  • 201 (Created): 请求已经被实现,而且有一个新的资源已经依据请求的需要而建立,且其 URI 已经随Location 头信息返回。
  • 202 (Accepted): 服务器已接受请求,但尚未处理。
  • 203 (Non-Authoritative Information): 服务器已成功处理了请求,但返回的实体头部元信息不是在原始服务器上有效的确定集合,而是来自本地或者第三方的拷贝。
  • 300 (Multiple Choices): 被请求的资源有一系列可供选择的回馈信息,用户或浏览器能够自行选择一个首选的地址进行重定向。
  • 301 (Moved Permanently): 被请求的资源已永久移动到新位置。
  • 302 (Move Temporarily): 请求的资源临时从不同的 URI响应请求。
  • 303 (See Other): 类似于302,后面会讲到。
  • 304 (Not Modified): 这个状态码和条件Get方法(If-Modified-Since) 配合使用。
  • 305 (Use Proxy): 被请求的资源必须通过指定的代理才能被访问。
  • 307 (Temporary Redirect): 类似于302,后面会讲到。
  • 400 (Bad Request): 当前请求无法被服务器理解。
  • 401 (Unauthorized): 当前请求需要用户验证。
  • 403 (Forbidden): 服务器已经理解请求,但是拒绝执行它。
  • 404 (Not Found): 请求失败,请求所希望得到的资源未被在服务器上发现。
  • 500 (Internal Server Error): 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。
  • 501 (Not Implemented): 服务器不支持当前请求所需要的某个功能。
  • 502 (Bad Gateway): 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
  • 503 (Service Unavailable): 由于临时的服务器维护或者过载,服务器当前无法处理请求
  • 504 (Gateway Time-out) 作为网关或代理的服务器,未及时从远端服务器获取请求。
  • 505 (HTTP Version Not Supported) 服务器不支持,或者拒绝支持在请求中使用的 HTTP 版本。

问:状态码302,303,307之间有什么区别?
302是HTTP/1.0的状态码,对这个状态码的描述是:如果客户端发出 非幂等请求 后,收到服务端的302状态码,那么就不能自动的向新URI发送重复请求,除非得到用户的确认。然而在实际操作中,浏览器默认原本的非幂等请求已经执行完成,往往直接向新的url发一个幂等的 GET 请求,不会经过用户的确认。
303和307是HTTP/1.1的状态码,对302做了细分。303不需要经过用户确认,走302的实际操作流程,307则是走原本302描述的“合法”流程。
注意,在HTTP/1.1中,302还是走的“非法”流程。总结一下就是:

302文档描述 = 307文档描述 = 307实际执行 = 询问客户是否向新的url发出相同的非幂等请求
302实际执行 = 303文档描述 = 303实际执行 = 直接将原本的非幂等请求变为幂等的GET请求


2.1.2 HTTP版本

  • 0.9
    1.没有协议头,因此只有GET方法,没有错误码。
    2.只能传输超文本。
    3.只能是短连接。
  • 1.0
    1.增加了请求和相应的报文头,引入了16个状态码和GET,HEAD,POST方法。
    2.响应对象不只支持超文本。
    3.默认短连接,支持长连接:需要在请求报文头添加Connection: Keep-Alive
  • 1.1
    1.再次新增了一些状态码和请求方法,和身份认证、状态管理和缓存相关。
    2.默认长连接。
    3.提供了虚拟主机的功能:随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机,并且它们共享一个IP地址,因此增加HOST头域。
  • 2.0
    1.所有帧都采用二进制编码。
    2.多路复用:不再依赖多个TCP 连接去实现多流并行,每个数据流都拆分成很多互不依赖的帧,而这些帧可以乱序发送,还可以分优先级。最后再在另一端把它们重新组合起来。
    3.头部压缩:HTTP1.1每次都要用很多资源发报文头部,因此双方缓存头部,减小头部大小。
    4.支持服务器推送:如服务器发送一个html时主动发送其中包含的图片,CSS等资源。

2.1.3 HTTP状态

HTTP协议是一个无状态协议,无状态协议是指协议对于事务处理没有记忆能力。这意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。解决方案有两个:Session和Cookie。

Cookie: Cookie实际上是一小段文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把报文连同Cookie一同提交给服务器。服务器检查Cookie,以此来辨认用户状态。

Session: 当某个客户第一次访问服务器是,服务器端给他分配一个sessionID,服务器端维护一张sessionID—>用户状态的映射表,当客户再次访问该服务器时,报文中会包含该sessionID,服务器根据sessionID查映射表来获取客户状态。当session过期,服务器从映射表中删除对应的session。

两者的区别:

  1. Cookie数据存放在客户的浏览器上,Session数据放在服务器上。
  2. Cookie不安全,别人可以分析存放在本地的Cookie并进行Cookie欺骗,考虑到安全应当使用Session。
  3. Session会在一定时间内保存在服务器上。当访问增多,会占用服务器的性能考虑到减轻服务器性能方面,应当使用Cookie。
  4. 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
  5. session一般有一个过期时间,cookie可以是临时的,也可以是永久的。

2.2 DNS

DNS协议是一个基于分布式数据库,将域名解析为相应的IP地址的协议,主要理解DNS解析的过程。

  1. 依次查询浏览器缓存,系统缓存(Hosts文件),路由器缓存,本地域名服务器缓存(LDNS),如果其中有一个命中,后续就不再查询,直接返回结果。
  2. 如果上述查询都没有命中,则由LDNS迭代地查询根域名服务器,顶级域名服务器和权威域名服务器。其中根域名服务器共有13个(13个IP地址,不是13台),迭代过程就是上游服务器将要查询的子服务器的IP地址返回给LDNS,LDNS根据得到的IP地址查下一个域名服务器。
  3. LDNS获得解析结果后,将其缓存并返回个客户。

2.3 FTP

FTP是文件传输协议,数据端口20,控制端口21。

2.4 SMTP

SMTP是简单邮件传输协议,大致过程就是发送代理–>发送方服务器–>接收方服务器–>接收代理。

和HTTP的区别:
1.SMTP是推协议,HTTP是拉协议
2.SMTP必须是7bit Ascall码,HTTP不一定
3.SMTP所有对象在一个报文中,HTTP一个对象一个报文。


3 会话层

前面提到,会话层提供了数据交换定界和同步功能,包括了建立检查点和恢复方案。一般认为HTTPS的SSL或TLS协议在会话层。

3.1 SSL协议

  1. 客户端发出一次请求
  2. 服务器端生成一对非对称加密密钥,把公钥发送给证书机构,证书机构通过服务器端的信息生成一个数字签名,和公钥一起,用自己的私钥加密后作为证书返回给服务器端。
  3. 服务器端把这个证书发送给客户端。
  4. 客户端向证书机构验证证书的真伪,并用证书机构的公钥解密证书,获取服务器端的公钥。
  5. 客户端形成随机的对称加密密钥,通过服务器端的公钥加密,发送给服务器端。
  6. 服务器端用自己的私钥破解,双方开始使用该对称加密密钥进行数据传输。
  • 数字签名: 通过提供可鉴别的数字信息验证自身身份的一种方式。一套数字签名通常定义两种互补的运算,一个用于 签名,另一个用于 验证。签名的目的是要能够 唯一证明发送方的身份 ,防止中间人攻击、CSRF 跨域身份伪造等。

  • 对称加密: 在对称加密算法中,使用的密钥只有一个,发送和接收双方都使用这个密钥对数据进行加密和解密。对称加密算法要求加密和解密方事先都必须知道加密的密钥。常见的对称加密算法主要有 D E S 、 3 D E S 、 A E S DES、3DES、AES DES3DESAES 等。

  • 非对称加密: 在非对称加密算法中,使用两个密钥,一个是公开的的密钥,称为 公钥,一个是私有的的密钥,称为 私钥。如果用公钥加密,则可用私钥解密;如果用私钥加密,则要用公钥解密。常见的非对称加密算法主要有 R S A 、 D S A RSA、DSA RSADSA 等。


4 传输层

提供不同主机进程间的逻辑通信。

4.1 可靠数据传输

协议情景改变实现
r d t   1.0 rdt\ 1.0 rdt 1.0/发送方发出包后便结束通信假设通信建立在完全可靠的网络信道之上
r d t   2.0 rdt\ 2.0 rdt 2.0考虑数据包的比特差错新增校验和机制接收方通过返回ACK或NAK来表示自己是否收到了正确的包,发送方收到NAK重发
r d t   2.1 rdt\ 2.1 rdt 2.1考虑ACK\NAK的比特差错对数据包和ACK/NAK新增版本号(0,1)通过检查ACK/NAK的版本号判断其正确性
r d t   2.2 rdt\ 2.2 rdt 2.2/取消NAK只需要通过ACK的版本号就能判断接收方是否正确收到数据包
r d t   3.0 rdt\ 3.0 rdt 3.0考虑信道上的丢包新增计时器发送方在计时器限定的时间内未收到ACK便重发

r d t rdt rdt 都是停等协议,即等到上一个包被正确接收后才会发送下一个包,效率低,因此又产生了两种流水线协议:回退N步选择重传

回退N步 ( G o − B a c k − N ) (Go-Back-N) (GoBackN)

  1. 发送者在流水线中最多有 N N N 个未确认的数据报。
  2. 接收者仅发送累计的确认 ,如果中间有数据报缺失,就不予以确认。
  3. 发送者对最久未确认的数据报进行计时,如果计时器到点, 重传所有未确认的数据报。
  4. 发送窗口大于1,接受窗口等于1(也就意味着如果某一个报文段出现错误,那么接受窗口会停留再次,之后收到的数据将会被丢弃)

选择重传 ( s e l e c t i v e   r e p e a t ) (selective\ repeat) (selective repeat)

  1. 发送者在流水线中最多有 N N N 个未确认的数据报。
  2. 接收者对单个数据报进行确认。
  3. 发送者对每一个未确认的数据报进行计时,如果计时器到点, 仅重传该个未确认的数据报。
  4. 发送窗口大于1,接受窗口大于1(意味着可以缓存出错位置之后的报文段),最好是两者相同。

TCP的可靠数据传输
TCP的可靠数据传输可以理解为 G B N GBN GBN S R SR SR 的混合体。

  1. 发送者在流水线中最多有 N N N 个未确认的数据报。
  2. 接收者仅发送累计的确认 ,如果中间有数据报缺失,就不予以确认。(此处类似GBN)
  3. 发送者对每一个未确认的数据报进行计时,如果计时器到点, 仅重传该个未确认的数据报。另外在当发送方收到三个冗余ACK时也会进行重传。
  4. 发送窗口大于1,接受窗口大于1(意味着可以缓存出错位置之后的报文段),最好是两者相同。(此处类似SR)

4.2 TCP

4.2.1 三次握手

发送方:SYN=1,seq=X,进入SYN_SENT状态
接收方:SYN=1,ACK=1,seq=Y,ack=X+1,进入SYN_RECVD状态
发送方:SYN=0,ACK=1,seq=X+1,ack=Y+1,双方进入ESTABLISHED状态


问:为什么有了SYN还要有ACK?
参考TCP报文段头结构:
在这里插入图片描述
可以看到,在图中第四行中存在6bit的标识位,其中A就是ACK标识位,其含义是第三行的确认序号是否有效。S就是SYN标识位,标识其是否是一次建立连接的请求。因此SYN和ACK表示两种不同的涵义。


问:为什么需要三次握手?
原因有以下两点:
1.假设客户端第一次发的请求包因某种原因在通讯中滞留(如在路由器中排队),导致客户端发了第二次请求,第二次请求的连接关闭后。服务器收到了之前滞留的包,就会建立第二次连接,进而浪费资源。
2.前面在可靠数据传输中提到,要在存在比特差错的信道上进行可靠传输,至少需要两次数据传输,即数据包和ACK。握手是客户端向服务器端建立连接,服务器端向客户端建立连接的两次过程,因此理论上需要进行四次传输,而TCP在连接建立时,服务器端机智地将数据包和ack合并为一次,因此是三次握手。


问:接收方出现大量SYN_RECVD状态如何解决?
在正常情况下,SYN_RECVD状态的持续时间都很短,不可能出现大量SYN_RECVD的情况。当出现大量SYN_RECVD状态时,多半是收到了SYN攻击。
SYN攻击 :SYN攻击是指在短时间内伪造大量不存在的IP地址,向服务器不断地发送SYN包,服务器回复确认包,并等待客户的确认,由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,目标系统运行缓慢,严重者引起网络堵塞甚至系统瘫痪。
Linux下可用如下命令查看SYN_RECVD状态的数量:

netstat -n -p TCP | grep SYN_RECV

一般解决方法有SynAttackProtect保护机制、SYN cookies技术、增加最大半连接和缩短超时时间等,但是不能完全防范syn攻击。
以下是一组优化参数。

net.ipv4.tcp_syn_retries = 2
#服务器在等待不到终端的确认消息时,syn消息报文会重新发送,需要根据网络情况尽量减少,如果网络不好的情况下终端可能会出现连不上服务器的情况】
net.ipv4.tcp_synack_retries = 2
#服务器在等待不到终端的确认消息时,synack消息报文会重新发送,需要根据网络情况尽量减少,情况和上面一样
net.ipv4.tcp_syncookies = 1
#该功能可以防止部分SYN攻击
net.ipv4.tcp_max_syn_backlog = 1024
#tcp_max_syn_backlog 是SYN队列的长度,加大SYN队列长度可以容纳更多等待连接的网络连接数

4.2.2 四次挥手

发送方:FIN=1,seq=X,ack=Z,进入FIN_WAIT1状态
接收方:ACK=X+1,seq=Z,进入CLOSE_WAIT状态
接收方:FIN=1,seq=Y,ack=X+1,进入LAST_ACK状态
发送方:ack=Y+1,seq=X+1 进入TIME_WAIT状态,等2MSL后关闭

特殊情况:
发送方,接收方(同时):FIN=1 进入CLOSING状态
发送方,接收方:ACK 进入TIME_WAIT状态


问:为什么需要四次挥手?
前面提到,握手只需要三次是因为服务器端将数据包和ack的发送合并为一次。而在挥手时,服务器端则不能这样做,因为客户端发送FIN只是说明客户端已经把消息发完了,服务器端返回ACK后,还要继续把自己要发的其他东西发完,才会发FIN,因此挥手需要四次。


问:为什么TIME_WAIT状态要等2MSL(报文段最长生存时间)才关闭?
原因有以下两点:
1.前面说到,可能有数据包会滞留在网络中,要确保连接关闭时该连接中所有的发送的数据包都已到达或丢弃,因此要等待一段时间。
2.如果最后一个ACK丢失,服务器在等待1MSL后会将FIN重发,最长间隔即为2MSL,因此要等待2MSL以便能收到重发的FIN。


问:接收方出现大量TIME_WAIT状态如何解决?
出现大量TIME_WAIT状态主要因为TCP连接太多,因此优化的思路主要是

  1. 提高TCP连接的复用(使用长链接)
  2. 加快TCP连接的回收和重用
  3. 增加可容纳的网络链接数
  4. 减少TIME_OUT的时间

以下是一组优化参数。

net.ipv4.tcp_syncookies = 1 
#表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1 
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1 
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout 
#修改系默认的 TIMEOUT 时间
net.ipv4.tcp_keepalive_time = 1200 
#表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为20分钟。
net.ipv4.ip_local_port_range = 1024 65000 
#表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为1024到65000。
net.ipv4.tcp_max_syn_backlog = 8192 
#表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
net.ipv4.tcp_max_tw_buckets = 5000 
#表示系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息。

问:接收方出现大量CLOSE_WAIT状态如何解决?
CLOSE_WAIT状态是留给接收方把余下的一些包发完,因此一般很短时间内就应该结束了。因此大量CLOSE_WAIT状态出现一般是因为程序本身的问题,或者是发送剩余数据包时出现了问题,或者是忘记了关闭连接(发FIN)。


4.2.3 超时机制

前面在可靠数据传输部分提到过,TCP的可靠数据传输会给每个数据包维护一个定时器,那么超时的时间如何计算呢?
估计往返时间: E s t i m a t e d R T T = ( 1 − α ) ∗ E s t i m a t e d R T T + α ∗ S a m p e l R T T EstimatedRTT = (1-α)*EstimatedRTT + α*SampelRTT EstimatedRTT=(1α)EstimatedRTT+αSampelRTT
往返时间偏离量: D e v R T T = ( 1 − β ) ∗ D e v R T T + β ∗ ∣ E s t i m a t e d R T T − S a m p e l R T T ) DevRTT = (1-β)*DevRTT + β*|EstimatedRTT - SampelRTT) DevRTT=(1β)DevRTT+βEstimatedRTTSampelRTT)
则超时间隔: T i m e O u t I n t e r v a l = E s t i m a t e d R T T + 4 ∗ D e v R T T TimeOutInterval = EstimatedRTT + 4*DevRTT TimeOutInterval=EstimatedRTT+4DevRTT
TCP超时间隔加倍机制:
TCP每次发生超时重传后,会把下一次的超时间隔设置为原先的两倍。
当接收到上层数据或收到新的ACK时,才会再次用SampleRTT计算超时间隔。

4.2.4 流量控制

假设接收方缓存区大小为 R c v B u f f e r RcvBuffer RcvBuffer,上层读取的最后一个字节编号为 L a s t B y t e R e a d LastByteRead LastByteRead,从网络中收到的最后一个字节的编号为 L a s t B y t e R c v d LastByteRcvd LastByteRcvd。TCP不允许缓存溢出,所以有: L a s t B y t e R c v d − L a s t B y t e R e a d ≤ R c v B u f f e r LastByteRcvd - LastByteRead ≤ RcvBuffer LastByteRcvdLastByteReadRcvBuffer
因此窗口大小: r w n d = R c v B u f f e r − ( L a s t B y t e R c v d − L a s t B y t e R e a d ) rwnd = RcvBuffer - (LastByteRcvd - LastByteRead) rwnd=RcvBuffer(LastByteRcvdLastByteRead)
发送方窗口大小要小于等于接收方窗口大小: L a s t B y t e S e n t − L a s t B y t e A c k e d ≤ r w n d LastByteSent - LastByteAcked ≤ rwnd LastByteSentLastByteAckedrwnd

4.2.5 拥塞控制

拥塞控制说白了就是拥塞窗口大小在作死的边缘疯狂试探,以适应网络情况。记拥塞窗口为 c w n d cwnd cwnd,阈值 s s t h r e s h ssthresh ssthresh ,算法流程大概如下:

  1. cwnd初始为1,每隔RTT翻倍。(慢启动)
  2. 若出现三个冗余ACK:重传(快速重传), s s t h r e s h = c w n d / 2 , c w n d = s s t h r e s h + 3 , c w n d ssthresh=cwnd/2,cwnd=ssthresh+3,cwnd ssthresh=cwnd/2cwnd=ssthresh+3cwnd线性增加。(拥塞避免)
  3. 若超时:重传, s s t h r e s h = c w n d / 2 , c w n d = 0 , c w n d ssthresh=cwnd/2,cwnd=0,cwnd ssthresh=cwnd/2cwnd=0cwnd继续翻倍增加,到达 s s t h r e s h ssthresh ssthresh 后线性增加。(快速恢复)

综合流量控制来看,有:
L a s t B y t e S e n t − L a s t B y t e A c k e d ≤ m i n { r w n d , c w n d } LastByteSent - LastByteAcked ≤ min\{rwnd,cwnd\} LastByteSentLastByteAckedmin{rwnd,cwnd}
 

4.3 UDP

问:TCP和UDP的区别?
TCP面向链接,UDP不面向连接。
TCP重视传输的可靠性,UDP重视传输的及时性。
TCP只能一对一,UDP可以一对多。
TCP提供流量控制和拥塞控制,UDP不提供。
TCP在接受方窗口缓存报文,对上层按序交付。UDP不按序。
TCP头部20字节,UDP头部8字节。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值