正在加载……
专注、离线、切勿分心
md5sum     filename校验两个文件内部内容是不是一样
du   -m   filename查看文件有多少兆

1、使用多进程,能够实现多个客户端同时下载文件

1、连接数控制:listen(个数),让业务进程数目和listen的数目相等;队列模式
2、子进程流程:接收到父进程给的new_fd,找到文件,映射到内存,向客户端发送文件,有一个协议规则(如何发一个文件),向fds[0]写入一个1,当父进程知道对应的fds[1]可读,父进程就把对应的子进程标示为非忙碌

子进程要告诉父进程我不忙了?
父进程给我们发送了描述符,子进程要睡觉?
recvmsg在没有接收到描述符时,是处于阻塞状态

3、main:
a:创建子进程
b:初始化sfd,bind
c:epoll_ctl注册sfd,注册每个子进程对应的fds[1]
d:listen,进入while(1),开始epoll_wait
客户端请求,就建立连接,得到new_fd,将new_fd发送给非忙碌的子进程,然后将对应子进程的状态标示为忙碌;当某个fds[1]可读,去读fds[1],标示对应子进程为非忙碌。

ARC  GC
引用计数:new_fd的引用计数2,当把new_fd给子进程以后,就关闭new_fd;子进程给客户端发送完毕数据,再关闭对应的new_fd;


                    
//服务器端 //客户端
main.c tcp_client.c
#include "func.h"
//使用多进程,实现多个客户端同时下载文件
int main(int argc,char* argv[])
{
        if(argc!=4)
        {
                printf("error args\n");
                return -1;
        }
        int num=atoi(argv[3]);         //要创建的进程数目
        pchild p=(pchild)calloc(num,sizeof(child));
        //创建进程
        make_child(p,num);             //在child.c实现"1"
        //创建socket
        int sfd=socket(AF_INET,SOCK_STREAM,0);
        if(-1==sfd)
        {
                perror("socket");
                return -1;
        }
        struct sockaddr_in ser;
        memset(&ser,0,sizeof(ser));
        ser.sin_family=AF_INET;
        ser.sin_port=htons(atoi(argv[2]));  //一定要用htons
        ser.sin_addr.s_addr=inet_addr(argv[1]);
        int ret;
        //给sfd绑定IP地址和端口号
        ret=bind(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr));
        if(-1==ret)
        {
                perror("bind");
                return -1;
        }
        //epoll注册
        int epfd=epoll_create(1);
        struct epoll_event event;
        struct epoll_event* evs=(struct epoll_event*)calloc(num+1,sizeof(event));
//之所以是num+1,还有一个sfd
        event.events=EPOLLIN;
        event.data.fd=sfd;
        ret=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event);      
        if(-1==ret)
        {
                perror("epoll_ctl");
                return -1;
        }
        int i;
        for(i=0;i<num;i++)
        {
                event.data.fd=p[i].fds;
                ret=epoll_ctl(epfd,EPOLL_CTL_ADD,p[i].fds,&event);      
                if(-1==ret)
                {
                        perror("epoll_ctl");
                        return -1;
                }
        }              
        ret=listen(sfd,num);  //子进程数目与监听的数目保持一致
        if(-1==ret)
        {
                perror("listen");
                return -1;
        }
        int new_fd;
        int j;
        int flag;
        while(1)
        {
            memset(evs,0,(num+1)*sizeof(event));
            int ret=epoll_wait(epfd,evs,num+1,-1);
            if(ret >0)
            {
                for(i=0;i<ret;i++)
                {
                     if(evs[i].data.fd == sfd)
                     {
                          new_fd=accept(sfd,NULL,NULL);
                          for(j=0;j<num;j++)
                          {
                               if(p[j].busy ==0)
                                   break;
                          }
                          send_fd(p[j].fds,new_fd);
                          p[j].busy=1;
                          printf("give child is ok\n");
                          close(new_fd);   //传递完后,父进程可以关闭描述符,引用计数-1
                     }
                     for(j=0;j<num;j++)
                     {
                          if(evs[i].data.fd == p[j].fds)    //当对应子进程的fds可读时,子进程不忙碌
                          {
                              read(p[j].fds,&flag,sizeof(flag));
                              p[j].busy=0;
                              printf("child is not busy\n");
                          }
                     }
                 }
            } 
      }
}     
#include "func.h"

