linux 网络编程:epoll 的实例

184 篇文章 2 订阅

http://blog.csdn.net/shanshanpt/article/details/7383400

在前面已经经过了PPC、TPC、select之类( TPC就是使用进程处理data,TPC就是使用线程处理 ),前面两个的缺点大家应该都是知道的是吧,对于select( 其实poll和他差不多 ),缺点是能同时连接的fd是在是不多,在linux中一般是1024/2048,对于很大的服务器来说是不够的!当然我们可以自己修改其值!但是效率上就会下降!

       对于改进poll的epoll来说:支持一个进程打开大数目的socket描述符,也就是说与本机的内存是有关系的!( 一般服务器的都是很大的! )

       下面是我的小PC机上的显示:

       pt@ubuntu:~$ cat /proc/sys/fs/file-max
       391658
       达到了391658个,那么对于服务器而言,显然,嘿嘿嘿~~~

      epoll的基础知识吧大家在网上到处都能找到,不就是epoll_creat 、epoll_ctl、epoll_wait 3函数!大家自己搜去,我也是在学习。。。

      此处主要是贴上自己的测试的一些垃圾代码,与大家共勉!呵呵呵~

     哦,忘了要注意一下:

     epoll_ctl epoll的事件注册函数,其events参数可以是以下宏的集合:
     EPOLLIN:    表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
     EPOLLOUT:   表示对应的文件描述符可以写;
     EPOLLPRI:   表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
     EPOLLERR:   表示对应的文件描述符发生错误;写已关闭socket pipe broken
     EPOLLHUP:   表示对应的文件描述符被挂断;譬如收到RST包。在注册事件的时候这个事件是默认添加。 
     EPOLLRDHUP: 表示对应的文件描述符对端socket关闭事件,主要应用于ET模式下。
    在水平触发模式下,如果对端socket关闭,则会一直触发epollin事件,驱动去处理client socket。
    在边沿触发模式下,如果client首先发送协议然后shutdown写端。则会触发epollin事件。但是如果处理程序只进行一次recv操作时,根据recv收取到得数据长度来判读后边是 

    否还有需要处理的协议时,将丢失客户端关闭事件。
     EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
    EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里


