网络编程基础

目录

一、网络编程能做什么?

二、网络、互联网、因特网

三、Internet历史

第一阶段

第二阶段:

第三阶段:

四、ISP

五、网络分类:

按交换技术分类:

按使用者分类:

按传输介质分类:

按覆盖范围分类:

按拓扑结构分类:

六、什么是IP地址:

分类:

IPV4的姿势:

*IPV4的组成:

*IPV4分类:

七、子网掩码:

八、网关:

九、计算机网络体系结构

OSI架构(七层):

*OSI失败原因:

*TCP/IP:

*端口号:

TCP数据报:

*传输层:

TCP/IP协议通信模型

 TCP: 三次握手 四次挥手

三次握手(请求连接):

四次挥手(断开连接):

十、字节序

 字节序转换函数

 IP地址的转换

十一、函数的使用

网络编程相关API

socket() 创建套接字

bind() 绑定本机地址和端口

listen() 设置监听端口

accept() 接受TCP连接

connect() 建立连接

recv(), recvfrom() , read()数据接收

send(), sendto() , write()数据发送

close(), shutdown() 关闭套接字

 recvfrom()和sendto()

 recvfrom()

sendto()

函数应用


一、网络编程能做什么?

        网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的。


二、网络、互联网、因特网

1、网络由若干个节点和连接这些节点的链路组成

2、多个网络还可以通过路由器互连起来,就构成了一个覆盖范围更大的网络,即互联网(网络的网络)

3、因特网(Internet)是世界做大的互联网。


三、Internet历史

第一阶段:

*》1969年,第一个分组交换机ARPANET诞生

*》70年代中期,研究多种网络之间的互连

*》1983年,TCP/IP协议成为ARPANET的标准协议\(因特网诞生)

第二阶段:

*》1985年,NSF围绕六个大型计算机中心创建NFSNET

*》1990年,ARPANET正式关闭

*》1991年,因特网由政府转移个私人公司,开始收费

第三阶段:

*》1993年,NSFNET被商用因特网主干网替代,\由因特网服务提供者ISP来运营

*》1994年,万维网(www)促使因特网迅猛发展

*》1995年,NSFNET停止运行,因特网商业化


四、ISP是什么?

* Internet Service Provider(因特网服务提供者)

* 中国电信,中国联网,中国移动

* 为已经缴费的使用者提供可以联网通信的IP地址


五、网络分类:

按交换技术分类:

*电路交换网络

*报文交换网络

*分组交换网络

按使用者分类:

*公用网

*专用网

按传输介质分类:

*有线网络

*无线网络

按覆盖范围分类:

*广域网(WAN)

*城域网(MAN)

*局域网(LAN)

*个域网(PAN)

按拓扑结构分类:

*总线型网络

*星型网络

*环形网络

*网状型网络


六、什么是IP地址:

        IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。

分类:

IPV4 -- 4个字节

IPV6 -- 16个字节

IPV4的姿势:

192.168.101.5 ---》点分十进制

*IPV4的组成:

网络地址 + 主机地址

主机地址:不能全部为0或者全部为1

---》全部为0 ---网络id号

---》全部为1 ---广播地址

*IPV4分类:

A类: 1个字节网络地址 + 3个字节主机地址

网络地址:以0开头

取值范围:

00000000.0.0.1 ~ 01111111.255.255.254

0.0.0.1 ~ 127.255.255.254

B类: 2个字节网络地址 + 2个字节主机地址

网络地址:以10开头

取值范围:

10000000.0.0.1 ~ 10111111.255.255.254

128.0.0.1 ~ 191.255.255.254

C类: 3个字节网络地址 + 1个字节主机地址

网络地址:以110开头

取值范围:

11000000.0.0.1 ~ 11011111.255.255.254

192.0.0.1 ~ 223.255.255.254

---->我们的IP : 192.168.101.5

D类: 4个字节网络地址 + 0个字节主机地址 ---》 多播(组播)

网络地址:以1110开头

取值范围:

11100000.0.0.0 ~ 11101111.255.255.255

224.0.0.0 ~ 239.255.255.255

E类: 网络地址: 11110开头 ,待留后用


七、子网掩码:

        用于减少IP浪费,细化IP分类,判断若干个网络是否在同一局域网内的机制

表现形式:

网络地址+主机地址

网络地址全部为1 主机地址全部为0

*姿势:

255.255.255.0 ====》 3个字节的网络地址 1个字节主机地址

[192.168.101.5/24] ----》 /24 代表 前 24位 子网掩码的 网络地址

