网络编程_2(网络属性+UDP(UDP模型+广播组播))

三、网络属性

一二章请点击:网络编程_1(网络基础+跨主机传输)

1.getsockop 和 setsockopt

功能:获取和设置网络属性;
头文件:
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
原型:
       int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
       int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
参数:
    int sockfd:要设置/获取属性的套接字
    int level:指定要控制套接字的层次;
    		1) SOL_SOCKET:通用套接字选项
    		2) IPPROTO_IP: ip选项
    		3) IPPROTO_TCP:TCP选项。
    int optname:指定控制方式,看下图。
    void *optval:获取或者设置套接字选项的变量地址,根据指定控制方式的数据类型进行转换。
    socklen_t *optlen:第四个 void *optval指向的参数的大小;   
返回值:
    成功,返回0;
	失败,返回-1, 更新errno; 

在这里插入图片描述

例子

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
int main(int argc, const char *argv[])
{
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd < 0)
    {
        perror("socket");
        return -1;
    }
    int flag = 0;
    int len = sizeof(flag);
    //获取接收缓冲区的大小
    if(getsockopt(sfd, SOL_SOCKET, SO_RCVBUF, &flag, &len)<0)
    {
        perror("getsockopt");
        return -1;
    }
    printf("%d\n", flag);
    //发送缓冲区
    if(getsockopt(sfd, SOL_SOCKET, SO_SNDBUF, &flag, &len) < 0)
    {
        perror("getsockopt");
        return -1;
    }
    printf("%d\n", flag);
    //是否允许快速重用本地端口 0:不允许, 1:允许
    getsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &flag, &len);
    printf("%d\n", flag);
    //设置本地端口快速重用
    int reuse = 1;
    int r_len = sizeof(reuse);
    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, r_len);
    getsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &flag, &len);
    printf("%d\n", flag);
    //获取接收超时时间
    struct timeval opttime;
    int tlen = sizeof(opttime);c
    getsockopt(sfd, SOL_SOCKET, SO_RCVTIMEO, &opttime, &tlen);
    printf("%lds%ldms\n", opttime.tv_sec, opttime.tv_usec);
    return 0;
}

四、UDP

某一层一个协议可能对应多个不同协议,这样写代码很麻烦,换一个协议就得改很多那我们可使用一个机制socket,把协议族指定起来,在Linux种一切皆文件的设计理念种,网络也是文件,网络之间的通讯也可以向操作文件一样对他进行读写。
在这里插入图片描述

(一)、UDP模型

在这里插入图片描述

1、socket

用来获取网络操作的文件描述符,就像open函数一样

头文件:
	#include <sys/types.h>
	#include <sys/socket.h>
原型:
	int socket(int domain, int type, int protocol);
参数:
    int domain:协议族
        AF_UNIX / AF_LOCAL 本地协议族 //在UNIX通信中用到
        AF_INET IPv4协议族
        AF_INET6 IPv6协议族
	int type:套接字的类型
    	SOCK_STREAM:流式套接字,对应着TCP
    		特点有序,可靠、双工、基于连接的,以字节流位单位。
    		可靠不是指不丢包,是保证只要你能接收到这个包,
    		那么这个包的数据完整性一定正确的。
    		双工:同时收发。
    		字节流:数据没有明显的界限,一端数据可以分为任意多个包发送。
		SOCK_DGRAM:报式套接字,对应着UDP
			无连接,固定最大长度,不可靠消息。
			就像写信,无法保证发出的信对方一定能收到,而且不能保证内容不会被篡改
             不能保证哪封信先到,大家都能收到这个包,
             但是发现不是自己的之后就丢弃。
             发现是自己的包再处理。
             由严格的数据分界
	int protocol:
    	参数通常填0,默认协议。
返回值:
    成功返回代表当前网络连接的文件描述符(套接字)
    失败-1设置errno

2、bind(2)

功能:绑定ip和端口到套接字;
头文件:
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
原型:
       int bind(int sockfd, const struct sockaddr *addr,  socklen_t addrlen);
