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端:

编译运行:

好了,这次先学到这,下次继续~~

posted on 2015-07-12 13:57  cexo  阅读(516)  评论(0编辑  收藏  举报

导航