*判断是否在同一网络下的规则:

ip地址 & 子网掩码 -----》 网络id号

如果网络id号相同,则在同一网络下

192.168.101.5 & 255.255.255.0 ----》 192.168.101.0

192.168.101.10 & 255.255.255.0 ----》 192.168.101.0

----》就说 192.168.101.5 和 192.168.101.10 在同一网络下

* 将主机地址 分出 一部分 作为 子网地址

255.255.255.1110000

192.168.101.0011000 & 255.255.255.1110000 ----》 192.168.101.0010000

192.168.101.0111000 & 255.255.255.1110000 ----》 192.168.101.0110000

称: 192.168.101.24 与 192.168.101.56 不在同一网络


八、网关:

        网关(Gateway)又称网间连接器、协议转换器。默认网关在网络层以上实现网络互连,是最复杂的网络互连设备,仅用于两个高层协议不同的网络互连。网关的结构也和路由器类似,不同的是互连层。网关既可以用于广域网互连,也可以用于局域网互连。其实质是一个网络通向另外一个网络的IP地址

*》数据如何生成帧 从 客户机 通过网络 发送出去:

微信中:我喜欢你 ----》 将其打包 ---》 帧 --》 网络 --》 对方


九、计算机网络体系结构

      为了使不同体系结构的计算机网络都能互联,国际标准\化组织于1977年成立了专门了研究机构研究该问题不久\就提出了一个标准框架OSI架构(开放系统互连参考模型)


OSI架构(七层):

        OSI模型相关的协议已经很少使用,但模型本身非常通用 OSI模型是一个理想化的模型,尚未有完整的实现 OSI模型共有七层(右图)

*OSI失败原因:

*OSI专家缺乏实际经验

*协议过分复杂,运行效率低

*OSI层次划分不合理,有些功能重复出现

*TCP/IP:

*将数据链路层和物理层合并为网络接口层

*将会话层和表示层合并到了应用层

*将网络层更名为网际层(因为使用的是IP协议)

/* 原理体系 */

*原理体系架构(五层)

--------------

| 应用层 | 通过应用进程的交互实现网络应用

--------------

| 传输层 | 解决进程间基于网络通信的问题 (TCP UDP 端口对端口)

--------------

| 网络层 | 解决在多个网络上传输的问题 (IP地址 寻找路线)

--------------

| 数据链路层 | 解决在一个网络上的传输问题

--------------

| 物理层 | 解决使用何种信号表示比特的问题

--------------

(适于教学的架构)


*端口号:

2个字节 65536

用户使用时端口在5001-65536之间,建议范围内越大越好

*》数据发送的整个流程:

*》 数据

*》应用层 :HTTP+数据 (HTTP报文)

*》传输层 :TCP+HTTP+数据 (TCP报文)

*》网络层 :IP+TCP+HTTP+数据 (IP报文)

*》数据链路层: ETH+IP+TCP+HTTP+数据+ETH (帧)

*》物理层:前导码+帧 将帧看成比特流

*》路由器转发过程:

*物理层 : 去掉前导码 ---》 帧

*数据链路层: 去掉帧头帧尾 ---》 IP数据报

*网络层: 分析IP数据报确认路线重新打包IP数据报

*数据链路层: 添加帧头帧尾 ---》 帧

*物理层 : 前导码+帧 将帧看成比特流

*》数据接受的整个流程:

*物理层 : 去掉前导码 ---》 帧

*数据链路层:去掉帧头帧尾 ---》 IP数据报

*网络层: 去掉IP协议头 ---》 TCP+HTTP+数据

*传输层: 去掉端口协议头 ---》 HTTP+数据

*应用层: 去掉协议头 ---》 数据

ARP请求: 通过IP地址得到物理地址

RARP请求:通过物理地址 得到 IP地址

IP数据报的生存时间:

TTL : 跳 次数 8


TCP数据报:

URG : 紧急数据

SYN表示建立连接

FIN表示关闭连接

ACK表示响应

PSH表示有 DATA数据传输,没有缓冲,直接发送给应用层

RST表示连接重置


*传输层:

        TCP是一种面向广域网的通信协议,目的是在跨越多个网络通信时,为两个通信端点之间提供一条具有下列特点的通信方式: [1] 

(1)基于流的方式;

(2)面向连接;

(3)可靠通信方式;

(4)在网络状况不佳的时候尽量降低系统由于重传带来的带宽开销;

(5)通信连接维护是面向通信的两个端点的,而不考虑中间网段和节点。