参数:
    int sockfd:socket创建的套接字;
	struct sockaddr *addr:通用结构体。一般不采用这个结构体,不同的协议族采用不同的结构体,然后强转传入函数中。
        				存储要绑定到套接字上的IP和端口的地址结构体
        				需要采用IPV4的地址结构体,使用的时候强制转换。
       man 7 ip
       #include <sys/socket.h>
       #include <netinet/in.h>
       #include <netinet/ip.h> /* superset of previous */
  		  struct sockaddr_in {
              //通用结构体,一般不写,为了不同格式的结构体强制转换成它传入函数
               sa_family_t    sin_family; /* address family: AF_INET */
               in_port_t      sin_port;   /* port in network byte order */ 端口的网络字节序(1024~49151)
              //如果是数字6666,则需要用函数htons()转化为网络字节序
              //如果是命令行传参,则需要用htons(atoi(argv[1]))转换
               struct in_addr sin_addr;   /* internet address */ 		ip的网络字节序
           };
           /* Internet address. */
           struct in_addr {
               uint32_t       s_addr;     /* address in network byte order */
           };
	socklen_t addrlen:地址结构体的大小,sizeof(struct sockaddr_in);
	
返回值:
    成功,返回0;
	失败,返回-1,更新errno;

3、recvfrom(2)

功能:接收客户端的数据,并获取客户端的ip和端口;
头文件:
       #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);
参数:
    int sockfd:套接字;
	void *buf:存储接收到的数据;
	size_t len:要存储的长度;
	int flags:指定要接收的方式,填0,阻塞方式接收;
	struct sockaddr *src_addr:通用结构体,存储发送端的ip及端口号。如果不想接收,填NULL;
		 ipv4结构体
		 struct sockaddr_in {
               sa_family_t    sin_family; /* address family: AF_INET */
               in_port_t      sin_port;   /* port in network byte order */ 端口的网络字节序(1024~49151)
               struct in_addr sin_addr;   /* internet address */ 		ip的网络字节序
           };
           /* Internet address. */
           struct in_addr {
               uint32_t       s_addr;     /* address in network byte order */
           };
	socklen_t *addrlen:地址结构体的大小。如果不想接收,可以填NULL;    
返回值:
    >0 返回接收的字节数;
	=0 连接终止,只适用于TCP;
	<0 错误,更新errNo;

4、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);
参数:
    int sockfd:套接字;
	const void *buf:发送的数据;
	size_t len:发送数据的大小;
	int flags:指定发送方式,填0,阻塞方式发送。
         	   如果没有数据发送,则将阻塞在该函数上.
    struct sockaddr *dest_addr:存储目标的ip和端口号信息
    socklen_t addrlen:地址结构体的大小,sizeof(struct sockaddr_in);
返回值:
    成功,返回发送的字节数;
	失败,返回-1,更新errno;

例子

