Linux网络编程综合运用之MiniFtp实现(十)
上次已经实现了将当前目录打印出来的效果,这次则实现这些列表信息显示在FTP客户端中,先将测试代码注释掉:
在实现之前,还是先来看下vsftpd的效果:
这里先实现POST主动模式,上面就将目录列表显示给了FTP客户端,在显示目录列表之前,首先需要创建一个数据连接。主动模式是先发送一个PORT命令,紧接着是LIST的命令:
下面来照着实现,在实现之前,先来看下目前miniftpd的效果,先将站点配置成PORT主动模式:
再来连接:
【函数说明】:sscanf() - 从一个字符串中读进与指定格式相符的数据。
然后将这个地址信息先存起来,所以先在Session结构体中增加一个变量:
然后对其进行初始化:
接着将这个地址信息实例化:
接着来给客户端响应200:
这时do_port方法就实现完了,编译运行一下:
下面先来回顾一下PORT主动模式的步骤:
接着按着第三步来实现LIST命令,该函数的实现可以分为以下几步:
接着来实现创建数据连接的方法,第一步需要进行判断:
下面来实现port_active的判断:
接下来回到get_transfer_fd()函数:
其中服务器POST主动模式需要主动绑定20端口号,但是这里会存在一个问题,调用tcp_client(20)的是在FTP服务器进程,当一个用户,比如说webor2006登录时,会将这个进程的用户ID和组ID改变为webor2006所对应的UID和GID,这时它是没有权限绑定20端口号的,之前也描述过,一个客户端过来,应该采取两个进程的方式:nobody进程用于协助数据连接的创建,这里先以一个进程的方式实现,之后会改用两人进程,所以这里先不传端口号:
这时将这个数据套接字绑定到session当中,所以需要再新建一个变量:
接下来回到do_list()这个函数继续实现:
接下来需要传输列表:
实现也得修加修改:
int list_common(session_t *sess) { //打开当前目录 DIR *dir = opendir("."); if (dir == NULL) { return 0; } //读取目录并进行遍历 struct dirent *dt; struct stat sbuf; while ((dt = readdir(dir)) != NULL) { //获取文件的状态 if (lstat(dt->d_name, &sbuf) < 0) { continue; } if (dt->d_name[0] == '.') continue; char perms[] = "----------"; perms[0] = '?'; //获取文件类型 mode_t mode = sbuf.st_mode; switch (mode & S_IFMT) { case S_IFREG://普通文件 perms[0] = '-'; break; case S_IFDIR://目录文件 perms[0] = 'd'; break; case S_IFLNK://链接文件 perms[0] = 'l'; break; case S_IFIFO://管道文件 perms[0] = 'p'; break; case S_IFSOCK://套接字文件 perms[0] = 's'; break; case S_IFCHR://字符设备文件 perms[0] = 'c'; break; case S_IFBLK://块设备文件 perms[0] = 'b'; break; } //获取文件9个权限位 if (mode & S_IRUSR) { perms[1] = 'r'; } if (mode & S_IWUSR) { perms[2] = 'w'; } if (mode & S_IXUSR) { perms[3] = 'x'; } if (mode & S_IRGRP) { perms[4] = 'r'; } if (mode & S_IWGRP) { perms[5] = 'w'; } if (mode & S_IXGRP) { perms[6] = 'x'; } if (mode & S_IROTH) { perms[7] = 'r'; } if (mode & S_IWOTH) { perms[8] = 'w'; } if (mode & S_IXOTH) { perms[9] = 'x'; } //获取特珠权限位 if (mode & S_ISUID) { perms[3] = (perms[3] == 'x') ? 's' : 'S'; } if (mode & S_ISGID) { perms[6] = (perms[6] == 'x') ? 's' : 'S'; } if (mode & S_ISVTX) { perms[9] = (perms[9] == 'x') ? 't' : 'T'; } //格式化信息 char buf[1024] = {0}; int off = 0; off += sprintf(buf, "%s ", perms);//连接权限位 off += sprintf(buf + off, " %3d %-8d %-8d ", sbuf.st_nlink, sbuf.st_uid, sbuf.st_gid);//连接连接数、uid、gid off += sprintf(buf + off, "%8lu ", (unsigned long)sbuf.st_size);//连接文件大小,以8位的长度展现 const char *p_date_format = "%b %e %H:%M"; struct timeval tv; gettimeofday(&tv, NULL); time_t local_time = tv.tv_sec; if (sbuf.st_mtime > local_time || (local_time - sbuf.st_mtime) > 60*60*24*182) { p_date_format = "%b %e %Y"; } char datebuf[64] = {0}; struct tm* p_tm = localtime(&local_time); strftime(datebuf, sizeof(datebuf), p_date_format, p_tm); off += sprintf(buf + off, "%s ", datebuf); if (S_ISLNK(sbuf.st_mode)) { char tmp[1024] = {0}; readlink(dt->d_name, tmp, sizeof(tmp)); off += sprintf(buf + off, "%s -> %s\r\n", dt->d_name, tmp); } else { off += sprintf(buf + off, "%s\r\n", dt->d_name); } //printf("%s", buf); writen(sess->data_fd, buf, strlen(buf));//由原来的打印在屏幕中,改为输出到数据套接字中 } return 1; }
另外在关闭数据套接字之后,将数据还原成默认值:
另外在get_transfer_fd函数中,有个内存需要释放一下:
至此PORT目录列表显示的代码已经写完,下面编译运行看下效果:
这里有几个是需要注意的:
①、如果没有关闭套接字,目录列表是没法显示滴:
所以需要注意,将代码还原。
②、目前无法绑定20端:
编译运行:
好了,这次先学到这,下次继续~~