int main(int argc,char** argv)
{
        if(argc !=3)
        {
                printf("error args\n");
                return -1;
        }
        int sfd=socket(AF_INET,SOCK_STREAM,0);
        if(-1==sfd)
        {
                perror("socket");
                return -1;
        }
        struct sockaddr_in ser;
        memset(&ser,0,sizeof(ser));
        ser.sin_family=AF_INET;
        ser.sin_port=htons(atoi(argv[2]));  //一定要用htons
        ser.sin_addr.s_addr=inet_addr(argv[1]);
        int ret;
        ret=connect(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr));
        if(-1==ret)
        {
                perror("connect");
                return -1;
        }
        data d;
        memset(&d,0,sizeof(d));
        recv_n(sfd,&d.len,sizeof(int));         //读取要接收文件名字的长度
        recv_n(sfd,d.buf,d.len);                //读取文件名
        int fd;
        fd=open(d.buf,O_RDWR|O_CREAT,0666);
        if(-1==fd)
        {
                perror("open");
                return -1;
        }
        while(1)
        {
                memset(&d,0,sizeof(d));
                recv_n(sfd,&d.len,sizeof(int));   //文件发送完毕,服务器会发送一个0过来,这里就可以跳出循环    
                if(d.len >0)
                {
                        recv_n(sfd,d.buf,d.len);
                        write(fd,d.buf,d.len);
                }else{
                        break;
                }
        }
        memset(&d,0,sizeof(d));
        ret=recv(sfd,&d.len,sizeof(int),0);  //服务器断开,这边会一直读到0
        printf("ret=%d\n",ret);   //判断服务器端是否断开
        close(sfd);
        close(fd);               
        return 0;
}

                                                 

child.c
send_fd.c
#include "func.h"
//创建n个子进程
void make_child(pchild p,int n)
{
        int i;
        int fds[2];
        pid_t pid;
        int ret;
        for(i=0;i<n;i++)  //主进程进来,for循环创建n个子进程,并且为每个子进程的对应数据信息结构体赋值。
        {
                ret=socketpair(AF_LOCAL,SOCK_STREAM,0,fds);
                if(-1==ret)
                {
                        perror("socketpair");
                        exit(-1);
                }
                pid=fork();
                if(pid==0)
                {
                        close(fds[1]);
                        child_handle(fds[0]);   //子进程的业务流程函数
                }
                close(fds[0]);
                p[i].fds=fds[1];
                p[i].pid=pid;
                p[i].busy=0;   //0代表子进程非忙碌
        }
}

void child_handle(int fdr)
{
        int new_fd;
        int flag=1;
        while(1)
        {       
            recv_fd(fdr,&new_fd);  //接收父进程发送的连接描述符new_fd,这里会阻塞等待,直到接收到newfd;
            send_file(new_fd);  //子进程向客户端发送文件"2"
            write(fdr,&flag,sizeof(flag));  //子进程向父进程发送通知,完成任务       
        }
}