服务器
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "参数错误 ip port");
        return -1;
    }
    int r_port = atoi(argv[2]);
    if(r_port < 1024 || r_port > 49151)
    {
        fprintf(stderr, "非法端口号");
        return -1;
    }
    printf("%d\n", r_port);
    //1.创建套接字
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd < 0)
    {
        perror("socket");
        return -1;
    }
    printf("创建套接字成功 %d\n", sfd);
    //2.绑定端口和ip 
    struct sockaddr_in sin;
    sin.sin_family  = AF_INET;
    sin.sin_port    = htons(r_port);    //端口:1024~49151
  //sin.sin_addr.s_addr = inet_addr("0.0.0.0"); //泛指本机,也可以用INADDR_ANY,本质是0的宏
    sin.sin_addr.s_addr = inet_addr(argv[1]);  "192.168.1.111"
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
    {
        perror("bind");
        close(sfd);
        return -1;
    }
    printf("绑定成功\n");
    char buf[200] = "";
    //要接收客户端的IP和端口号,所以要定义一个客户端的结构体
    struct sockaddr_in cin;
    socklen_t size = sizeof(cin);
    int port = 0;
    char ip[20] = "";                             
    while(1)
    {
        //接收
        bzero(buf, sizeof(buf));
        if(recvfrom(sfd, buf, 200, 0, \
                (struct sockaddr*)&cin, &size)<=0)
        {
            perror("recvfrom");
            return -1;
        }
        //端口号
        port = 0;
        port = ntohs(cin.sin_port); //把接收到的客户端的网络字节序的端口号转化为本地字节序端口号
        //IP
        bzero(ip, 20);
        //把接收到的客户端的网络字节序的IP转化为本地字节序IP
        inet_ntop(AF_INET, &cin.sin_addr, ip, 20);
        printf("[%s:%d]:%s\n", ip, port, buf);
    /*  
        bzero(buf, 200);
        fgets(buf, 200-1, stdin);
        //发送
        if(sendto(sfd, buf, strlen(buf), 0,\
                (struct sockaddr*)&cin, sizeof(cin)) < 0)
        {
            perror("sendto");
            return -1;
        }
        printf("发送成功\n");c
        */
    }
    close(sfd);
    return 0;
}
客户端
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/types.h>
#include <string.h>
int main(int argc, const char *argv[])
{
    //1.创建套接字
    int cfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(cfd < 0)
    {
        perror("socket");
        return -1;
    }
    //bind 非必须
    //填充服务器的信息
    struct sockaddr_in sin;
    sin.sin_family      = AF_INET;
    sin.sin_port        = htons(2021);  //服务器的端口号
    sin.sin_addr.s_addr = inet_addr("192.168.1.109");
//  sin.sin_addr.s_addr = inet_addr("0");
    printf("客户端启动成功\n");
    char buf[200] = "";
    while(1)
    {
        //从终端获取
        bzero(buf, 200);
        fprintf(stderr, "请输入:");
        fgets(buf, 200-1, stdin)if(sendto(cfd, buf, strlen(buf), 0, \
                (struct sockaddr*)&sin, sizeof(sin))<0)
        {
            perror("sento");
            close(cfd);
            return -1;
        }
        if(strncasecmp(buf, "quit", 4) == 0)
        {
            break;
        }
        /*
        //接收
        bzero(buf, sizeof(buf));
        if(recvfrom(cfd, buf, 200, 0, NULL,NULL)<=0)
        {   
            perror("recvfrom");
            return -1; 
        }   
        printf("%s\n", buf);
        */
    }
    close(cfd);
    return 0;
}

(二)多点通讯

1)广播

1.概念

1)如果同时给同一网段下的所有主机发送数据称之广播
2)只有UDP(报式套接字)才能广播
3)广播地址(主机号全是1)
​ 例子:192.168.1.0那么他的广播地址是 192.168.1.255;
​ 255.255.255.255 给所有网段中的所有主机发送广播

2.广播的发送流程(客户端)

1)创建报式套接字 (socket)
2)可选择绑定也可以不绑定(bind)
3)设置网络属性:允许广播,如果不设置,默认是不允许的(setsockopt)
4)接收端(服务器端)指定为广播地址、同时指定端口号 (填充服务器的信息)
5)发送数据 (sendto)

3.广播的接收流程(服务器)

1)创建报式套接字(socket)
2)绑定IP地址(广播IP地址(例如:192.168.1.255 或者 0.0.0.0(INADDR_ANY)))和端口号。(bind)
​注意:绑定的端口号必须和客户端填充的端口号的一样;
3)等待接收数据(recvfrom)

