进程池

本文详细介绍了进程池的概念,以及如何在父子进程间传递文件描述符控制信息,包括为何不能通过管道直接传递和如何使用socketpair及sendmsg接口。通过示例代码展示了进程间传递文件描述符的方法,并讨论了进程池的实现、客户端请求处理和内存管理等细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

进程池和线程池
并发的给多个客户端提供服务。


一、内核控制信息的理解  
进程池最难的地方:当客户端连接服务器时,服务器产生一个new_fd,主进程如何将new_fd发送给子进程。使用下列两种方法传递内核控制信息,是不可以的:
1.通过fork一个子进程,父进程打开一个文件 fd,通过管道把fd给子进程。这种方法是错误的。文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每个进程所维护的该进程打开文件的记录表。当进程打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。
2.为什么不先创建fd,然后再fork,这样ford出来的子进程就同主进程的一样了,这样就相当于已经把fd传过来了。这种方法也是不可取的,因为实在应用的时候,一个服务器启动多少个进程或线程都已经定好了。需要让子进程处于待命状态,这样效率才最佳。所以要先创建子进程。

测试用例:通过管道传递描述符,是读不了的。
int main(){
        int fds[2];                                        //创建无名管道,对应文件描述符为3,4 
        pipe(fds);
        if(!fork()){                                      //子进程 
                close(fds[1]);                           //客户端关闭写端,使用读端 。关闭4,保留3 
                int fd;
                read(fds[0],&fd,sizeof(fd));      //读4个字节,放到fd中,此时fd=3。
                printf("child fd=%d\n",fd);
                char buf[10]={0};
                read(fd,buf,sizeof(buf));           //后面对fd操作,其实就是对fds[0]进行操作。本来我想对file进行操作,其实没有达目的。
                printf("buf=%s\n",buf);
                exit(0);
        }else{                                              //父进程 
                close(fds[0]);                            //关闭3,保留4 
                int fd=open("file",O_RDWR);   //打开一个文件,此时fd=3 
                printf("fd=%d\n",fd);
                write(fds[1],&fd,sizeof(fd));      //向管道里面写fd。也就是把数字3写进管道。等价于write(4,&fd,4); 
                wait(NULL);
                return 0;       
        }
}

二、如何把一个进程描述符的控制信息,传递给另一个进程。
第一步,初始化socketpair类型描述符
 int fds[2]; 
 socketpair(AF_LOCAL,SOCK_STREAM,0,fds);   //必须要用这个管道。这个管道是可以传递控制信息。这个管道只能用于父子进程之                                                                                间。这个管道是全双工的。
第二步:sendmsg接口发送描述符 
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); //告诉内核我们要传递描述信息,并不是我们把秒数信息放在这个函数里                                                                                                  传递,内核并没有把描述信息告诉我们。sockfd即sockpair初始化的描述符
                                                                                               fds[1]。Sendmsg关键是初始化msghdr结构体。
struct msghdr {               
  void           *msg_name;         //没用,memset为0即可
  socklen_t     msg_namelen;     //没用,memset为0即可
  struct iovec *msg_iov;           //结构体指针。这个机构体跟一次可以写多个buf有关。虽然不传内容,但是因为有激活的问题,所以                                                        必须要写点东西。                   
  size_t         msg_iovlen;         //结构体个数。因为前面是struct *类型。
  void          *msg_control;       //关键,即下面的cmsghdr结构体地址。告诉它我们要传递哪一个的信息。         
  size_t         msg_controllen;    //cmsghdr结构体的长度。这个长度只能是字节,因为前面是void * 类型,没有类型,所以不可能是个数.
  int              msg_flags;           //没用
 }; 
 