#include "func.h"
void send_fd(int fds,int fd)
{
        struct msghdr msg;
        memset(&msg,0,sizeof(msg));
        struct iovec iov[2];
        char buf1[10]="hello";
        char buf2[10]="world";
        iov[0].iov_base=buf1;
        iov[0].iov_len=5;
        iov[1].iov_base=buf2;
        iov[1].iov_len=5;
        msg.msg_iov=iov;
        msg.msg_iovlen=2;
        struct cmsghdr* cmsg;
        int len=CMSG_LEN(sizeof(int));
        cmsg=(struct cmsghdr*)calloc(1,len);
        cmsg->cmsg_len=len;
        cmsg->cmsg_level=SOL_SOCKET;
        cmsg->cmsg_type=SCM_RIGHTS;
        *(int*)CMSG_DATA(cmsg)=fd;
        msg.msg_control=cmsg;
        msg.msg_controllen=len;
        int ret=sendmsg(fds,&msg,0);   
        if(-1==ret)
        {
                perror("sendmsg");
                return;
        }
       free(cmsg);   //释放资源
void recv_fd(int fds,int* pfd)
{
        struct msghdr msg;
        memset(&msg,0,sizeof(msg));
        struct iovec iov[2];
        char buf1[10]={0};
        char buf2[10]={0};
        iov[0].iov_base=buf1;
        iov[0].iov_len=5;
        iov[1].iov_base=buf2;
        iov[1].iov_len=5;
        msg.msg_iov=iov;
        msg.msg_iovlen=2;
        struct cmsghdr* cmsg;
        int len=CMSG_LEN(sizeof(int));
        cmsg=(struct cmsghdr*)calloc(1,len);
        cmsg->cmsg_len=len;
        cmsg->cmsg_level=SOL_SOCKET;
        cmsg->cmsg_type=SCM_RIGHTS;
        msg.msg_control=cmsg;
        msg.msg_controllen=len;
        int ret=recvmsg(fds,&msg,0);   //阻塞函数
        if(-1==ret)
        {
                perror("recvmsg");
                return;
        }
        *pfd=*(int*)CMSG_DATA(cmsg);
        free(cmsg);   //释放资源
}
send_file.c pool_n.c
#include "func.h"

/*
发送文件:
1、服务器把文件名发过去,客户端接收到文件名,在本地新建一个和服务器文件名相同的文件
2、发一个len+strlen(buf)
3、文件结束时,发送len=0;
*/

void send_file(int fdw)
{
        data d;
        memset(&d,0,sizeof(d));
        d.len=strlen(FILENAME);
        strcpy(d.buf,FILENAME);
        int ret;
        ret=send(fdw,&d,4+d.len,0);      //发送文件名
        if(-1==ret)
        {
                perror("send");
                exit(-1);
        }
        int fd=open(FILENAME,O_RDONLY);
        if(-1==fd)
        {
                perror("open");
                exit(-1);
        }
        while(memset(&d,0,sizeof(d)),(d.len=read(fd,d.buf,sizeof(d.buf)))>0)
        {
                send_n(fdw,&d,4+d.len);   //确保数据全都发送成功,实现 pool_n.c
//因为int类型是4个字节,所以发送长度是 4+d.len ;
        }
        ret=0;
        send_n(fdw,&ret,sizeof(int));   //发送文件结束标志
        close(fdw);   //发完文件关闭文件描述符,引用计数-1
}
#include "func.h"
void send_n(int new_fd,char* buf,int len)
{
        int ret;
        int total=0;
        while(total<len)
        {
                ret=send(new_fd,buf+total,len-total,0); 
//socket缓冲区是有大小的,64K
我们的电脑CPU很快,就会一直read然后send往socket缓冲区放,一直发,如果对端recv速度慢,和可能就会先把socket缓冲区填满,导致接收发送的数据不一致;
//假设接收端socket缓冲区满了,我们这边发数据,那边同时也在接收数据;这时候我们还发送d.len个字节,假设1000个,对面recv只从缓冲区读走100个,这个时候send发送1000个字符不会返回失败,只会返回真正能发送的字节数100;如果这时候我们不控制我们发送端的时序,又读d.len个字节发送,那么上次1000个数据就有900个丢失;
                total=total+ret;
        }
}

void recv_n(int new_fd,char* buf,int len)
{
        int ret;
        int total=0;
        while(total<len)
        {
                ret=recv(new_fd,buf+total,len-total,0);
                total=total+ret;
        }
}
//发送数据和接收数据都是先放到缓冲区,让后程序带去再去读,如果速度不匹配,会导致缓冲区溢出,数据丢失; tcp udp发送接收缓冲区的大小都是64K


func.h
#ifndef __FUNC_H__       //防止头文件重复引用
#define __FUNC_H__
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/uio.h>

#define FILENAME "hello.avi"
typedef struct{
          pid_t pid;
     //   int new_fd;
          int fds; //sockpair管道的另一端
          int busy;
}child,*pchild;

//传输数据使用的结构体
typedef struct{
        int len;           //表明要从buf里面读多少个字节,len==0表示读完了
        char buf[1000];   
}data,*pdata;

void child_handle(int);

#endif
  
           
                                                                                                                                                                       
C语言不是面向对象的语言,但是可以写出类似面向对象的程序,用的就是结构体;

//server.c
#include"func.h"
int main(int argc,char **argv)
{       
        if(4!=argc)
        {
                printf("error argcs\n");
                return -1;
        }
        int sfd = socket(AF_INET,SOCK_STREAM,0);
        if(-1==sfd)
        {
                perror("socket");
                return -1;
        }
        int num = atoi(argv[3]);
        struct sockaddr_in ser;
        bzero(&ser,sizeof(ser));
        ser.sin_port = htons(atoi(argv[2]));
        ser.sin_addr.s_addr = inet_addr(argv[1]);
        ser.sin_family = AF_INET;
        int ret = bind(sfd,(struct sockaddr*)&ser,sizeof(ser));
        if(-1==ret)
        {
                perror("bind");
                return -1;
        }
        ret = listen(sfd,num);
        if(-1==ret)
        {
                perror("listen");
                return -1;
        }
        //注册描述符,sfd和创建好的子进程对应的父进程端的管道
        int epfd  = epoll_create(1);
        if(-1==epfd)
        {
                perror("epoll_create");
                return -1;
        }
        struct epoll_event eve,eves[num+1];  //num个连接到子进程的管道和一个sfd
        bzero(&eve,sizeof(eve));
        eve.events = EPOLLIN;
        eve.data.fd = sfd;
        ret = epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&eve);
        if(-1==ret)
        {
                perror("epoll_ctl");
                return -1;
        }
        //创建子进程
        pchild p = (pchild)calloc(num,sizeof(child));
        make_child(p,num);
        for(int i=0;i<num;i++)
        {
                bzero(&eve,sizeof(eve));
                eve.events = EPOLLIN;
                eve.data.fd = p[i].fds;
                epoll_ctl(epfd,EPOLL_CTL_ADD,p[i].fds,&eve);
        }
        int new_fd;
        int flag;
        int j = 0;
        int i;
        while(1)
        {
                memset(eves,0,(num+1)*sizeof(eve));
                //bzero(eves,(num+1)*sizeof(eve));
                ret = epoll_wait(epfd,eves,num+1,-1);
                if(ret>0)
                {
                        for(i=0;i<ret;i++)
                        {
                                if(eves[i].data.fd == sfd)
                                {
                                        new_fd = accept(sfd,NULL,NULL);
                                        //printf("new_fd =%d\n",new_fd);
                                        for(j=0;j<num;j++)
                                        {
                                                if(p[j].status == 0)
                                                {
                                                        break;
                                                }
                                        }
                                        send_fd(p[j].fds,new_fd);
                                        p[j].status = 1;
                                        close(new_fd);  //new_fd已经发送给空闲子进程,main进程不在需要new_fd
                                        //printf("find leisure process , and send new_fd success\n");
                                }
                                for(j=0;j<num;j++)
                                {
                                        if(eves[i].data.fd == p[j].fds)
                                        {
                                                read(p[j].fds,&flag,sizeof(int));
                                                p[j].status = 0;
                                                //printf("p[%d].fds = %d , is not busy\n",j,p[j].fds);
                                        }
                                }
                        }
                }
        }
        return 0;
}
//client.c
#include"func.h"
int main(int argc,char **argv)
{
        if(3!=argc)
        {
                printf("error args\n");
                return -1;
        }
        int sfd = socket(AF_INET,SOCK_STREAM,0);
        if(-1==sfd)
        {
                perror("socket");
                return -1;
        }
        struct sockaddr_in ser;
        bzero(&ser,sizeof(ser));
        ser.sin_family = AF_INET;
        ser.sin_port = htons(atoi(argv[2]));
        ser.sin_addr.s_addr = inet_addr(argv[1]);
        int ret = connect(sfd,(struct sockaddr *)&ser,sizeof(ser));
        if(-1==ret)
        {
                perror("connect");
                return -1;
        }
        data d;
        bzero(&d,sizeof(d));
        recv(sfd,&d.len,sizeof(int),0);  //先接收要接收的文件名字大小
        recv(sfd,&d.buf,d.len,0);
        printf("file name is %s\n",d.buf);
        int fdw = open(d.buf,O_WRONLY|O_CREAT,0666);
        if(-1==fdw)
        {
                perror("open");
                return -1;
        }
        while(1)
        {
                memset(&d,0,sizeof(d));
                recv_n(sfd,(char*)&d.len,sizeof(int));
                if(d.len>0)
                {
                        recv_n(sfd,d.buf,d.len);
                        write(fdw,d.buf,d.len);
                }
                else
                {
                        break;
                }
        }
        memset(&d,0,sizeof(d));
        ret = recv(sfd,&d.len,sizeof(int),0); //阻塞,等待服务器关闭文件描述符这边就会读到0,然后再执行下面;
        //printf("ret =%d\n",ret);
        close(sfd);
        close(fdw);
        return 0;
}