TCP:事先必须建立连接,数据传输稳定,可靠,不丢包

        UDP即用户数据报协议,是一种面向无连接的不可靠传输协议,具有资源消耗小、处理速度快的特点。为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法,实际应用:多媒体数据流。

UDP:不需要连接,不可靠,可能会丢包


TCP和UDP两种协议对比:

        TCP 是面向连接的传输控制协议,而UDP 提供了无连接的数据报服务;TCP 具有高可靠性,确保传输数据的正确性,不出现丢失或乱序;UDP 在传输数据前不建立连接,不对数据报进行检查与修改,无须等待对方的应答,所以会出现分组丢失、重复、乱序,应用程序需要负责传输可靠性方面的所有工作;UDP 具有较好的实时性,工作效率较 TCP 协议高;UDP 段结构比 TCP 的段结构简单,因此网络开销也小。TCP 协议可以保证接收端毫无差错地接收到发送端发出的字节流,为应用程序提供可靠的通信服务。对可靠性要求高的通信系统往往使用 TCP 传输数据。比如 HTTP 运用 TCP 进行数据的传输。


TCP/IP协议通信模型


 TCP: 三次握手 四次挥手

三次握手(请求连接):

1. 客户端给服务器发送SYN连接请求

2. 服务器给客户回复了确认序列ACK,并发起SYN连接请求

3. 客户端给服务器回复了确认序列ACK

通俗理解:

我:上号?(第一次握手)

你:上!速度!(第二次握手)

我:1(第三次握手)

四次挥手(断开连接):

1. 客户端给服务器发送FIN断开请求,并回复确认序列ACK

2. 服务器给客户端回复了确认序列ACK

3. 服务器给客户端发送FIN断开请求,并回复确认序列ACK

4. 客户端给服务器回复确认序列ACK

通俗理解:

我:分手?(第一次挥手)

你:好(第二次挥手)

你:联系互删吧?(第三次挥手)

我:好(第四次挥手)


十、字节序

        不同类型CPU的主机中,内存存储多字节整数序列有两种方法,称为主机字节序(HBO): 小端序(little-endian) - 低序字节存储在低地址 将低字节存储在起始地址,称为“Little-Endian”字节序,Intel、AMD等采用的是这种方式;

        大端序(big-endian)- 高序字节存储在低地址 将高字节存储在起始地址,称为“Big-Endian”字节序,由ARM、Motorola等所采用 网络中传输的数据必须按网络字节序,即大端字节序。

        网络字节序(NBO - Network Byte Order) 使用统一的字节顺序,避免兼容性问题 主机字节序(HBO - Host Byte Order) 不同的机器HBO是不一样的,这与CPU的设计有关 Motorola 68K系列、ARM系列,HBO与NBO是一致的 Intel X86系列,HBO与NBO不一致。


 字节序转换函数

    htons: 主机数据转网络数据  host to net short  
    ntohs: 网络数据转主机数据  net to host short 

    #include <arpa/inet.h>
    uint16_t htons(uint16_t hostshort);
    uint16_t ntohs(uint16_t netshort);

 IP地址的转换

inet_aton
      #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    int inet_aton(const char *cp, struct in_addr *inp);
    主机地址转网络地址
    cp : 点分十进制ip地址
    inp : struct in_addr
    

inet_ntoa
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    char *inet_ntoa(struct in_addr in);
    网络地址转主机地址

inet_addr
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    in_addr_t inet_addr(const char *cp);
    主机地址转网络地址
    cp : 点分十进制ip地址 
    in_addr_t :网络数据类型的ip


十一、函数的使用

网络编程相关API

网络编程常用函数:

socket() 创建套接字

bind() 绑定本机地址和端口

listen() 设置监听端口

accept() 接受TCP连接

connect() 建立连接

send(), sendto(), write() 数据发送

recv(), recvfrom(), read() 数据接收

close(), shutdown() 关闭套接字


socket() 创建套接字

*socket:

#include <sys/types.h>             /* See NOTES */

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

解释:

创建socket套接字

参数:

domain : 协议

AF_UNIX  AF_LOCAL Local communication unix(7)

AF_INET IPv4 Internet protocols ip(7)

AF_INET6 IPv6 Internet protocols ipv6(7)

type:

SOCK_STREAM :流式套接字 (TCP)

SOCK_DGRAM : 数据报套接字 (UDP)

SOCK_RAW : 原始套接字

protocol : 0

返回值:

int :成功返回socket文件描述符

失败返回-1 并设置错误码

IPV4:

#define __SOCK_SIZE__ 16                  /* sizeof(struct sockaddr) */