struct cmsghdr{                                               //cmsghdr是控制信息结构体。可以通过man cmsg查看。这是一个变长结构体。
  socklen_t cmsg_len;                                 //变长结构体的长度。
                       //因为前三个成员变量的长度是一定的,所以可以通过偏移找到最后一个成员的首地址,                                                                          可以通过接口SMSG_DATA()来得到最后一个成员的首地址。
                                                                      //变长结构体长度的计算就是前三个成员变量的长度(3*4)加上最后一个成员变量的长度。                                                                         本应用中,我们在最后一个成员变量中传递fd,所以最后一个成员变量的长度为4。所以                                                                          cmsg_len等于16,除了自己计算的方法,还可以通过接口:CMSG_LEN()来得到长度。                                                                          CMSG_LEN的参数为最后一个成员变量的长度。
  int           cmsg_level;                               //填SOL_SOCKET即可。                 
  int           cmsg_type;                                //填SCM_RIGHTS即可。
       //followed by unsigned char cmsg_data[];   //最后一个位置自己放什么都行。在本应用中,我们想存放fd.          
 }; 

首先定义  struct cmsghdr *cmsg 指针 
cmsg_len 中存取cmsghdr结构体的长度,通过CMSG_LEN进行计算,我们传递的fd的大小为整型四个字节,所以 
Int len = CMSG_LEN(sizeof(int));    //先得到边长结构体的大小 然后为结构体申请空间: 
cmsg = (struct cmsghdr *)calloc(1,len);             //calloc函数可以动态分配空间,分配的空间大小为1*len。与malloc的区别是:calloc函数                                                                         不光能申请空间,而且会把空间初始化为0。
cmsg->cmsg_len = len; 
cmsg->cmsg_level =SOL_SOCKET; 
cmsg->cmsg_type = SCM_RIGHTS; 
int *fdptr; 
fdptr= (int *) CMSG_DATA(cmsg);    //得到最后一个元素的首地址,因为要放int,所以强转一下。解引用就是一个整形数 。 
*fdptr = fd; 

第三步: Recvmsg接收文件描述符,接收的msghdr结构体初始化和sendmsg几乎完全一致,区别如下: *fd = *fdptr; 

例1:writev的使用:一次可以写多个buf 
ssize_t writev(int fd,const struct iovec*iov,int iovcnt);  //iov是结构体指针。iovcnt是结构体的个数。表示向fd中写几个结构体。
struct iovec{                                                              //通过man writev 可以看到iovec结构体  
  void * iov_base;        //起始地址
  size_t iov_len;          //长度
}

int main(){
        int fd=open("file1",O_RDWR);
        char buf1[10]="hello ";
        char buf2[10]="world";
        struct iovec iov[2];                          //定义两个结构体 
        iov[0].iov_base=buf1;                     //第一个结构体的起始地址 
        iov[0].iov_len=6;          //第一个结构体的长度 
        iov[1].iov_base=buf2;                     //第二个结构体的起始地址 
        iov[1].iov_len=5;          //第二个结构体的长度 
        int ret=writev(fd,iov,2);                   //向fd中写2个结构体,起始地址是iov。执行后可以看到文件的内容是:hello world.
        if(-1==ret){
                perror("writev");
                return -1;
        }
        return 0;
}

例2:在进程间传递内核控制信息(这段代码以后就这么写,不会的时候抄就行了)
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;
        }
}
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);                     
}

int main(){
        int fds[2];
        int ret;
        ret=socketpair(AF_LOCAL,SOCK_STREAM,0,fds);     //第一步。
        if(-1==ret){                                                                 //成功返回0,失败返回-1
                perror("socketpair");
                return -1;
        }
        if(!fork()){                                                                  //子进程 
                close(fds[1]);                                                      //关闭一端 
                int fd;
                recv_fd(fds[0],&fd);                                            //通过fds[0],接收放到fd中 
                printf("child fd=%d\n",fd);
                char buf[10]={0};
                read(fd,buf,sizeof(buf)); 
                printf("buf=%s\n",buf);
                exit(0);
        }else{                                                                         //父进程 
                close(fds[0]);                                                       //关闭一端 
                int fd=open("file",O_RDWR);
                printf("parent fd=%d\n",fd);                                 
                send_fd(fds[1],fd);                                               //发送fd。调用函数,在函数中可以使用这个描述符。所以值传递即可。
                wait(NULL);
                return 0;
        }
}


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

