在Go中实现一个获取远程机器文件的tcp rpc

简介

rpc可以作为http或tcp层面的服务对外提供功能调用,主要分为server端和client端的代码, 今天我们来实现一个通过clinent端传送一个路径给rpc server,获取server的文件夹信息, 实现一个简单的tcp file server的功能

使用的包介绍

在go中,有2个rpc包, 一个为rpc, 一个为json rpc.

rpc包提供的rpc服务用gob编码传输, 只能使用go rpc client调用

json rpc包可以提供一个通用的json rpc服务端,方便跨编程语言的调用

因为我们今天的server和client都是go编写,所以我们采用rpc包来编写

Server端代码

首先我们需要定义3个结构体, 一个为rpc服务, 一个为rpc请求, 一个为rpc响应

type Listener struct{}

type GetFileRequest struct {
	Path    string
}

type FileResult struct {
	Type string
	Data []map[string]interface{}
}

这三个结构体作用分别是:

  1. 第一个:作为rpc服务, 我们所有的rpc服务函数都需要绑定这个结构体,如获取文件, 上传文件
  2. 第二个:作为请求体, 在rpc客户端调用我们的rpc服务函数时,需要实例化这个结构体,传入参数给我们, 我们也会在rpc服务函数中接收到这个参数
  3. 第三个:作为响应体, 在rpc客户端调用我们的rpc服务函数时,会接到我们返回的rpc响应结构体,我们在rpc服务函数中也需要实例化一个结构体,传入对应的参数, 返回给客户端

接着我们写一个处理函数, 处理客户端发来的获取文件请求

func (l *Listener) GetFile(req GetFileRequest, resp *FileResult) (err error) {
	defer func() {
		if _e := recover(); _e != nil {
			err = fmt.Errorf("%s", _e)
		}
	}()
	path := req.Path
	var pathResult []map[string]interface{}
	if _, _err := os.Stat(path); _err != nil {
		err = fmt.Errorf("%s", err)
	}
	if err != nil {
		resp.Data = nil
		return err
	}
	fileInfo, _ := os.Stat(path)
	if fileInfo.IsDir() {
		resp.Type = "path"
		fsSlice, _ := ioutil.ReadDir(path)
		pathResult = []map[string]interface{}{}
		if path != "/" {
			pathResult = append(pathResult, map[string]interface{}{
				"name":  "..",
				"size":  "0",
				"isDir": true,
				"key":   time.Now().Nanosecond(),
				"owner": "",
				"path":  filepath.Dir(path),
			})
		}
		if len(fsSlice) == 0 {
			resp.Data = pathResult
			return nil
		}
		for _, file := range fsSlice {
			name := file.Name()
			pathObj := map[string]interface{}{}
			pathObj["name"] = name
			pathObj["size"] = file.Size()
			pathObj["isDir"] = file.IsDir()
			pathObj["key"] = time.Now().Nanosecond()
			abspath, _ := filepath.Abs(path)
			if strings.HasSuffix(abspath, "/") {
				pathObj["path"] = abspath + name
			} else {
				pathObj["path"] = abspath + "/" + name
			}
			sysinfo := file.Sys().(*syscall.Stat_t)
			u := strconv.FormatUint(uint64(sysinfo.Uid), 10)
			g := strconv.FormatUint(uint64(sysinfo.Gid), 10)
			usr, _ := user.LookupId(u)
			group, _ := user.LookupGroupId(g)
			pathObj["owner"] = usr.Username + "/" + group.Name
			pathObj["time"] = file.ModTime().Format("2006-01-02 15:04:05")
			pathResult = append(pathResult, pathObj)
			resp.Data = pathResult
		}
	} else {
		resp.Type = "file"
		file, _ := os.Open(path)
		content, err := ioutil.ReadAll(file)
		if err != nil {
			return err
		}
		result := []map[string]interface{}{}
		contentmap := map[string]interface{}{}
		contentmap["content"] = content
		result = append(result, contentmap)
		resp.Data = result
	}

	return nil
}

这个函数的功能, 如果客户端传输过来的req.Path是文件夹,那么我们遍历这个文件夹,把里面的文件属性传送给客户端, 如果是文件,那么我们把文件内容直接返回给客户端

然后我们就再编写main函数, 开始启动rpc服务

func main() {
	rpc.Register(new(Listener))
	listen, err := net.Listen("tcp", ":10000")
	if err != nil {
		log.Fatal(err)
	}
	defer listen.Close()
	for {
		conn, err := listen.Accept()
		if err != nil {
			log.Printf("[-] err client: %s", err.Error())
			continue
		}
		go rpc.ServeConn(conn)
	}
}

客户端

package main

import (
	"fmt"
	"net/rpc"
)

type GetFileRequest struct {
	Path string
}

type FileResult struct {
	Type string
	Data []map[string]interface{}
}

func main() {
	conn, err := rpc.Dial("tcp", "10.0.9.201:10000")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer conn.Close()
	req := GetFileRequest{Path: "/tmp"}
	var result FileResult
	err = conn.Call("Listener.GetFile", req, &result)
	if err != nil {
		fmt.Println(err)
		return
	}
	if result.Type == "path" {
		for _, i := range result.Data {
			fmt.Println(i)
		}
	} else {
		fmt.Println(string((result.Data[0]["content"].([]byte))))
	}

}

获取文件夹运行结果:

获取文件运行结果

可以看到在go中编写rpc服务的套路, 定义rpc服务结构体, 以及不同的rpc请求或rpc相应结构体, 编写不同的处理函数,接着注册rpc服务, 启动服务, 接收并处理客户端请求


评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注