struct sockaddr_in {

__kernel_sa_family_t sin_family;           /* Address family */

__be16 sin_port;                                    /* Port number         */

struct in_addr sin_addr;                          /* Internet address */

/* Pad to size of `struct sockaddr'. */

unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -sizeof(unsigned short int) - sizeof(struct in_addr)] ;

};

struct in_addr {

__be32 s_addr;

};

IPV6:

struct sockaddr_in6

本地:

struct sockaddr_un

bind() 绑定本机地址和端口

#include <sys/types.h>         /* See NOTES */

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

解释:

绑定IP和PORT

参数:

sockfd :sock套接字

addr :struct sockaddr *

struct sockaddr {

sa_family_t sa_family; //unsigned short

char sa_data[14];

}// 加起来 16个字节

addrlen : addr的长度

返回值:

成功返回0

失败返回-1 并设置错误码

listen() 设置监听端口

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int listen(int sockfd, int backlog);

*解释:

监听,将主动连接变成被动连接

参数:

sockfd : sock套接字

backlog: 允许同一瞬间同时连接服务器的客户数量

返回值:

成功返回0

失败返回-1 并设置错误码

accept() 接受TCP连接

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数:

sockfd : sock套接字

addr : struct sockaddr * 客户端的数据(IP和PORT)

addrlen: addr的长度指针 socklen_t *

返回值:

成功返回文件描述符

失败返回-1 并设置错误码

accept:阻塞等待客户端的连接

 socket和accept返回的套接字不同之处。

转载https://blog.csdn.net/jiujiederoushan/article/details/82178698

1、socket返回的套接字用于listen、bind或者connect,服务端调用listen、bind之后再用于生产accept_fd,客户端则connect后直接可以读写和服务端通讯。

2、accept调用socket产生的套接字并返回xi套接新的,用于和客户端通讯读写,若一台服务器的一个端口连接了三个客户端,则有一个sock_fd和三个accept_fd,共四个套接字描述符。

3、accept返回成功后,可以直接关闭socket产生的套接字sock_fd,不影响后面accept_fd的通讯。

4、客户端关闭连接后,服务器需要关闭所有套接字,包括socket_fd和accept_fd。

 

举例:一个客户端和一个服务端连接

双方socket产生各自的c_sock_fd和s_sock_fd;

s_sock_fd进行bind和listen后,accept准备接受客户端的连接请求;

c_sock_fd调用connect请求连接服务端;

服务端接到请求产生accept_fd,届时accept_fd和c_sock_fd两个套接字可以通讯,而s_sock_fd则可以关闭;

客户端关闭close(c_sock_fd)后,服务端关闭所有未关闭的fd,通讯彻底断开。

(服务端的socket产生的套接字只是用来监听的,不能直接用于发送接收数据。)
————————————————
版权声明:本文为CSDN博主「OctopusMonster」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/jiujiederoushan/article/details/82178698

connect() 建立连接

 #include <sys/types.h>      /* See NOTES */

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

参数:

sockfd : sock套接字

addr : 服务器的IP和PORT

addrlen: addr的长度

返回值:

成功返回0

失败返回-1 并设置错误码

=============

#include <stdlib.h>

int atoi(const char *nptr);

/* 将字符串nptr转换为整型数,并返回这个数,错误返回0 */

转化时跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时(’/0’)才结束转换,并将结果返回。

recv(), recvfrom() , read()数据接收

#include <sys/types.h>

#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数:

sockfd : 文件描述符(套接字)

buf : 读取数据存储的位置

len : 长度

flags :0 阻塞

返回值:

成功返回得到的字节数

失败返回-1 并设置error

掉线则返回0 

read()/write()

ssize_t read(int fd, void *buf, size_t count);

ssize_t write(int fd, const void *buf, size_t count);

read()和write()经常会代替recv()和send(),通常情况下,看程序员的偏好

使用read()/write()和recv()/send()时最好统一

send(), sendto() , write()数据发送

#include <sys/types.h>

#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数:

sockfd : 文件描述符(套接字)

buf : 读取数据存储的位置

len : 长度

flags :0 阻塞

返回值:

成功返回发送的字节数

失败返回-1 并设置error

close(), shutdown() 关闭套接字

int close(int sockfd)

对已连接的套接字执行 close 操作就可以,若成功则为 0,若出错则为 -1。

int shutdown(int sockfd, int howto)