子进程要告诉父进程我不忙了。
子进程不忙的时候应该睡觉,父进程给子进程发送了描述符,子进程才醒来。

向fds[0]写入1,当父进程知道对应的fds[1]可读,父进程把对应的子进程标示为非忙碌。
recvmsg在没有接收到父进程给的描述符,子进程在睡觉。
程序分析:   
父进程流程:
1.sfd=socket();
  bind();
  listen()监听客户端的请求;
  创建10个子进程(),等待我把new_fd发送给它,让子进程通过new_fd与客户端通话。创建的子进程要接收父进程传过来的new_fd,需要                                                       用到socketpair();
  epfd=epoll_creat();
  epoll_ctl(ADD,sfd);
  epoll_ctl(ADD,十个进程的读端);
  epoll_wait();
  if(sfd){      //有客户端请求
    accept();
    分配一个空闲的进程
    把new_fd传递给这个进程
    把这个进程标记为忙碌
  }
  if(子进程的各个端口){  //说明数据已经发送完了
    把这个进程标记为空闲
  }

子进程流程:
1.等待父进程把new_fd发送给自己
2.收到new_fd之后开始发送数据
3.发送完数据之后告诉父进程已经发送完了

客户端流程:
1.sfd=socket();
2.connect();
3.接收文件

细节二:如何处理客户端请求的数目和处理的数目
方式一,链接数控制:listen(个数),让业务进程数目和listen的数目相等。方式二,队列模式。本设计中使用方式一。

细节三:内存管理:ARC(引用计数) GC(自动回收) 
引用计数:一个结构体或对象,假如有一个进程使用它,使用他一次引用技术就加1。只有当为0时,才回收。
C、C++使用引用计数,java使用GC。
当把new_fd发送给了子进程之后,new_fd的引用计数为2。
可以当把new_fd发给了子进程之后,就关闭new_fd。这样引用计数就变回了1。这种的劣势是
可以当子进程给客户端发送完数据,再关闭对应的new_fd。

细节四:如果想要定义一个数组,但是数组的个数通过参数传进来的。
创建结构体数组,不知道多少时,用堆内存来做。
struct student *s=(struct student *)calloc(num,sizeof(struct student));    //calloc一块空间,大小为num个结构体的大小。这样就可以用                                                                                                                   p[0],p[1]...p[num-1]来访问数组了。
细节五:如何创建多个进程。 
a=fork();
if(a==0){
                   //子进程1
}else if(a!=0){
  b=fork();
  if(b==0){
              //子进程2
  }else{
                 //主进程
  }
}
等价于:
if(!fork()){
  //子进程1
}
if(!fork()){
  //子进程2
}
..     //主进程

细节六:如何传递一个文件,双方协商好什么时候开始发送,什么时候发送完
首先发送文件名,再发送数据。当发送完之后,发送一个只有火车头没有数据的火车。对方收到以后得知数据已经发送完毕。

typedef struct{                //小火车,只有车长才知道后面有多少个字节。这是一个简单的协议。这就是上层协议。
  int len;                    //小火车:接下来我要发多少。
  char buf[1000];       //存放数据。
}data,*pdata;

细节七:当发送大文件时,可能会出错。socket的缓冲区只有64K。极有可能在网络中传输的速度不匹配。
通过循环一直写,直到把自己想写的内容完全写进去为止。
通过循环一直读,直到读出自己想要的数目为止。

细节八:如何看两个文件完全相同 
查看两个文件是否完全相同:md5查看内容是否一样。闪电算法:做一致性校验。
md5sum file 

