Go程序设计语言练习题-第8章(8.2) - Go语言中文社区

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:t[command] [args]ncd [path]n")
}

func handleCommand(ftpCon *client.FtpClient, command string, args []string) (err error) {
    cmdid, ok := ftp.Commands[command]
    if !ok {
        return errors.New("unsupported commandn")
    }
    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("n%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("n%s:", ftpCon.Cwd)
        return nil
    }
    if cmdid == ftp.Commands["exit"] {
        ftpCon.Exit = true
        fmt.Printf("%sn", ftp.Sbyte2str(res))
        return nil
    }

    fmt.Printf("%sn%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:%dn", len(finfos))
        for _, info := range finfos {
            res = res + fmt.Sprintf("%.30st%.10dt%sn", info.Name(), info.Size(), info.ModTime())
        }
        err = ftpCon.Write(ftp.Str2sbyte(res))
    } else {
        res := fmt.Sprintf("%.30st%.10dt%sn", 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

 

转载于:https://www.cnblogs.com/ling-diary/p/10475925.html

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_33701294/article/details/93551143
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-02-02 18:55:15
  • 阅读 ( 859 )
  • 分类:Go

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