server端:


  1. #include <stdio.h>   
  2. #include <unistd.h>   
  3. #include <stdlib.h>   
  4. #include <string.h>   
  5. #include <sys/types.h>   
  6. #include <errno.h>   
  7. #include <sys/socket.h>   
  8. #include <netinet/in.h>           /* socket类定义需要*/   
  9. #include <sys/epoll.h>            /* epoll头文件 */   
  10. #include <fcntl.h>                    /* nonblocking需要 */   
  11. #include <sys/resource.h>     /* 设置最大的连接数需要setrlimit */   
  12.   
  13. #define MAXEPOLL    10000   /* 对于服务器来说,这个值可以很大的! */   
  14. #define MAXLINE     1024   
  15. #define     PORT            6000   
  16. #define MAXBACK 1000   
  17.   
  18. //!> 设置非阻塞   
  19. //!>    
  20. int setnonblocking( int fd )  
  21. {  
  22.     if( fcntl( fd, F_SETFL, fcntl( fd, F_GETFD, 0 )|O_NONBLOCK ) == -1 )  
  23.     {  
  24.         printf("Set blocking error : %d\n", errno);  
  25.         return -1;  
  26.     }  
  27.     return 0;  
  28. }  
  29.   
  30. int main( int argc, char ** argv )  
  31. {  
  32.     int         listen_fd;  
  33.     int         conn_fd;  
  34.     int         epoll_fd;  
  35.     int         nread;  
  36.     int         cur_fds;                //!> 当前已经存在的数量   
  37.     int         wait_fds;               //!> epoll_wait 的返回值   
  38.     int     i;  
  39.     struct sockaddr_in servaddr;  
  40.     struct sockaddr_in cliaddr;  
  41.     struct  epoll_event ev;  
  42.     struct  epoll_event evs[MAXEPOLL];  
  43.     struct  rlimit  rlt;        //!> 设置连接数所需   
  44.     char    buf[MAXLINE];  
  45.     socklen_t   len = sizeofstruct sockaddr_in );  
  46.   
  47.     //!> 设置每个进程允许打开的最大文件数   
  48.     //!> 每个主机是不一样的哦,一般服务器应该很大吧!   
  49.     //!>    
  50.     rlt.rlim_max = rlt.rlim_cur = MAXEPOLL;  
  51.     if( setrlimit( RLIMIT_NOFILE, &rlt ) == -1 )      
  52.     {  
  53.         printf("Setrlimit Error : %d\n", errno);  
  54.         exit( EXIT_FAILURE );  
  55.     }  
  56.       
  57.     //!> server 套接口   
  58.     //!>    
  59.     bzero( &servaddr, sizeof( servaddr ) );  
  60.     servaddr.sin_family = AF_INET;  
  61.     servaddr.sin_addr.s_addr = htonl( INADDR_ANY );  
  62.     servaddr.sin_port = htons( PORT );  
  63.       
  64.     //!> 建立套接字   
  65.     if( ( listen_fd = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 )  
  66.     {  
  67.         printf("Socket Error...\n" , errno );  
  68.         exit( EXIT_FAILURE );  
  69.     }  
  70.       
  71.     //!> 设置非阻塞模式   
  72.     //!>    
  73.     if( setnonblocking( listen_fd ) == -1 )  
  74.     {  
  75.         printf("Setnonblocking Error : %d\n", errno);  
  76.         exit( EXIT_FAILURE );  
  77.     }  
  78.       
  79.     //!> 绑定   
  80.     //!>   
  81.     if( bind( listen_fd, ( struct sockaddr *)&servaddr, sizeofstruct sockaddr ) ) == -1 )  
  82.     {  
  83.         printf("Bind Error : %d\n", errno);  
  84.         exit( EXIT_FAILURE );  
  85.     }  
  86.   
  87.     //!> 监听   
  88.     //!>    
  89.     if( listen( listen_fd, MAXBACK ) == -1 )  
  90.     {  
  91.         printf("Listen Error : %d\n", errno);  
  92.         exit( EXIT_FAILURE );  
  93.     }  
  94.       
  95.     //!> 创建epoll   
  96.     //!>    
  97.     epoll_fd = epoll_create( MAXEPOLL );    //!> create   
  98.     ev.events = EPOLLIN | EPOLLET;      //!> accept Read!   
  99.     ev.data.fd = listen_fd;                 //!> 将listen_fd 加入   
  100.     if( epoll_ctl( epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev ) < 0 )  
  101.     {  
  102.         printf("Epoll Error : %d\n", errno);  
  103.         exit( EXIT_FAILURE );  
  104.     }  
  105.     cur_fds = 1;  
  106.       
  107.     while( 1 )  
  108.     {  
  109.         if( ( wait_fds = epoll_wait( epoll_fd, evs, cur_fds, -1 ) ) == -1 )  
  110.         {  
  111.             printf( "Epoll Wait Error : %d\n", errno );  
  112.             exit( EXIT_FAILURE );  
  113.         }  
  114.   
  115.         for( i = 0; i < wait_fds; i++ )  
  116.         {  
  117.             if( evs[i].data.fd == listen_fd && cur_fds < MAXEPOLL )    
  118.                                                     //!> if是监听端口有事   
  119.             {  
  120.                 if( ( conn_fd = accept( listen_fd, (struct sockaddr *)&cliaddr, &len ) ) == -1 )  
  121.                 {  
  122.                     printf("Accept Error : %d\n", errno);  
  123.                     exit( EXIT_FAILURE );  
  124.                 }  
  125.                   
  126.                 printf( "Server get from client !\n"/*,  inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port */);  
  127.                   
  128.                 ev.events = EPOLLIN | EPOLLET;      //!> accept Read!   
  129.                 ev.data.fd = conn_fd;                   //!> 将conn_fd 加入   
  130.                 if( epoll_ctl( epoll_fd, EPOLL_CTL_ADD, conn_fd, &ev ) < 0 )  
  131.                 {  
  132.                     printf("Epoll Error : %d\n", errno);  
  133.                     exit( EXIT_FAILURE );  
  134.                 }  
  135.                 ++cur_fds;   
  136.                 continue;         
  137.             }  
  138.               
  139.             //!> 下面处理数据   
  140.             //!>    
  141.             nread = read( evs[i].data.fd, buf, sizeof( buf ) );  
  142.             if( nread <= 0 )                     //!> 结束后者出错   
  143.             {  
  144.                 close( evs[i].data.fd );  
  145.                 epoll_ctl( epoll_fd, EPOLL_CTL_DEL, evs[i].data.fd, &ev );  //!> 删除计入的fd   
  146.                 --cur_fds;                  //!> 减少一个呗!   
  147.                 continue;  
  148.             }  
  149.               
  150.             write( evs[i].data.fd, buf, nread );            //!> 回写   
  151.               
  152.         }  
  153.     }  
  154.       
  155.     close( listen_fd );  
  156.     return 0;  
  157. }  
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>			/* socket类定义需要*/
#include <sys/epoll.h>			/* epoll头文件 */
#include <fcntl.h>	                /* nonblocking需要 */
#include <sys/resource.h>		/* 设置最大的连接数需要setrlimit */