例:使用多进程,实现多个客户端同时下载文件
//服务器端            
/****头文件****/
#define FILENAME "hello.avi"
typedef struct{
        pid_t pid;
        int fds;
        int busy;
}child,*pchild;

typedef struct{                                //传输数据使用的结构体。该结构体约定双方传递数据使用的协议。
        int len;                                   //len为后面要发送的数据长度 
        char buf[1000];
}data,*pdata;

void child_handle(int);

/****进程间传递描述符*****/
void send_fd(int fds,int fd){                                       //把fd发送给进程fds  
        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);                                       //释放calloc出来的内容 
}

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);
}
/****发送文件****/
#include "func.h"

void send_n(int new_fd,char* buf,int len){                  //向new_fd发送数据,数据首地址为buf,长度为len。可以解决对方读的慢,自己写                                                                                   的快,导致不能一次全写进去的问题。 
        int ret;
        int total=0;                                                        //total表示已经发送了多少
        while(total<len){                                               
                ret=send(new_fd,buf+total,len-total,0);       //已经发送了total个,接下来还要发送len-total个,首地址为buf+total
                total=total+ret;
        }
}

void recv_n(int new_fd,char* buf,int len){                 //从new_fd接收数据,放到buf中,要接收的长度为len.可以解决对方写的慢,自己读                                                                                的快,导致一次不能读len个字节的问题。
        int ret;
        int total=0;                                                       //total表示已经接收了多少 
        while(total<len){ 
                ret=recv(new_fd,buf+total,len-total,0);       //已经接收了total个,接下来还要接收len-total个,放到buf+total中 
                total=total+ret;
        }
}

void send_file(int fdw){                                                     //子进程向客户端发送文件。子进程通过fdw与客户端通信。 
        data d;                                                                      //定义小火车结构体  
        memset(&d,0,sizeof(d));
        d.len=strlen(FILENAME);                                         //文件名的长度
        strcpy(d.buf,FILENAME);                                         //文件名放到buf中
        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){   //首先清空小火车,把fd中的内容读到小火车的buf中.只要能读                                                                                                     出内容就循环.用d.len记录读出的数量,以便记录在火车头上,让对方知道.
                send_n(fdw,&d,4+d.len);                                   //把小火车发送给客户端 
        }
        ret=0;
        send_n(fdw,&ret,sizeof(int));                                     //当发送的小火车,只有车头时,表示数据已经发送完毕。
        close(fdw);
}

/****创建多个子进程*****/
#include "func.h"

void child_handle(int fdr){
        int new_fd;
        int flag=1;
        while(1){     
                recv_fd(fdr,&new_fd);                            //接收父进程发送的连接描述符new_fd,如果没有就睡觉。接收到的fd放到new_fd中 
                send_file(new_fd);                                  //子进程向客户端发送文件。子进程通过new_fd与客户端通信。
                write(fdr,&flag,sizeof(flag));                   //子进程向父进程发送通知,完成任务 
        }
}

void make_child(pchild p,int n){                                                  //创建n个子进程 
        int i;
        int fds[2];
        pid_t pid;
        int ret;
        for(i=0;i<n;i++){                                                                 //循环创建n个子进程,注意写法 
                ret=socketpair(AF_LOCAL,SOCK_STREAM,0,fds);    //创建管道 
                if(-1==ret){
                        perror("socketpair");
                        exit(-1);
                }
                pid=fork();                                                                  //创建子进程
                if(pid==0){
                        close(fds[1]);                                                       //子进程关闭fds[1]端,使用fds[0]端 
                        child_handle(fds[0]);                                            //子进程的业务流程函数  
                }
                close(fds[0]);                                                               //父进程关闭fds[0]端,使用fds[1]端 
                p[i].fds=fds[1];                                                            //父进程使用的管道fds[1]与子进程进行通信 
                p[i].pid=pid;                                                                //在p中记录下子进程ID  
                p[i].busy=0;                                                                //在p中初始化子进程的忙碌状态,0代表子进程非忙碌 
        }
}