//child.c
#include"func.h"
void child_handle(int fds)
{
        int new_fd;
        int flag = 1;
        while(1)
        {
                //printf("wait read new_fd\n");
                read_fd(fds,&new_fd);  //子进程等待接收main进程发送过来的客户端链接请求产生的newfd;
                sendfile(new_fd);  //用newfd发送文件给客户端;
                //printf("file send success\n");
                write(fds,&flag,sizeof(int));  //文件发送完毕通过管道给main进程发送一个数据,触发epoll_wait,关闭new_fd,重置子进程状态;
        }
}
void make_child(pchild p,int num)
{
        int fds[2];
        int ret;
        pid_t pid;
        for(int i=0;i<num;i++)
        {
                ret = socketpair(AF_LOCAL,SOCK_STREAM,0,fds);
                if(-1==ret)
                {
                        perror("socketpair");
                        return ;
                }
                pid = fork();
                if( 0==pid )
                {
                        close(fds[1]);
                        child_handle(fds[0]);
                }
                close(fds[0]);
                p[i].fds = fds[1];
                p[i].pid = pid;
                p[i].status = 0;//刚创建好,待命状态,非忙碌,status=0;
        }
}

//send_file.c
#include"func.h"
void sendfile(int new_fd)
{
        data d;
        memset(&d,0,sizeof(d));
        d.len = strlen(FILENAME);
        strcpy(d.buf,FILENAME);
        int ret = send(new_fd,&d,4+d.len,0);  //先发送文件名给客户端,客户端获得这个文件名建立一个新的相同名字的文件;文件名的长度存放在d.len里面,所以发送长度就为4+d.len;
        if(-1==ret)
        {
                perror("send");
                return  ;
        }
        int fdr = open(FILENAME,O_RDONLY);
        if(-1==fdr)
        {
                perror("open");
                return ;
        }
        while(memset(&d,0,sizeof(d)),(d.len=read(fdr,d.buf,sizeof(d.buf)))>0)
        {
                send_n(new_fd,(char*)&d,4+d.len);
        }
        int flag = 0;  //标志这边已经发送完文件里,客户端可以断开连接了
        send(new_fd,&flag,sizeof(int),0);
        close(new_fd);
        close(fdr);
}
//send_fd.c
#include"func.h"
void send_fd(int fds,int fd)
{
        struct msghdr msg;
        struct iovec iov[2];
        char buf1[6] = "hello";
        char buf2[6] = "world";
        iov[0].iov_base = buf1;
        iov[0].iov_len = strlen(buf1);
        iov[1].iov_base = buf2;
        iov[1].iov_len = strlen(buf2);

        struct cmsghdr *cmsg;
        int len = CMSG_LEN(sizeof(int));   //我们要传递的是一个整形的描述符
        cmsg = (struct cmsghdr*)calloc(1,len);
        cmsg->cmsg_len = len;
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS;
        int *fdptr;
        fdptr = (int *)CMSG_DATA(cmsg);
        *fdptr = fd;

        bzero(&msg,sizeof(msg));
        msg.msg_iov = iov;
        msg.msg_iovlen = 2;
        msg.msg_control = cmsg;
        msg.msg_controllen = len;

        int ret = sendmsg(fds,&msg,0);
        if(-1==ret)
        {
                perror("sendmsg");
                return ;
        }
        free(cmsg);
        return ;
}
void read_fd(int fds,int *fd)
{
        struct msghdr msg;
        bzero(&msg,sizeof(msg));
        //char buf1[6] = "";
        char buf1[6] = {0};
        //char buf2[6] = "";
        char buf2[6] = {0};
        struct iovec iov[2];
        iov[0].iov_base = buf1;
        //iov[0].iov_len = strlen(buf1);
        //最开始就因为这里写了长度,前面直接赋值的"",导致子进程在read_fd的时候不能够卡住,等待接收main进程接收到新的new_fd发送过来;以前这里写过小例子测试过,后面调试加输出才定位到这里,把这个错误解决了,这里是内核控制信息,具体原理也不懂,这边必须要放数据,随便放几个,但是并不能接收到对面发来的数据;
        iov[0].iov_len = sizeof(buf1);
        iov[1].iov_base = buf2;
        //iov[1].iov_len = strlen(buf2);
        iov[1].iov_len = sizeof(buf2);

        struct cmsghdr *cmsg;
        int len = CMSG_LEN(sizeof(int));
        cmsg = (struct cmsghdr*)calloc(1,len);
        cmsg->cmsg_len = len;
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS;

        bzero(&msg,sizeof(msg));
        msg.msg_iov = iov;
        msg.msg_iovlen = 2;
        msg.msg_control = cmsg;
        msg.msg_controllen = len;

        int ret = recvmsg(fds,&msg,0);
        if(-1==ret)
        {
                perror("recvmsg");
                return ;
        }
        *fd = *(int *)CMSG_DATA(cmsg);
        //printf("buf1=%s buf2=%s\n",buf1,buf2);
        free(cmsg);
        return;
}

