zoukankan      html  css  js  c++  java
  • Go程序设计语言练习题-第8章(8.2)

    ex8.2:要求实现一个ftp服务器,支持cd,ls,put,get等命令,目前实现了用户身份简单确认,获取家目录后可以进行cd,ls,mkdir以及上传和下载文件。

    TODO:

    1)未实现输入密码时不回显(类似C里的getpass函数);

    2)不支持文件夹的上传与下载;

    3)未实现与linux用户权限管理保持一致。

    目录结构:

    ----go工作目录/gobook/ch8/ex8.2
    --------client
    ------------ftp
    ----------------ftp.go
    ------------client.go
    --------ftp ------------ftp.go
    --------server ------------ftp ----------------ftp.go ------------server.go

    代码实现:

    ftp/ftp.go

    package ftp
    
    import (
        "encoding/binary"
        "net"
        "unsafe"
    )
    
    var Commands = map[string]uint8{
        "cd":    uint8(1),
        "ls":    uint8(2),
        "exit":  uint8(3),
        "mkdir": uint8(4),
        "put":   uint8(5),
        "get":   uint8(6),
    }
    
    type FtpConn struct {
        Con  net.Conn
        Cwd  string
        Home string
        Exit bool
    }
    
    func (ftpCon *FtpConn) Write(content []byte) error {
        var length uint32
        length = uint32(len(content))
        if length == 0 {
            return binary.Write(ftpCon.Con, binary.LittleEndian, &length)
        }
        length = length + uint32(binary.Size(length))
        err := binary.Write(ftpCon.Con, binary.LittleEndian, &length)
        if err != nil {
            return err
        }
        err = binary.Write(ftpCon.Con, binary.LittleEndian, content)
        if err != nil {
            return err
        }
        return nil
    }
    
    // string转[]byte
    // 利用string本来的底层数组
    func Str2sbyte(s string) (b []byte) {
        *(*string)(unsafe.Pointer(&b)) = s
        *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&b)) + 2*unsafe.Sizeof(&b))) = len(s)
        return
    }
    
    // []byte转string
    // 利用[]byte本来的底层数组
    func Sbyte2str(b []byte) string {
        return *(*string)(unsafe.Pointer(&b))
    }
    View Code

    client/client.go

    package main
    
    import (
        "bufio"
        "encoding/binary"
        "errors"
        "fmt"
        "log"
        "net"
        "os"
        "strings"
    
        "gobook/ch8/ex8.2/client/ftp"
        "gobook/ch8/ex8.2/ftp"
    )
    
    func printHelp() {
        log.Println("Help:	[command] [args]
    cd [path]
    ")
    }
    
    func handleCommand(ftpCon *client.FtpClient, command string, args []string) (err error) {
        cmdid, ok := ftp.Commands[command]
        if !ok {
            return errors.New("unsupported command
    ")
        }
        err = ftpCon.WriteCommand(cmdid, args)
        if err != nil {
            return err
        }
    
        if cmdid == ftp.Commands["get"] {
            err = ftpCon.HandleGet(args[0])
            if err != nil {
                return err
            }
        }
    
        var length uint32
        err = binary.Read(ftpCon.Con, binary.LittleEndian, &length)
        if err != nil {
            return err
        }
        if length == 0 {
            fmt.Printf("
    %s:", ftpCon.Cwd)
            return nil
        }
    
        res := make([]byte, length-uint32(binary.Size(length)))
        err = binary.Read(ftpCon.Con, binary.LittleEndian, res)
        if err != nil {
            return err
        }
        if cmdid == ftp.Commands["cd"] {
            ftpCon.Cwd = ftp.Sbyte2str(res)
            fmt.Printf("
    %s:", ftpCon.Cwd)
            return nil
        }
        if cmdid == ftp.Commands["exit"] {
            ftpCon.Exit = true
            fmt.Printf("%s
    ", ftp.Sbyte2str(res))
            return nil
        }
    
        fmt.Printf("%s
    %s:", ftp.Sbyte2str(res), ftpCon.Cwd)
        return
    }
    
    func main() {
        // 获取用户身份信息与ftp服务器host信息
        if len(os.Args) < 2 {
            fmt.Println("无法通过身份认证")
            return
        }
        arg := os.Args[1]
        if !strings.Contains(arg, "@") {
            fmt.Println("无法通过身份认证")
            return
        }
        args := strings.Split(arg, "@")
        user := args[0]
        host := args[1]
        fmt.Print("Password:")
        var pwd string
        input := bufio.NewScanner(os.Stdin)
        if input.Scan() {
            pwd = input.Text()
        }
    
        // 连接到ftp服务器
        con, err := net.Dial("tcp", host)
        if err != nil {
            fmt.Println(err)
            return
        }
        defer con.Close()
        ftpCon := ftp.FtpConn{
            Con: con,
        }
        ftpClient := client.FtpClient{
            ftpCon,
        }
    
        // 身份验证
        err = ftpClient.Write(ftp.Str2sbyte(user))
        if err != nil {
            fmt.Println(err)
            return
        }
        err = ftpClient.Write(ftp.Str2sbyte(pwd))
        if err != nil {
            fmt.Println(err)
            return
        }
    
        var res uint32
        err = binary.Read(con, binary.LittleEndian, &res)
        if err != nil {
            fmt.Println(err)
            return
        }
        if res == 0 {
            fmt.Println("认证失败")
            return
        }
    
        cwd := make([]byte, res)
        err = binary.Read(con, binary.LittleEndian, cwd)
        if err != nil {
            fmt.Println(err)
            return
        }
        ftpClient.Cwd = ftp.Sbyte2str(cwd)
        ftpClient.Home = ftpCon.Cwd
        fmt.Println(ftpClient.Cwd, ":")
    
        // 监听命令行输入
        for input.Scan() && !ftpClient.Exit {
            argstr := input.Text()
            args := strings.Split(strings.TrimSpace(argstr), " ")
            if len(args) == 0 {
                printHelp()
                continue
            }
            command := args[0]
            if len(args) > 1 {
                args = args[1:]
            } else {
                args = nil
            }
            err = handleCommand(&ftpClient, command, args)
            if err != nil {
                log.Println(err)
            }
        }
    
    }
    View Code

    client/ftp/ftp.go

    package client
    
    import (
        "bufio"
        "encoding/binary"
        "errors"
        "io"
        "os"
        "path"
        "strings"
    
        "gobook/ch8/ex8.2/ftp"
    )
    
    type FtpClient struct {
        ftp.FtpConn
    }
    
    func (ftpCon *FtpClient) WriteCommand(cmdid uint8, args []string) error {
        if cmdid == ftp.Commands["put"] {
            return ftpCon.WritePut(cmdid, args[0])
        }
    
        var length uint32
        argstr := strings.Join(args, "")
        length = uint32(binary.Size(length)+binary.Size(cmdid)) + uint32(len(argstr))
    
        err := binary.Write(ftpCon.Con, binary.LittleEndian, length)
        if err != nil {
            return err
        }
        err = binary.Write(ftpCon.Con, binary.LittleEndian, cmdid)
        if err != nil {
            return err
        }
        err = binary.Write(ftpCon.Con, binary.LittleEndian, ftp.Str2sbyte(argstr))
        if err != nil {
            return err
        }
        return nil
    }
    
    func (ftpCon *FtpClient) WritePut(cmdid uint8, filePath string) error {
        filePath = strings.Replace(filePath, "\", "/", -1)
        f, err := os.Open(filePath)
        if err != nil {
            return err
        }
        defer f.Close()
    
        // 发送命令与文件名
        var length uint32
        fileName := ftp.Str2sbyte(path.Base(filePath))
        length = uint32(binary.Size(length)+binary.Size(cmdid)) + uint32(len(fileName))
    
        err = binary.Write(ftpCon.Con, binary.LittleEndian, length)
        if err != nil {
            return err
        }
        err = binary.Write(ftpCon.Con, binary.LittleEndian, cmdid)
        if err != nil {
            return err
        }
        err = binary.Write(ftpCon.Con, binary.LittleEndian, fileName)
        if err != nil {
            return err
        }
    
        // 发送文件长度
        fileInfo, err := f.Stat()
        if err != nil {
            return err
        }
        if fileInfo.IsDir() {
            return errors.New("put 命令不支持发送文件夹,请尝试putdir命令")
        } else {
            err = binary.Write(ftpCon.Con, binary.LittleEndian, fileInfo.Size())
            if err != nil {
                return err
            }
        }
    
        // 发送文件内容
        buf := make([]byte, 4096)
        bufReader := bufio.NewReader(f)
        for {
            n, err := bufReader.Read(buf)
            if err != nil {
                if err == io.EOF {
                    break
                }
                return err
            }
            err = binary.Write(ftpCon.Con, binary.LittleEndian, buf[0:n])
            if err != nil {
                return err
            }
        }
        return nil
    }
    
    func (ftpCon *FtpClient) HandleGet(filePath string) error {
        fileName := path.Base(filePath)
        f, err := os.Create(fileName)
        if err != nil {
            if os.IsExist(err) {
                err = f.Truncate(0)
                if err != nil {
                    return err
                }
            } else {
                return err
            }
        }
        defer f.Close()
    
        var length int64
        err = binary.Read(ftpCon.Con, binary.LittleEndian, &length)
        if err != nil {
            return err
        }
        var total, bufSize int64
        if length > 4096 {
            bufSize = 4096
        } else {
            bufSize = length
        }
        buf := make([]byte, bufSize)
        for total < length {
            err = binary.Read(ftpCon.Con, binary.LittleEndian, buf)
            if err != nil {
                return err
            }
            n, err := f.Write(buf)
            if err != nil {
                return err
            }
            total += int64(n)
            if length-total < bufSize {
                buf = buf[0 : length-total]
            }
        }
        return nil
    }
    View Code

    server/server.go

    // ftp server
    package main
    
    import (
        "encoding/binary"
        "log"
        "net"
    
        "gobook/ch8/ex8.2/ftp"
        "gobook/ch8/ex8.2/server/ftp"
    )
    
    func handleFunc(con net.Conn) {
        defer con.Close()
    
        // 身份验证
        // 读取用户名
        var length uint32
        err := binary.Read(con, binary.LittleEndian, &length)
        if err != nil {
            err = binary.Write(con, binary.LittleEndian, uint32(0))
            if err != nil {
                log.Println(err)
            }
            return
        }
        user := make([]byte, length-uint32(binary.Size(length)))
        err = binary.Read(con, binary.LittleEndian, user)
        if err != nil {
            err = binary.Write(con, binary.LittleEndian, uint32(0))
            if err != nil {
                log.Println(err)
            }
            return
        }
    
        // 读取密码
        err = binary.Read(con, binary.LittleEndian, &length)
        if err != nil {
            err = binary.Write(con, binary.LittleEndian, uint32(0))
            if err != nil {
                log.Println(err)
            }
            return
        }
        pwd := make([]byte, length-uint32(binary.Size(length)))
        err = binary.Read(con, binary.LittleEndian, pwd)
        if err != nil {
            err = binary.Write(con, binary.LittleEndian, uint32(0))
            if err != nil {
                log.Println(err)
            }
            return
        }
    
        // 验证用户名密码获取家目录
        validated, cwd := server.Validate(ftp.Sbyte2str(user), ftp.Sbyte2str(pwd))
        if !validated {
            err = binary.Write(con, binary.LittleEndian, uint32(0))
            if err != nil {
                log.Println(err)
            }
            return
        }
    
        home := ftp.Str2sbyte(cwd)
        err = binary.Write(con, binary.LittleEndian, uint32(binary.Size(home)))
        if err != nil {
            log.Println(err)
            return
        }
        err = binary.Write(con, binary.LittleEndian, home)
        if err != nil {
            log.Println(err)
            return
        }
    
        ftpCon := ftp.FtpConn{
            Con:  con,
            Home: cwd,
            Cwd:  cwd,
        }
        ftpServer := server.FtpServer{
            ftpCon,
        }
        // 循环监听命令请求
        for !ftpServer.Exit {
            var length uint32
            err = binary.Read(con, binary.LittleEndian, &length)
            if err != nil {
                log.Println(err)
                return
            }
            var cmdid uint8
            err = binary.Read(con, binary.LittleEndian, &cmdid)
            if err != nil {
                log.Println(err)
                return
            }
            args := make([]byte, length-uint32(binary.Size(cmdid))-uint32(binary.Size(length)))
            err = binary.Read(con, binary.LittleEndian, args)
            if err != nil {
                log.Println(err)
                return
            }
    
            switch cmdid {
            case ftp.Commands["cd"]:
                err = ftpServer.HandleCd(args)
            case ftp.Commands["ls"]:
                err = ftpServer.HandleLs(args)
            case ftp.Commands["exit"]:
                err = ftpServer.HandleExit(args)
            case ftp.Commands["mkdir"]:
                err = ftpServer.HandleMkdir(args)
            case ftp.Commands["put"]:
                err = ftpServer.HandlePut(args)
            case ftp.Commands["get"]:
                err = ftpServer.HandleGet(args)
            default:
                err = ftpServer.Write([]byte("no command handler."))
            }
    
            if err != nil {
                log.Println(err)
            }
        }
    }
    
    func main() {
        listener, err := net.Listen("tcp", "localhost:5900")
        if err != nil {
            log.Fatal(err)
        }
    
        for {
            con, err := listener.Accept()
            if err != nil {
                log.Println(err)
                continue
            }
            go handleFunc(con)
        }
    }
    View Code

    server/ftp/ftp.go

    package server
    
    import (
        "bufio"
        "encoding/binary"
        "fmt"
        "io"
        "log"
        "os"
        "path"
        "runtime"
        "strings"
        "sync"
    
        "gobook/ch8/ex8.2/ftp"
    )
    
    var Commands = map[string]uint8{
        "cd":    uint8(1),
        "ls":    uint8(2),
        "exit":  uint8(3),
        "mkdir": uint8(4),
        "put":   uint8(5),
        "get":   uint8(6),
    }
    
    var DefaultDir = map[string]string{
        "windows": "C:/Users/Kylin/workspace/go/src/gobook/ch8",
        "unix":    "home/www",
    }
    
    type userInfo struct {
        name string
        pwd  string
        home string
    }
    
    var lock sync.Once // 初始化users一次
    var users []userInfo
    
    func init() {
        lock.Do(initUsers)
    }
    
    type FtpServer struct {
        ftp.FtpConn
    }
    
    func (ftpCon *FtpServer) HandleCd(args []byte) error {
        cwd := ftp.Sbyte2str(args)
        if strings.HasPrefix(cwd, "/") {
            cwd = path.Join(ftpCon.Cwd, cwd)
        }
        f, err := os.Open(cwd)
        if err != nil {
            ftpCon.Write(ftp.Str2sbyte(err.Error()))
            return nil
        }
        defer f.Close()
        finfo, err := f.Stat()
        if err != nil {
            ftpCon.Write(ftp.Str2sbyte(err.Error()))
            return nil
        }
        if !finfo.IsDir() {
            ftpCon.Write(ftp.Str2sbyte("cd parameter must be directory."))
            return nil
        }
        ftpCon.Cwd = cwd
        return ftpCon.Write(ftp.Str2sbyte(cwd))
    }
    
    func (ftpCon *FtpServer) HandleLs(args []byte) error {
        cwd := ftp.Sbyte2str(args)
        if strings.HasPrefix(cwd, "/") {
            cwd = path.Join(ftpCon.Cwd, cwd)
        }
        f, err := os.Open(cwd)
        if err != nil {
            ftpCon.Write(ftp.Str2sbyte(err.Error()))
            return nil
        }
        finfo, err := f.Stat()
        if err != nil {
            ftpCon.Write(ftp.Str2sbyte(err.Error()))
            return nil
        }
        if finfo.IsDir() {
            finfos, err := f.Readdir(0)
            if err != nil {
                ftpCon.Write(ftp.Str2sbyte(err.Error()))
            }
            var res string
            res = fmt.Sprintf("Total:%d
    ", len(finfos))
            for _, info := range finfos {
                res = res + fmt.Sprintf("%.30s	%.10d	%s
    ", info.Name(), info.Size(), info.ModTime())
            }
            err = ftpCon.Write(ftp.Str2sbyte(res))
        } else {
            res := fmt.Sprintf("%.30s	%.10d	%s
    ", finfo.Name(), finfo.Size(), finfo.ModTime())
            err = ftpCon.Write(ftp.Str2sbyte(res))
        }
        if err != nil {
            err = ftpCon.Write(ftp.Str2sbyte(err.Error()))
        }
        return err
    }
    
    func (ftpCon *FtpServer) HandleExit(args []byte) error {
        ftpCon.Exit = true
        ftpCon.Write(ftp.Str2sbyte("Byebye."))
        return nil
    }
    
    func (ftpCon *FtpServer) HandleMkdir(args []byte) error {
        dir := ftp.Sbyte2str(args)
        if strings.HasPrefix(dir, "/") {
            dir = path.Join(ftpCon.Home, dir)
        } else {
            dir = path.Join(ftpCon.Cwd, dir)
        }
    
        err := os.Mkdir(dir, os.ModePerm)
        if err != nil {
            return err
        }
        return ftpCon.Write(ftp.Str2sbyte("Ok"))
    }
    
    func (ftpCon *FtpServer) HandlePut(args []byte) error {
        fileName := ftp.Sbyte2str(args)
        f, err := os.Create(path.Join(ftpCon.Cwd, fileName))
        if err != nil {
            return err
        }
        defer f.Close()
    
        var length int64
        err = binary.Read(ftpCon.Con, binary.LittleEndian, &length)
        if err != nil {
            return err
        }
        var total, bufSize int64
        if length > 4096 {
            bufSize = 4096
        } else {
            bufSize = length
        }
        buf := make([]byte, bufSize)
        for total < length {
            err = binary.Read(ftpCon.Con, binary.LittleEndian, buf)
            if err != nil {
                return err
            }
            n, err := f.Write(buf)
            if err != nil {
                return err
            }
            total += int64(n)
            if (length - total) < bufSize {
                buf = buf[0 : length-total]
            }
        }
    
        ftpCon.Write(ftp.Str2sbyte("Ok."))
        return nil
    }
    
    func (ftpCon *FtpServer) HandleGet(args []byte) error {
        filePath := ftp.Sbyte2str(args)
        if strings.HasPrefix(filePath, "/") {
            filePath = path.Join(ftpCon.Home, filePath)
        } else {
            filePath = path.Join(ftpCon.Cwd, filePath)
        }
        f, err := os.Open(filePath)
        if err != nil {
            return err
        }
        defer f.Close()
        finfo, err := f.Stat()
        if err != nil {
            return err
        }
        // TODO 暂不支持下载文件夹
        if finfo.IsDir() {
            return binary.Write(ftpCon.Con, binary.LittleEndian, int64(0))
        }
    
        err = binary.Write(ftpCon.Con, binary.LittleEndian, finfo.Size())
        if err != nil {
            return err
        }
        bufReader := bufio.NewReader(f)
        buf := make([]byte, 4096)
        for {
            n, err := bufReader.Read(buf)
            if err != nil {
                if err == io.EOF {
                    break
                } else {
                    return err
                }
            }
            err = binary.Write(ftpCon.Con, binary.LittleEndian, buf[0:n])
            if err != nil {
                return err
            }
        }
        ftpCon.Write(ftp.Str2sbyte("Ok."))
        return nil
    }
    
    func initUsers() {
        cwd, ok := DefaultDir[runtime.GOOS]
        if !ok {
            log.Fatal("Unsupported system.")
        }
    
        // TODO 打开相对路径的问题
        f, err := os.Open("C:/Users/Kylin/workspace/go/src/gobook/ch8/ex8.2/server/ftp/users")
        if err != nil {
            log.Fatal("failed to load users' information.", err)
        }
        defer f.Close()
        scanner := bufio.NewScanner(f)
        for scanner.Scan() {
            line := scanner.Text()
            userinfo := strings.Split(line, ";;")
            if len(userinfo) < 3 {
                continue
            }
            home := path.Join(cwd, userinfo[2])
            f, err := os.Open(home)
            if err != nil && os.IsNotExist(err) {
                err = os.Mkdir(home, os.ModePerm)
                if err != nil {
                    log.Fatal("failed to make directory", home)
                }
            } else {
                f.Close()
            }
            users = append(users, userInfo{userinfo[0], userinfo[1], home})
        }
    }
    
    // 验证用户名和密码,返回验证结果true/false和验证通过后的用户家目录
    func Validate(name string, pwd string) (pass bool, home string) {
        if len(users) <= 0 {
            return
        }
    
        for _, info := range users {
            if info.name == name && info.pwd == pwd {
                return true, info.home
            }
        }
        return
    }
    View Code
  • 相关阅读:
    51nod 1134 最长递增子序列
    51nod 1135 原根
    51nod 1136 欧拉函数
    51nod 1137 矩阵乘法
    51nod 1174 区间中最大的数
    51nod 1079 中国剩余定理
    51nod 1181 质数中的质数(质数筛法)
    伪共享(False Sharing)和缓存行(Cache Line)
    mybatis 批量 操作数据
    java开发中beancopy比较
  • 原文地址:https://www.cnblogs.com/ling-diary/p/10475925.html
Copyright © 2011-2022 走看看