/*****main.c****/
#include "func.h"

int main(int argc,char* argv[]){                                  //传入参数:IP地址、端口、要创建的进程数目 
        if(argc!=4){
                printf("error args\n");
                return -1;
        }
        int num=atoi(argv[3]);                                        //要创建的进程数目 
        pchild p=(pchild)calloc(num,sizeof(child));          //calloc出一块num*sizeof(child)大小的内存空间,存放child结构体。并把首地址给p.
        make_child(p,num);                                           //创建n个子进程 
        int sfd=socket(AF_INET,SOCK_STREAM,0);    //生成套接字描述符 
        if(-1==sfd){
                perror("socket");
                return -1;
        }
        struct sockaddr_in ser;                                        //socket信息数据结构
        memset(&ser,0,sizeof(ser));
        ser.sin_family=AF_INET;                                  //IPV4
        ser.sin_port=htons(atoi(argv[2]));                       //端口
        ser.sin_addr.s_addr=inet_addr(argv[1]);              //地址
        int ret;
        ret=bind(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr)); //给sfd绑定IP地址和端口号
        if(-1==ret){
                perror("bind");
                return -1;
        }
        int epfd=epoll_create(1);                                    //创建epoll句柄 
        struct epoll_event event;
        struct epoll_event* evs=(struct epoll_event*)calloc(num+1,sizeof(event));   //本来是要写evs[num+1],但是数目是传进来的,不是确定                                                                                                                           的数值,所以动态分配。
        event.events=EPOLLIN;
        event.data.fd=sfd;
        ret=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event);   //注册sfd。想要监听有没有客户请求。 
        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); //注册p[0].fds,p[1].fds,p[2].fds等,想要监听进程传送完之后的通知。
                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));                          //把evs清空 
                int ret=epoll_wait(epfd,evs,num+1,-1);                          //监听epfd,结果放到evs中
                if(ret >0){
                        for(i=0;i<ret;i++){
                                if(evs[i].data.fd == sfd){                              //如果有客户端请求。
                                        new_fd=accept(sfd,NULL,NULL);       //接收请求,产生new_fd  
                                        for(j=0;j<num;j++){                            //找到一个不忙的进程 
                                                if(p[j].busy ==0)
                                                        break;
                                        }
                                        send_fd(p[j].fds,new_fd);                     //把new_fd发送给进程p[j].fds 
                                        p[j].busy=1;                                        //把该进程的状态该为忙碌 
                                        printf("give child is ok\n");
                                        close(new_fd);                                    //发过去之后,子进程就能使用new_fd了,这里就可以关闭,使引用计数为1 
                                }
                                for(j=0;j<num;j++){
                                        if(evs[i].data.fd == p[j].fds){                //如果有子进程请求,表示子进程已经干完活了。
                                                read(p[j].fds,&flag,sizeof(flag));   //把子进程发过来的数据读出来,虽然不使用这个数据,但是要读出来
                                                p[j].busy=0;                                //把该进程状态改为不忙碌
                                                printf("child is not busy\n");
                                        }
                                }
                        }
                }
        }
}

//客户端            
#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);
                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;
        }
}

int main(int argc,char** argv){                              //传入参数:IP地址、端口
        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]));
        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;
        }sdd
        while(1){
                memset(&d,0,sizeof(d));
                recv_n(sfd,&d.len,sizeof(int));               //接收小火车头 
                if(d.len >0){                                         //如果后面有数据 
                        recv_n(sfd,d.buf,d.len);                 //读出buf的长度。一定要保证读出len的长度,而不能读少了。
                        write(fd,d.buf,d.len);                     //读完之后写到文件中。
                }else{                                                  //如果只有火车头,后面没有数据,说明文件已经下载完了
                        break;
                }
        }
        close(sfd);
        close(fd);              
        return 0;
}















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值