例子
发送端(客户端)
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <string.h>
#include <stdlib.h>
#define PORT 2020
#define IP  "192.168.1.255"
int main(int argc, const char *argv[])
{
    //1.socket        
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd < 0)
    {
        perror("socket");
        return -1;
    }
    //2.bind可选
    //3.允许广播
    int broadcast = 1;
    if(setsockopt(fd, SOL_SOCKET, SO_BROADCAST, \
                &broadcast, sizeof(int))<0)
    {
        perror("setsockopt");
        return -1;
    }
    //4.填充服务器信息
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(PORT);         //该端口号是服务器的端口号
    sin.sin_addr.s_addr = inet_addr(IP); //填充广播地址,主机号全为1
    char buf[BUFSIZ] = "";
    while(1)
    {
        //5.发送  
        bzero(buf, sizeof(buf));
        fprintf(stderr, "请输入:");
        fgets(buf, BUFSIZ-1, stdin);
        if(sendto(fd, &buf, strlen(buf), 0, \
                    (struct sockaddr*)&sin, sizeof(sin))<0)
        {
            perror("sendto");
            return -1;
        }
        if(strncasecmp(buf, "exit", 4) == 0)
        {
            break;
        }
        sleep(1);
    }
    close(fd);
    return 0;
}
接收端(服务器端)
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <string.h>
#include <stdlib.h>
#define PORT 2020
#define IP  "192.168.1.255"
int main(int argc, const char *argv[])
{
    //1.创建套接字
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd < 0)
    {
        perror("socket");
        return -1;
    }
    printf("创建套接字成功\n");
    //允许快速重用本地端口
    int reuse = 1;
    if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int))<0)
    {
        perror("setsockopt");
        return -1;
    }
    //绑定服务器的ip和端口
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(PORT);
    sin.sin_addr.s_addr = inet_addr(IP);
    //2.绑定 
    if(bind(fd, (struct sockaddr*)&sin, sizeof(sin))<0)
    {
        perror("bind");
        exit(1);
    }
    //定义发送方的结构体
    struct sockaddr_in cin;
    socklen_t len = sizeof(cin);
    char buf[BUFSIZ] = "";
    char ip[20] = "";
    while(1)
    {
        //接收
        bzero(buf, sizeof(buf));
        if(recvfrom(fd, buf, BUFSIZ, 0,(struct sockaddr*)&cin, &len)<0)
        {
            perror("recvfrom");
            exit(1);
        }
        //提取ip
        bzero(ip, sizeof(ip));
        inet_ntop(AF_INET, &cin.sin_addr, ip, 20);
        printf("[%s:%d]:%s\n", ip, ntohs(cin.sin_port), buf); //注意要转换为本地字节序
        if(strncasecmp(buf, "exit",4) == 0)
        {
            break;
        }
    }
    close(fd);
    return 0;
}

拓展:

//填充信息
  struct msg_st sbuf;
  memset(&sbuf,'\0',sizeof(sbuf));
  strcpy(sbuf.name,"zhangsan");
  srand(time(NULL));//设置随机数种子,
  //这样每次执行的时候 rand产生的值都不一样。
  sbuf.math =    htonl(rand()%100);
  sbuf.chinese = htonl(rand()%100);  
//填充接收端地址信息
  struct sockaddr_in raddr;
  raddr.sin_family =  AF_INET;
  raddr.sin_port   =  htons(RCVPORT);
  if( inet_pton(AF_INET,"255.255.255.255",&raddr.sin_addr.s_addr) !=1) {c
    perror("转换失败");
    exit(6);
  }

2)组播

1. 概念

​ 1)广播方式是发送给同一网段下的所有主机,过多的广播会占用大量网络带宽,会造成广播风暴,影响正常通讯;
​ 2)组播(多播),加入到多播组中的主机才可以收发消息;
​ 3)多播方式既可以给多个主机发送,又能避免造成过多负载。
​ 4)每台主机要到传输层才能判断广播是否要处理
​ 5)组播地址:D类 IP地址:224.0.0.0~239.255.255.255

2.组播的发送流程(客户端)

​ 1)创建报式套接字(socket)
​ 2)接收方(服务器)的地址指定为组播地址和端口号
​ 3)发送数据

3. 组播的接收流程(服务器)

​ 1)创建报式套接字(socket)
​ 2)加入多播组
​ 3)绑定组播IP(例如:224.1.2.3 或者 0.0.0.0)地址和端口
​ 注意:绑定的端口号必须和发送方指定的端口号一致
​ 4)等待接收数据;

4. 组播实现

组播是在IPPROTO_IP man 7 IP

功能:获取和设置网络属性;
头文件:
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
原型:
       int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
参数:
    int sockfd:要设置/获取属性的套接字
    int level:指定要控制套接字的层次;
    		2) IPPROTO_IP: ip选项
    int optname:指定控制方式,
    	 IP_MULTICAST_IF: 	创建多播组(这部可以省略)
         IP_ADD_MEMBERSHIP:	加入多播组
		IP_DROP_MEMBERSHIP:	脱离多播组
    void *optval:获取或者设置套接字选项的变量地址,根据指定控制方式的数据类型进行转换。
		struct ip_mreqn
         {
            struct in_addr imr_multiaddr;   多播组的ip,网络字节序 224.1.2.3-->网络字节序
            struct in_addr imr_address;     本机IP地址,网络字节序 0.0.0.0192.168.1.105-->网络字节序
            int imr_ifindex;            	当前使用的网络设备索引号。
              /*1.使用 $ ip ad命令查看网络设备索引号*/
              /*2.通过函数的方式获取:*/
                if_nametoindex(网卡的名字) 	//通过ifconfig获取网卡的名字
                例子: imr_ifindex = if_nametoindex("eth0");
              /*3.填0:默认索引号*/
          }; 
    socklen_t optlen:第四个 void *optval指向的参数的大小; sizeof(struct ip_mreqn)  