#define	MAXEPOLL	10000	/* 对于服务器来说,这个值可以很大的! */
#define	MAXLINE		1024
#define 	PORT			6000
#define	MAXBACK	1000

//!> 设置非阻塞
//!> 
int setnonblocking( int fd )
{
	if( fcntl( fd, F_SETFL, fcntl( fd, F_GETFD, 0 )|O_NONBLOCK ) == -1 )
	{
		printf("Set blocking error : %d\n", errno);
		return -1;
	}
	return 0;
}

int main( int argc, char ** argv )
{
	int 		listen_fd;
	int 		conn_fd;
	int 		epoll_fd;
	int 		nread;
	int 		cur_fds;				//!> 当前已经存在的数量
	int 		wait_fds;				//!> epoll_wait 的返回值
	int		i;
	struct sockaddr_in servaddr;
	struct sockaddr_in cliaddr;
	struct 	epoll_event	ev;
	struct 	epoll_event	evs[MAXEPOLL];
	struct 	rlimit	rlt;		//!> 设置连接数所需
	char 	buf[MAXLINE];
	socklen_t	len = sizeof( struct sockaddr_in );

	//!> 设置每个进程允许打开的最大文件数
	//!> 每个主机是不一样的哦,一般服务器应该很大吧!
	//!> 
	rlt.rlim_max = rlt.rlim_cur = MAXEPOLL;
	if( setrlimit( RLIMIT_NOFILE, &rlt ) == -1 )	
	{
		printf("Setrlimit Error : %d\n", errno);
		exit( EXIT_FAILURE );
	}
	
	//!> server 套接口
	//!> 
	bzero( &servaddr, sizeof( servaddr ) );
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl( INADDR_ANY );
	servaddr.sin_port = htons( PORT );
	
	//!> 建立套接字
	if( ( listen_fd = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 )
	{
		printf("Socket Error...\n" , errno );
		exit( EXIT_FAILURE );
	}
	
	//!> 设置非阻塞模式
	//!> 
	if( setnonblocking( listen_fd ) == -1 )
	{
		printf("Setnonblocking Error : %d\n", errno);
		exit( EXIT_FAILURE );
	}
	
	//!> 绑定
	//!>
	if( bind( listen_fd, ( struct sockaddr *)&servaddr, sizeof( struct sockaddr ) ) == -1 )
	{
		printf("Bind Error : %d\n", errno);
		exit( EXIT_FAILURE );
	}

	//!> 监听
	//!> 
	if( listen( listen_fd, MAXBACK ) == -1 )
	{
		printf("Listen Error : %d\n", errno);
		exit( EXIT_FAILURE );
	}
	
	//!> 创建epoll
	//!> 
	epoll_fd = epoll_create( MAXEPOLL );	//!> create
	ev.events = EPOLLIN | EPOLLET;		//!> accept Read!
	ev.data.fd = listen_fd;					//!> 将listen_fd 加入
	if( epoll_ctl( epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev ) < 0 )
	{
		printf("Epoll Error : %d\n", errno);
		exit( EXIT_FAILURE );
	}
	cur_fds = 1;
	
	while( 1 )
	{
		if( ( wait_fds = epoll_wait( epoll_fd, evs, cur_fds, -1 ) ) == -1 )
		{
			printf( "Epoll Wait Error : %d\n", errno );
			exit( EXIT_FAILURE );
		}

		for( i = 0; i < wait_fds; i++ )
		{
			if( evs[i].data.fd == listen_fd && cur_fds < MAXEPOLL )	
													//!> if是监听端口有事
			{
				if( ( conn_fd = accept( listen_fd, (struct sockaddr *)&cliaddr, &len ) ) == -1 )
				{
					printf("Accept Error : %d\n", errno);
					exit( EXIT_FAILURE );
				}
				
				printf( "Server get from client !\n"/*,  inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port */);
				
				ev.events = EPOLLIN | EPOLLET;		//!> accept Read!
				ev.data.fd = conn_fd;					//!> 将conn_fd 加入
				if( epoll_ctl( epoll_fd, EPOLL_CTL_ADD, conn_fd, &ev ) < 0 )
				{
					printf("Epoll Error : %d\n", errno);
					exit( EXIT_FAILURE );
				}
				++cur_fds; 
				continue;		
			}
			
			//!> 下面处理数据
			//!> 
			nread = read( evs[i].data.fd, buf, sizeof( buf ) );
			if( nread <= 0 )						//!> 结束后者出错
			{
				close( evs[i].data.fd );
				epoll_ctl( epoll_fd, EPOLL_CTL_DEL, evs[i].data.fd, &ev );	//!> 删除计入的fd
				--cur_fds;					//!> 减少一个呗!
				continue;
			}
			
			write( evs[i].data.fd, buf, nread );			//!> 回写
			
		}
	}
	
	close( listen_fd );
	return 0;
}





对于client:

由于本人比较懒,所以就使用上一次的select的client吧,一样的,呵呵:


  1. #include <stdio.h>   
  2. #include <unistd.h>   
  3. #include <stdlib.h>   
  4. #include <string.h>   
  5. #include <errno.h>   
  6. #include <netinet/in.h>   
  7. #include <sys/types.h>   
  8. #include <sys/socket.h>   
  9. #include  <arpa/inet.h>   
  10. #include <sys/select.h>   
  11.   
  12. #define MAXLINE 1024   
  13. #define SERV_PORT 6000   
  14.   
  15. //!> 注意输入是由stdin,接受是由server发送过来   
  16. //!> 所以在client端也是需要select进行处理的   
  17. void send_and_recv( int connfd )  
  18. {  
  19.     FILE * fp = stdin;  
  20.     int   lens;  
  21.     char send[MAXLINE];  
  22.     char recv[MAXLINE];  
  23.     fd_set rset;  
  24.     FD_ZERO( &rset );  
  25.     int maxfd = ( fileno( fp ) > connfd ? fileno( fp ) : connfd  + 1 );    
  26.                                     //!> 输入和输出的最大值   
  27.     int n;  
  28.       
  29.     while( 1 )  
  30.     {  
  31.         FD_SET( fileno( fp ), &rset );  
  32.         FD_SET( connfd, &rset );            //!> 注意不要把rset看作是简单的一个变量   
  33.                                 //!> 注意它其实是可以包含一组套接字的哦,   
  34.                                 //!> 相当于是封装的数组!每次都要是新的哦!   
  35.           
  36.         if( select( maxfd, &rset, NULL, NULL, NULL ) == -1 )  
  37.         {  
  38.             printf("Client Select Error..\n");  
  39.             exit(EXIT_FAILURE  );  
  40.         }  
  41.           
  42.         //!> if 连接口有信息   
  43.         if( FD_ISSET( connfd, &rset ) ) //!> if 连接端口有信息   
  44.         {  
  45.             printf( "client get from server ...\n" );  
  46.             memset( recv, 0, sizeof( recv ) );  
  47.             n = read( connfd, recv, MAXLINE );  
  48.             if( n == 0 )  
  49.             {  
  50.                 printf("Recv ok...\n");  
  51.                 break;  
  52.             }  
  53.             else if( n == -1 )  
  54.             {  
  55.                 printf("Recv error...\n");  
  56.                 break;  
  57.             }  
  58.             else  
  59.             {  
  60.                 lens = strlen( recv );  
  61.                 recv[lens] = '\0';  
  62.                 //!> 写到stdout   
  63.                 write( STDOUT_FILENO, recv, MAXLINE );  
  64.                 printf("\n");  
  65.             }  
  66.   
  67.         }  
  68.           
  69.         //!> if 有stdin输入   
  70.         if( FD_ISSET( fileno( fp ), &rset ) )   //!> if 有输入   
  71.         {  
  72.             //!> printf("client stdin ...\n");   
  73.               
  74.             memset( send, 0, sizeof( send ) );  
  75.             if( fgets( send, MAXLINE, fp ) == NULL )  
  76.             {  
  77.                 printf("End...\n");  
  78.                 exit( EXIT_FAILURE );  
  79.             }  
  80.             else  
  81.             {  
  82.                 //!>if( str )   
  83.                 lens = strlen( send );  
  84.                 send[lens-1] = '\0';        //!> 减一的原因是不要回车字符   
  85.                                 //!> 经验值:这一步非常重要的哦!!!!!!!!   
  86.                 if( strcmp( send, "q" ) == 0 )  
  87.                 {  
  88.                     printf( "Bye..\n" );  
  89.                     return;  
  90.                 }  
  91.                   
  92.                 printf("Client send : %s\n", send);  
  93.                 write( connfd, send, strlen( send ) );  
  94.             }  
  95.         }  
  96.           
  97.     }  
  98.       
  99. }  
  100.   
  101. int main( int argc, char ** argv )  
  102. {  
  103.     //!> char * SERV_IP = "10.30.97.188";   
  104.     char    buf[MAXLINE];  
  105.     int     connfd;  
  106.     struct sockaddr_in servaddr;  
  107.       
  108.     if( argc != 2 )  
  109.     {  
  110.         printf("Input server ip !\n");  
  111.         exit( EXIT_FAILURE );  
  112.     }  
  113.       
  114.     //!> 建立套接字   
  115.     if( ( connfd = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 )  
  116.     {  
  117.         printf("Socket Error...\n" , errno );  
  118.         exit( EXIT_FAILURE );  
  119.     }  
  120.   
  121.     //!> 套接字信息   
  122.     bzero(&servaddr, sizeof(servaddr));  
  123.     servaddr.sin_family = AF_INET;  
  124.     servaddr.sin_port = htons(SERV_PORT);  
  125.     inet_pton(AF_INET, argv[1], &servaddr.sin_addr);  
  126.       
  127.     //!> 链接server   
  128.     if( connect( connfd, ( struct sockaddr *  )&servaddr, sizeof( servaddr ) ) < 0 )  
  129.     {  
  130.         printf("Connect error..\n");  
  131.         exit(EXIT_FAILURE);  
  132.     }     
  133.     /*else 
  134.     { 
  135.         printf("Connet ok..\n"); 
  136.     }*/  
  137.   
  138.     //!>   
  139.     //!> send and recv   
  140.     send_and_recv( connfd );  
  141.       
  142.     //!>    
  143.   
  144.     close( connfd );  
  145.     printf("Exit\n");  
  146.       
  147.     return 0;  
  148. }  
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include  <arpa/inet.h>
#include <sys/select.h>

#define MAXLINE 1024
#define SERV_PORT 6000

//!> 注意输入是由stdin,接受是由server发送过来
//!> 所以在client端也是需要select进行处理的
void send_and_recv( int connfd )
{
	FILE * fp = stdin;
	int   lens;
	char send[MAXLINE];
	char recv[MAXLINE];
	fd_set rset;
	FD_ZERO( &rset );
	int maxfd = ( fileno( fp ) > connfd ? fileno( fp ) : connfd  + 1 );	
							        //!> 输入和输出的最大值
	int n;
	
	while( 1 )
	{
		FD_SET( fileno( fp ), &rset );
		FD_SET( connfd, &rset );			//!> 注意不要把rset看作是简单的一个变量
								//!> 注意它其实是可以包含一组套接字的哦,
								//!> 相当于是封装的数组!每次都要是新的哦!
		
		if( select( maxfd, &rset, NULL, NULL, NULL ) == -1 )
		{
			printf("Client Select Error..\n");
			exit(EXIT_FAILURE  );
		}
		
		//!> if 连接口有信息
		if( FD_ISSET( connfd, &rset ) )	//!> if 连接端口有信息
		{
			printf( "client get from server ...\n" );
			memset( recv, 0, sizeof( recv ) );
			n = read( connfd, recv, MAXLINE );
			if( n == 0 )
			{
				printf("Recv ok...\n");
				break;
			}
			else if( n == -1 )
			{
				printf("Recv error...\n");
				break;
			}
			else
			{
				lens = strlen( recv );
				recv[lens] = '\0';
				//!> 写到stdout
				write( STDOUT_FILENO, recv, MAXLINE );
				printf("\n");
			}

		}
		
		//!> if 有stdin输入
		if( FD_ISSET( fileno( fp ), &rset ) )	//!> if 有输入
		{
			//!> printf("client stdin ...\n");
			
			memset( send, 0, sizeof( send ) );
			if( fgets( send, MAXLINE, fp ) == NULL )
			{
				printf("End...\n");
				exit( EXIT_FAILURE );
			}
			else
			{
				//!>if( str )
				lens = strlen( send );
				send[lens-1] = '\0';		//!> 减一的原因是不要回车字符
								//!> 经验值:这一步非常重要的哦!!!!!!!!
				if( strcmp( send, "q" ) == 0 )
				{
					printf( "Bye..\n" );
					return;
				}
				
				printf("Client send : %s\n", send);
				write( connfd, send, strlen( send ) );
			}
		}
		
	}
	
}

int main( int argc, char ** argv )
{
	//!> char * SERV_IP = "10.30.97.188";
	char 	buf[MAXLINE];
	int   	connfd;
	struct sockaddr_in servaddr;
	
	if( argc != 2 )
	{
		printf("Input server ip !\n");
		exit( EXIT_FAILURE );
	}
	
	//!> 建立套接字
	if( ( connfd = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 )
	{
		printf("Socket Error...\n" , errno );
		exit( EXIT_FAILURE );
	}

	//!> 套接字信息
	bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
	
	//!> 链接server
	if( connect( connfd, ( struct sockaddr *  )&servaddr, sizeof( servaddr ) ) < 0 )
	{
		printf("Connect error..\n");
		exit(EXIT_FAILURE);
	}	
	/*else
	{
		printf("Connet ok..\n");
	}*/

	//!>
	//!> send and recv
	send_and_recv( connfd );
	
	//!> 

	close( connfd );
	printf("Exit\n");
	
	return 0;
}

编译运行:

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值