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
|
//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; |