返回值:
    成功,返回0;
	失败,返回-1, 更新errno; 
例子
发送端(客户端)
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#define PORT 2020
#define GROUP "224.1.2.3"
struct msg
{
    int num;
    char buf[128];
}__attribute((packed))__;
int main(int argc, const char *argv[])
{
    //1)创建报式套接字(socket)
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd < 0)
    {
        perror("socket");
        return -1;
    }
    //2)创建多播组(省)
    //3)接收收方(服务器)的地址指定为组播地址和端口号
    //填充服务器的ip和端口
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(PORT);
    sin.sin_addr.s_addr = inet_addr(GROUP);
    printf("填充完毕\n");
    struct msg snd;
    int num = 0;
    snd.num = 0;
    while(1)
    {
        bzero(snd.buf, sizeof(snd.buf));
        //4)发送数据
        fprintf(stderr, "请输入:");
        fgets(snd.buf, sizeof(snd.buf), stdin);
        //int类型转网络字节序
        num++;
        snd.num = htonl(num);
        if(sendto(fd, &snd, sizeof(snd), 0, (struct sockaddr*)&sin,\
                sizeof(sin))<0)
        {
            perror("sendto");
            exit(1);
        }
        printf("发送成功\n");
    }
    close(fd);
    return 0;
}
接收端(服务器)
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#define PORT 2020
#define GROUP "224.1.2.3"
struct msg
{
    int num;
    char buf[128];
}__attribute((packed))__;
int main(int argc, const char *argv[])
{
    //1)创建报式套接字(socket)
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd < 0)
    {
        perror("socket");
        return -1;
    }
    //2)加入多播组
    struct ip_mreqn mreq;                                                                             
    inet_pton(AF_INET, GROUP, &mreq.imr_multiaddr); //填充多播组的ip,网络字节序
    inet_aton("0.0.0.0", &mreq.imr_address);    //本机ip
    mreq.imr_ifindex = 0;       //网络设备索引号
//  mreq.imr_ifindex = 2;       //终端输入ip ad查找
//  mreq.imr_ifindex = if_nametoindex("eth0"); //用函数获取设备所以号
    if(setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq))<0)
    {
        perror("setsockopt");
        exit(1);
    }
    printf("加入多播组成功\n");
    //允许本地端口快速重用
    int reuse = 1;
    if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int))<0)
    {
        perror("setsockopt");
        exit(1);
    }
    //3)绑定组播IP(例:224.1.2.3 或者 0.0.0.0)地址和端口
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(PORT);
    sin.sin_addr.s_addr = inet_addr(GROUP);     //注意该位置填的是组播的ip或者0.0.0.0
//  sin.sin_addr.s_addr = inet_addr("0.0.0.0");
    if(bind(fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
    {
        perror("bind");
        exit(1);
    }
    struct sockaddr_in cin;
    socklen_t len = sizeof(cin);
    struct msg rcv;
    char ip[20] = "";
    //4)等待接收数据;
    while(1)
    {
        bzero(rcv.buf, sizeof(rcv.buf));
        if(recvfrom(fd, &rcv, sizeof(rcv), 0, (struct sockaddr*)&cin, &len)<0)
        {
            perror("recvfrom");
            exit(1);
        }
        //ip转换
        bzero(ip, 20);
        inet_ntop(AF_INET, &cin.sin_addr, ip ,20);
        //端口转换
        int port = ntohs(cin.sin_port);
        //结构体中的num转换
        int num = ntohl(rcv.num);
        printf("[%s:%d]:num=%d,%s\n", ip, port, num, rcv.buf);
    }
    close(fd);
    return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值