对已连接的套接字执行 shutdown 操作,若成功则为 0,若出错则为 -1。

        close终止了数据传送的的两个方向,shutdown可以有选择的终止某个方向的数据传送或者终止数据传送的两个方向


 recvfrom()和sendto()

        这两个函数一般在使用UDP协议时使用

 recvfrom()

UDP:
    不需要连接,数据可能会丢失,不可靠
    
*接收数据     recvfrom    
   
#include <sys/types.h>
#include <sys/socket.h>

 ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
参数:
    sockfd : sock文件描述符
    buf    :存储接收到的数据
    len       :buf的长度 sizeof 
    flags  :0  阻塞 
    src_addr : 保存 数据的来源 (数据来源的 IP和PORT)
    addrlen  : addr的长度指针
返回值:    
    成功返回接送到的字节数
    失败返回-1 并设置errno

/*   ssize_t recv(int sockfd, void *buf, size_t len, int flags);
      int bind(int sockfd, const struct sockaddr *addr,  socklen_t addrlen);   参数相似*/

sendto()

*udp             发送数据
sendto
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
    sockfd : sock文件描述符
    buf    :存储发送的数据
    len       :buf的长度 strlen
    flags  :0  阻塞 
    src_addr : 发送给谁 ,对方的IP和PORT 
    addrlen  : addr的长度
返回值:
    成功返回发送的字节数
    失败返回-1 并设置errno  

/*   ssize_t send(int sockfd, const void *buf, size_t len, int flags);    对比*/


函数应用

代码如下(示例):

server 的创建

#include <stdio.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define PORT  12345
#define IP 	  "192.168.101.68"	

int main(void)
{
	//创建socket套接字
	int sockfd =  socket(AF_INET,SOCK_STREAM,0);
	//参数1:IPv4协议。参数2:套接类型为流式套接字。参数3:零表示以上两个遵循协议。
	if (sockfd < 0){
		perror("socket error");
		exit(-1);
	}
	
	//绑定IP和PORT(端口)
	struct sockaddr_in server;
	server.sin_family = AF_INET;//协议
	server.sin_port = htons(PORT);//大于五千的端口号,注意大小端序转换
	server.sin_addr.s_addr = INADDR_ANY;//自动获取IP
	
	int ret = bind(sockfd,(struct sockaddr *)&server,sizeof(server));
	if(ret){
		perror("bind error");
		exit(-1);
	}
	
	//监听,将主动连接变成被动连接,准备接受客户端的连接请求。
	ret = listen(sockfd,30);//30表示在一瞬间内只允许30个客户端连接,超过30的则掉线.
	if (ret){
		perror("listen error");
		exit(-1);
	}
	
	//阻塞等待客户端的连接,等待并接收客户端的连接请求,建好TCP连接后,accept函数会返回一个新的已连接套接字
	struct sockaddr_in client;
	int len = sizeof(client);
	int fd = accept(sockfd,(struct sockaddr *)&client,&len);
	//参数1:套接字。参数二:客户端的数据(IP和端口PORT)。参数三:地址长度,指针类型。
	if (!fd){
		perror("accept error");
		exit(-1);
	}
	printf("%d号客户端上线了\n",fd);
	printf("客户端IP:%s\n",inet_ntoa(client.sin_addr));
	printf("客户端PORT:%hu\n",ntohs(client.sin_port));
	
	char buf[100];
	int n = 10;
	while(1){
		bzero(buf,sizeof(buf));
		ret = read(fd,buf,sizeof(buf));//文件IO读取数据
		if (ret > 0){
		printf("客户端<ip:%s>说:%s\n",inet_ntoa(client.sin_addr),buf);
		n = 10;
		}else if (ret == 0){
			printf("客户端掉线了\n");
			close(fd);
			break;
		}else{
			while(n--)
				continue;
			close(fd);
			break;
		}
	}
	
	close(fd);
	close(sockfd);
	
}

client 的创建

#include <stdio.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(int argc,char *argv[])
{
	if(argc!=3){
		printf("usage:%s <ip> <port>\n",argv[0]);
		exit(-1);
	}
	
	//创建socket套接字 
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd<0){
		perror("socket error");
		exit(-1);
	}
	
	//连接服务器	
	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_port = htons(atoi(argv[2]));
	server.sin_addr.s_addr = inet_addr(argv[1]);;
	int ret = connect(sockfd,(struct sockaddr *)&server,sizeof(server));
	if(ret){
		perror("connect error");
		exit(-1);
	}
	
	char buf[100];
	while(1){
		bzero(buf,sizeof(buf));
		scanf("%s",buf);
		write(sockfd,buf,strlen(buf));
	}
	
	close(sockfd);

	
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值