//pool_n.c
#include"func.h"
void send_n(int new_fd,char *buf,int len)
{
        int ret;
        int total = 0;
        while(total<len)
        {
                ret = send(new_fd,buf+total,len-total,0); //socket缓冲区是有大小的,接收方和发送方各自的socket缓冲区都是64K,如果接收方接收速度不匹配,那么会导致接收数据方缓冲区满,发送方成功发送的数据不等于预定发送长度;
                total += ret;
        }
}
void recv_n(int new_fd,char *buf,int len)
{
        int ret;
        int total = 0;
        while(total<len)
        {
                ret = recv(new_fd,buf+total,len-total,0);
                total += ret;
        }
}




//func.h
#ifndef __FUNC_H__
#define __FUNC_H__
#define FILENAME "meihao.mp4"
#include<stdio.h>
#include<string.h>
#include<strings.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/stat.h>
#include<sys/epoll.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<unistd.h>


typedef struct
{
        int pid;
        int fds;
        int status;
}child,*pchild;

typedef struct
{
        int len;  //buf里面存放的数据长度
        char buf[128];
}data,*pdada;


void send_fd(int fds,int fd);
void read_fd(int fds,int *fd);
void child_handle(int fds);
void make_child(pchild p,int num);
void sendfile(int new_fd);
void recv_n(int new_fd,char *buf,int len);
void send_n(int new_fd,char *buf,int len);
#endif





写程序思路:
主进程main先创建套接字,绑定,监听,然后创建num个子进程,socketpair生成的管道是全双工的,main进程中定义了num个子进程结构体里记录一端的管道描述符,epoll来监控,另外一端子进程自己保留,用来接收main进程发来的客户端连接产生的new_fd,然后发送文件;文件发送方和接收方所有一个socket缓冲区,由于速度不能能匹配,会导致数据接收不完整,所以要用一个data结构体来存放发送了多少个字节,和对应的字节,这样客户端先接收4个字节,得到对端发送了多少个字节,然后再用一个小数据结构接收指定数量的字节;文件发送完毕,子进程用管道发送一个数据,main进程epoll监听到了就读出这个数据,然后修改这个进程的状态非忙碌,子进程自己有while(1)执行到read_fd阻塞,等待main选中传递new_fd;


posted on 2018-03-30 08:59  正在加载……  阅读(480)  评论(0编辑  收藏  举报