📄 ftp.c
字号:
}
//进入PASV模式,成功返回0
int enpasv(int sock,char *dip,int *dport)
{
int nret;
int i=0;
char buffer[1024];
sprintf(buffer,"PASV\r\n");
if(sendout(sock,buffer,strlen(buffer))<0) return -1; //write data error
int movepos=0;
while(linefeedpos(buffer,movepos)==-1) //没有结束一行
{
if(movepos>1000) return -3; //buffer runs out
if((nret=read(sock,buffer+movepos,1024-movepos))<=0) return -2; //read data error
movepos+=nret;
}
if(movepos<5) {
logmsg("服务器返回无效数据。\n");
return -5;
}
//读返回码
char code[4];
memcpy(code,buffer,3);
code[4]=0;
if(atoi(code)!=227) {
logmsg("服务器不能进入PASV模式。\n");
return -3;
}
//读参数 (218,64,254,162,243,239)
char s[32]={0};
while(buffer[i++]!='(' && i<movepos);
if(buffer[i-1]!='(') return -4;
memcpy(s,&buffer[i],movepos-i+1);
i=31;
while(s[i]!=')' && i>1) i--;
s[i]=0;
int a,b,c,d,p1,p2;
sscanf(s,"%d,%d,%d,%d,%d,%d",&a,&b,&c,&d,&p1,&p2);
sprintf(dip,"%d.%d.%d.%d",a,b,c,d);
*dport=(p1<<8) + p2;
return 0;
}
//检查服务器的续传能力,有续传返回>0,无返回0,网络错误<0
int chkresume(int sock)
{
int nret;
char buffer[256];
sprintf(buffer,"REST 100\r\n");
if(sendout(sock,buffer,strlen(buffer))<0) return -1; //write data error
nret=getretcode(sock);
if(nret<0) return nret;
else if(nret==350){
sprintf(buffer,"REST 0\r\n");
if(sendout(sock,buffer,strlen(buffer))<0) return -1; //write data error
nret=getretcode(sock);
if(nret==350)
return 1;
else
return 0;
}
else return 0;
}
//指定文件续传断点,成功返回>0
int setresume(int sock ,unsigned long pos)
{
int nret;
char buffer[256];
sprintf(buffer,"REST %ld\r\n",pos);
if(sendout(sock,buffer,strlen(buffer))<0) return -1; //write data error
nret=getretcode(sock);
if(nret<0) return nret;
else if(nret==350) return 1;
else return 0;
}
//进入BINARY模式,成功返回0
int enbinary(int sock)
{
int nret;
char buffer[256];
sprintf(buffer,"TYPE I\r\n");
if(sendout(sock,buffer,strlen(buffer))<0) return -1; //write data error
nret=getretcode(sock);
if(nret<0) return nret;
if(nret==200) return 0;
else return -1;
}
//filename like /test/test.fc
//返回<0 网络错误,=0不可获得文件大小 >0正确
int getsize(int sock,char* filename,unsigned long *lsize)
{
int nret;
unsigned long size;
char buffer[1024];
sprintf(buffer,"SIZE %s\r\n",filename);
if(sendout(sock,buffer,strlen(buffer))<0) return -1; //write data error
int movepos=0;
while(linefeedpos(buffer,movepos)==-1) //没有结束一行
{
if(movepos>1000) return -3; //buffer runs out
if((nret=read(sock,buffer+movepos,1024-movepos))<=0) return -2; //read data error
movepos+=nret;
}
assert(movepos>=5);
buffer[movepos]=0;
//读返回码
//读文件大小
sscanf(buffer,"%d %ld",&nret,&size);
if(nret==213) { *lsize=size;return 1;}
else return 0;
}
//控制连接上发送请求文件命令
int getfile(int sock,char*filename)
{
int nret;
char buffer[512];
sprintf(buffer,"RETR %s\r\n",filename);
if(sendout(sock,buffer,strlen(buffer))<0) return -1; //write data error
nret=getretcode(sock);
logmsg("getfile nret=%d\n",nret);
if(nret<0) return nret;
if(nret==150) return 0;
else return -1;
}
//将获得的数据写进文件中,可以考虑用map
static pthread_mutex_t wmutex;
int writedata(unsigned long offset,unsigned char *buf,unsigned long len)
{
static unsigned long total=0;
pthread_mutex_lock(&wmutex);
total+=len;
logmsg("totalget=%ld\r",total);
//写数据到文件。。。
pthread_mutex_unlock(&wmutex);
return 1;
}
//sock数据连接,spos起点偏移,epos终点偏移,bytes获得的字节数
//返回0正确完成任务,如果epos!=0 那么返回=epos-spos+1就完成了任务
//否则直到对方关闭连接或网络错误才算完成任务
//线程安全?
//调用这个函数,任务不能被中途剥夺,所以当线程分配到一个比较大的任务片时
//为了能够将来让出部分任务片给其他进程,最好小片地调用这个函数,完成小片
//后再检查任务的结尾点是否修改了
int getdata(int sock,int spos,int epos,unsigned long *bytes)
{
#define RECVBUF 32768 //16K缓冲区
unsigned char buffer[RECVBUF];
int movepos=0;
int nret;
unsigned long total=0; //本次已经接收的数据
unsigned long need=epos-spos+1; //需要接收的数据量
while(1){
nret=read(sock,buffer+movepos,RECVBUF-movepos);
if(nret==-1) {
if(errno!=EWOULDBLOCK){
//如果缓冲中有老数据,写出
if(movepos>0) {
writedata(spos+total,buffer,movepos);
}
total+=movepos;
*bytes=total;
return -1;
}
else continue;
}
else if(nret==0)
{ //数据传送正常关闭,传输完毕
//写剩余数据
writedata(spos+total,buffer,movepos);
total+=movepos;
*bytes=total;
return 0;
}
else if(nret<0)
{
logmsg("网络错误。\n");
//如果缓冲中有老数据,写出
if(movepos>0) {
writedata(spos+total,buffer,movepos);
}
total+=movepos;
*bytes=total;
return -1;
}
else
{
movepos+=nret;
//这次是否就够数据量了?
if(total+movepos>=need)
{
writedata(spos+total,buffer,need-total);
*bytes=need;
return 0;
}
if(movepos>=RECVBUF-1024)
{
//buffer full , write to disk
writedata(spos+total,buffer,movepos);
total+=movepos;
movepos=0;
}
}
}
return 0;
#undef RECVBUF
}
//工作线程
void* workants(void *indata)
{
char serverip[24];
int port;
unsigned long *spos,*epos; //任务的起点和终点,当epos=0时,没设定结束
char user[24];
char pass[24];
char enfile[1024]; //编码的文件名
int dport; //数据服务器端口
char dip[24]; //数据服务器地址
//读取数据
struct antsdata *pin=(struct antsdata*)indata;
strcpy(serverip,pin->serverip);
strcpy(user,pin->user);
strcpy(pass,pin->pass);
strcpy(enfile,pin->enfile);
port=pin->port;
spos=pin->spos;
epos=pin->epos;
logmsg("进入工作线程,offset=%ld\n",*spos);
//有了初始数据,开始工作
int sockctrl,sockdata;
int nret;
//make connection
if((sockctrl=makectrlconn(serverip,port))<0) {
logmsg("控制连接失败。\n");
pthread_exit(NULL);
}
logmsg("==>建立控制连接成功\n");
//login
if(login(sockctrl,user,pass)<0) {
logmsg("登录失败\n");
pthread_exit(NULL);
}
logmsg("==>登录成功\n");
//进入BINARY
if(enbinary(sockctrl)<0){
logmsg("不能进入二进制传送模式\n");
pthread_exit(NULL);
}
logmsg("==>进入二进制模式\n");
if((nret=setresume(sockctrl ,spos))==0)
{
if(spos!=0) {
logmsg("不能设定指定的断点!pos=%ld\n",spos);
pthread_exit(NULL);
}
}
else if(nret<0)
{
logmsg("网络失败。\n");
pthread_exit(NULL);
}
//send PASV command to get data
if(enpasv(sockctrl,dip,&dport)<0) {
logmsg("不能进入PASV模式\n");
pthread_exit(NULL);
}
//pasv ok,we then make data connection
logmsg("数据服务器=%s,数据端口=%d\n",dip,dport);
if((sockdata=makeconn(dip,dport))<0) {
logmsg("数据连接失败。\n");
pthread_exit(NULL);
}
//ok ,connected to data port
//控制连接上先发REST设置位置(如果支持多点传送)
//然后发RETR /filename请求传送,这时返回150表示命令成功
logmsg("数据连接建立。\n");
if(getfile(sockctrl,enfile)<0) {
logmsg("文件传送命令失败。\n");
pthread_exit(NULL);
}
logmsg("从%ld开始接受数据...\n",spos);
int retrytime=10;
unsigned long ndata;
retry:
nret=getdata(sockdata,spos,epos,&ndata);
if(nret==0)
{//正常完成任务
logmsg("正常完成任务片,接受了%ld字节\n",ndata);
close(sockctrl);
close(sockdata);
pthread_exit(NULL);
}
else
{//出现网络问题,无限重试
logmsg("网络故障,重试。\n");
spos+=ndata;
if(--retrytime<0) {logmsg("重试太多,退出\n");pthread_exit(NULL);}
goto retry;
}
}
int main(int argc,char* argv[])
{
if(argc!=2) {
logmsg("usage:\n %s urlstring\n",argv[0]); return -1;
}
char *url=argv[1];
int sockctrl,sockdata,nret,resume=0,port,dport;//port 控制端口 dport 数据服务器端口
unsigned long filesize=0;
char server[256],serverip[24],dip[24],user[24],pass[24],filename[512],encodefilename[1024];
//获得服务器基本信息
if(!islegalurl(url)) return -1;
logmsg("==>合法地址\n");
if(!getserverandportbyurl(url,server,&port,256)) return -1;
logmsg("==>服务器:%s,端口:%d\n",server,port);
if(!getuserandpassbyurl(url,user,24,pass,24)) return -1;
logmsg("==>用户:%s,密码:%s\n",user,pass);
if(!getfilebyurl(url,filename,512)) return -1;
logmsg("==>文件名:%s\n",filename);
//解码文件名
if(!getserverip(server,serverip,24)) return -1;
logmsg("==>服务器IP:%s\n",serverip);
//make connection
if((sockctrl=makectrlconn(serverip,port))<0) return -2;
logmsg("==>建立控制连接成功\n");
//login
if(login(sockctrl,user,pass)<0) return -3;
logmsg("==>登录成功\n");
//login ok,check if can resume
if((resume=chkresume(sockctrl))<0) return -4;
logmsg("==>服务器%s支持断点续传\n",resume?"":"不");
//switch to Binary to get filesize
if(enbinary(sockctrl)<0) return -5;
//get the filesize
if((nret=getsize(sockctrl,filename,&filesize))<0) return -6;
if(nret==0) {
logmsg("不能获得文件大小,无法多点传送。\n");
//调用单个线程下载函数并返回
return 0;//任务完成
}else{
close(sockctrl);
logmsg("文件大小:%ld\n",filesize);
}
//到这里确定是要采用多线程下载了,暂时搞5个
//工作线程传入结构
pthread_mutex_init (&wmutex,NULL);
#define THREADNUM 5
logmsg("==>准备多线程下载\n");
struct antsdata taskin;
taskin.serverip=serverip;
taskin.user=user;
taskin.pass=pass;
// taskin.enfile=encodefilename;
taskin.enfile=filename;
taskin.port=port;
int i;
for(i=0;i<THREADNUM;i++) {
g_tsk[i].start=0+i*(filesize/THREADNUM);
g_tsk[i].end=(i+1)*(filesize/THREADNUM)-1;
taskin.spos=&g_tsk[i].start;
taskin.epos=&g_tsk[i].end;
if(0!=pthread_create(&g_tid[i],NULL,workants,&taskin))
{
logmsg("thread create error\n");
}
pthread_join(g_tid[i],NULL);
}
#undef THREADNUM
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -