内存网盘 - Go语言实现的WebDAV内存文件系统

这个 Go 小玩具,让内存秒变网盘!关机就消失,重命名随便改,还能自动挂成 Z 盘------这不是魔法,是 Go 写的 WebDAV 内存文件系统!

你有没有想过:如果有一个网盘,不用注册、不占硬盘、速度飞快,但只要一关程序,所有数据就"人间蒸发"?听起来像间谍工具?其实它只是个用 Go 写的小玩具------而且现在,它还能让你在 Windows 资源管理器里直接重命名文件,就像操作本地文件一样丝滑!

今天,我们就来揭开这个"内存网盘"的神秘面纱。放心,代码不多,笑点管够,原理讲透,连你家猫都能看懂(大概)。

🎯 功能特性

  • ✅ 所有文件存在内存里(RAM),关进程就清零,干净得像没来过
  • ✅ 支持完整的 WebDAV 协议:新建、删除、读写、建文件夹......
  • ✅ 新增重磅功能:支持重命名文件/文件夹!拖拽、右键改名统统 OK
  • ✅ 启动时自动在 Windows 上映射为 Z: 盘(可自定义),像本地磁盘一样用!
  • ✅ 自带 README.txt 彩蛋,内含作者签名(不是病毒,真的!)

适用场景

  • 临时共享
  • 快速测试
  • 演示环境

🛠️ 技术原理

1. 内存文件系统 = 一棵树

我们用 Go 的 map[string]*File 模拟文件目录结构,每个 File 可以是普通文件或目录。整棵树从根节点 / 开始,长得像这样:

复制代码
/
└── README.txt

所有操作(创建、读取、删除、重命名)都是在这棵树上"修枝剪叶"。

2. WebDAV = HTTP 的"文件操作扩展包"

普通 HTTP 只能 GET/POST,而 WebDAV 增加了 MKCOL(建目录)、PUT(上传)、MOVE(重命名)等方法。Go 的 golang.org/x/net/webdav 包帮我们实现了协议解析,我们只需提供底层文件系统的实现。

3. 重命名 = "搬家+改名"

重命名的本质是:

  1. 从旧父目录中删掉条目
  2. 在新父目录中加上新名字
  3. 如果跨目录,还要更新父指针

关键是要同时锁住两个父目录,防止并发时"文件失踪"。我们的代码做到了这一点,安全又高效。

4. Windows 映射 = net use 命令自动化

程序启动后,偷偷执行:

cmd 复制代码
net use Z: http://localhost:8080 /y

于是你的资源管理器就多了一个"网络驱动器"。退出时再执行 /delete,不留痕迹。

📦 使用方法

第一步:准备环境

确保已安装 Go(1.16+),并启用 Go Modules。

第二步:保存以下三个文件

注意:三个文件需放在同一模块下,例如项目结构如下:

go 复制代码
your-project/
├── go.mod
├── main.go
└── memdisk/
    ├── memfs.go
    └── webdav.go

第三步:运行!

bash 复制代码
go run main.go -port=8080 -drive=Z:

然后打开"此电脑",看看是不是多了个 Z: 盘?双击进去,试试新建文件、重命名------是不是和本地磁盘一模一样?

📄 源码展示

main.go

go 复制代码
package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"sync"
	"syscall"
	"time"

	"golang.org/x/net/webdav"
	
	"memdisk/memdisk"
)

// 使用Windows net use命令映射网络驱动器
func mapNetworkDrive(serverURL, drive string) {
	// 首先,如果已经连接则断开连接
	exec.Command("net", "use", drive, "/delete", "/y").Run()

	// 然后映射网络驱动器
	cmd := exec.Command("net", "use", drive, serverURL)
	if err := cmd.Run(); err != nil {
		log.Printf("映射网络驱动器失败: %v", err)
		// 尝试不同的方法
		log.Printf("您可以手动映射驱动器: net use %s %s", drive, serverURL)
	} else {
		log.Printf("成功将 %s 映射到 %s", drive, serverURL)
	}
}

// 使用Windows net use命令取消映射网络驱动器
func unmapNetworkDrive(drive string) {
	log.Printf("正在取消映射网络驱动器: %s", drive)
	cmd := exec.Command("net", "use", drive, "/delete", "/y")
	if err := cmd.Run(); err != nil {
		log.Printf("取消映射网络驱动器失败: %v", err)
	} else {
		log.Printf("成功取消映射 %s", drive)
	}
}

func main() {
	// 解析命令行参数
	var port string
	var drive string

	flag.StringVar(&port, "port", "8080", "服务器端口")
	flag.StringVar(&drive, "drive", "Z:", "映射的网络驱动器盘符")
	flag.Parse()

	// 如果使用旧的参数方式,仍然支持
	if flag.NArg() > 0 {
		port = flag.Arg(0)
	}
	if flag.NArg() > 1 {
		drive = flag.Arg(1)
	}

	// 创建一个新的内存文件系统
	fs := memdisk.NewMemFS()

	// 只创建一个README文件
	fs.WriteFile("/README.txt", []byte("内存文件系统\r\n\r\n这是一个运行在内存中的文件服务器。\r\n所有文件都存储在RAM中,服务器停止时将丢失。\r\nJjMgx"))

	// 用于网络驱动器映射的服务器URL
	serverURL := fmt.Sprintf("http://localhost:%s", port)

	// 启动WebDAV服务器
	startWebDAVServer(fs, port, drive, serverURL)

}

// 启动WebDAV服务器
func startWebDAVServer(fs *memdisk.MemFS, port, drive, serverURL string) {
	// 创建WebDAV文件系统
	wdFs := memdisk.NewWebDAVFS(fs)

	// 创建WebDAV处理器
	handler := &webdav.Handler{
		FileSystem: wdFs,
		LockSystem: webdav.NewMemLS(),
		Logger: func(r *http.Request, err error) {
			if err != nil {
				log.Printf("WebDAV错误: %v %v", r.Method, err)
			} else {
				log.Printf("WebDAV请求: %v %v", r.Method, r.URL)
			}
		},
	}

	// 创建服务器多路复用器
	mux := http.NewServeMux()

	// 为所有路径注册WebDAV处理器
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// 添加CORS头部
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, MKCOL, COPY, MOVE, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Depth, Overwrite, Destination, Authorization")

		// 添加可能影响显示名称的自定义头部
		w.Header().Set("Server", "CustomWebDAVServer")
		w.Header().Set("X-Server-Name", "MemoryFileSystem")

		// 处理OPTIONS请求
		if r.Method == "OPTIONS" {
			w.WriteHeader(http.StatusOK)
			return
		}

		// 调用WebDAV处理器
		handler.ServeHTTP(w, r)
	})

	// 监听中断信号的通道
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	// 确保清理完成的等待组
	var wg sync.WaitGroup

	// 启动服务器goroutine并在准备好时映射网络驱动器
	log.Printf("正在启动WebDAV服务器于 :%s", port)
	log.Printf("访问服务器地址 http://localhost:%s", port)
	log.Printf("尝试映射为网络驱动器: %s", drive)

	// 服务器goroutine
	server := &http.Server{
		Addr:    fmt.Sprintf(":%s", port),
		Handler: mux,
	}

	go func() {
		wg.Add(1)
		defer wg.Done()
		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("服务器启动失败: %v", err)
		}
	}()

	// 给服务器一点时间启动,然后映射网络驱动器
	go func() {
		time.Sleep(500 * time.Millisecond)
		log.Printf("正在映射网络驱动器: %s 到 %s", drive, serverURL)
		mapNetworkDrive(serverURL, drive)
	}()

	// 等待中断信号
	<-sigChan
	log.Println("正在关闭服务器...")

	// 取消映射网络驱动器
	unmapNetworkDrive(drive)

	// 优雅地关闭服务器
	if err := server.Shutdown(context.Background()); err != nil {
		log.Printf("服务器关闭错误: %v", err)
	}

	// 等待所有goroutine完成
	wg.Wait()
	log.Println("服务器已优雅退出")
}

memdisk/memfs.go

go 复制代码
package memdisk

import (
	"os"
	"sync"
	"time"
)

// File 表示内存文件系统中的一个文件
type File struct {
	Filename     string
	Content      []byte
	FileSize     int64
	CreatedAt    time.Time
	ModifiedAt   time.Time
	IsDirectory  bool
	Children     map[string]*File
	Parent       *File
	mu           sync.RWMutex
}

// MemFS 表示内存文件系统
type MemFS struct {
	Root  *File
	mu    sync.RWMutex
}

// NewMemFS 创建一个新的MemFS实例
func NewMemFS() *MemFS {
	root := &File{
		Filename:     "",
		IsDirectory:  true,
		Children:     make(map[string]*File),
		CreatedAt:    time.Now(),
		ModifiedAt:   time.Now(),
	}
	return &MemFS{
		Root: root,
	}
}

// splitPath 将路径分割为其组成部分
func splitPath(path string) []string {
	if path == "/" {
		return []string{}
	}

	components := []string{}
	current := ""
	for _, c := range path {
		if c == '/' {
			if current != "" {
				components = append(components, current)
				current = ""
			}
		} else {
			current += string(c)
		}
	}
	if current != "" {
		components = append(components, current)
	}
	return components
}

// GetFile 根据路径获取文件或目录
func (fs *MemFS) GetFile(path string) (*File, error) {
	if path == "" {
		return nil, os.ErrInvalid
	}

	fs.mu.RLock()
	defer fs.mu.RUnlock()

	components := splitPath(path)
	current := fs.Root

	for _, comp := range components {
		current.mu.RLock()
		child, exists := current.Children[comp]
		current.mu.RUnlock()
		if !exists {
			return nil, os.ErrNotExist
		}
		current = child
	}

	return current, nil
}

// CreateDir 在给定路径中创建新目录
func (fs *MemFS) CreateDir(path string) (*File, error) {
	if path == "" {
		return nil, os.ErrInvalid
	}

	components := splitPath(path)
	if len(components) == 0 {
		return fs.Root, nil // 根目录已存在
	}

	// 在不持有全局锁的情况下获取父级
	parentPathComponents := components[:len(components)-1]
	parentPath := "/" + joinPath(parentPathComponents)
	parent, err := fs.GetFile(parentPath)
	if err != nil {
		return nil, err
	}

	fs.mu.Lock()
	defer fs.mu.Unlock()

	parent.mu.Lock()
	defer parent.mu.Unlock()

	newDirName := components[len(components)-1]
	if _, exists := parent.Children[newDirName]; exists {
		return nil, os.ErrExist
	}

	newDir := &File{
		Filename:     newDirName,
		IsDirectory:  true,
		Children:     make(map[string]*File),
		CreatedAt:    time.Now(),
		ModifiedAt:   time.Now(),
		Parent:       parent,
	}

	parent.Children[newDirName] = newDir
	return newDir, nil
}

// CreateFile 在给定路径中创建新文件
func (fs *MemFS) CreateFile(path string) (*File, error) {
	if path == "" {
		return nil, os.ErrInvalid
	}

	components := splitPath(path)
	if len(components) == 0 {
		return nil, os.ErrInvalid
	}

	// 在不持有全局锁的情况下获取父级
	parentPathComponents := components[:len(components)-1]
	parentPath := "/" + joinPath(parentPathComponents)
	parent, err := fs.GetFile(parentPath)
	if err != nil {
		return nil, err
	}

	fs.mu.Lock()
	defer fs.mu.Unlock()

	parent.mu.Lock()
	defer parent.mu.Unlock()

	newFileName := components[len(components)-1]
	if _, exists := parent.Children[newFileName]; exists {
		return nil, os.ErrExist
	}

	newFile := &File{
		Filename:     newFileName,
		IsDirectory:  false,
		Content:      []byte{},
		FileSize:     0,
		CreatedAt:    time.Now(),
		ModifiedAt:   time.Now(),
		Parent:       parent,
	}

	parent.Children[newFileName] = newFile
	return newFile, nil
}

// ReadFile 读取文件内容
func (fs *MemFS) ReadFile(path string) ([]byte, error) {
	file, err := fs.GetFile(path)
	if err != nil {
		return nil, err
	}

	file.mu.RLock()
	defer file.mu.RUnlock()

	if file.IsDirectory {
		return nil, os.ErrInvalid
	}

	content := make([]byte, len(file.Content))
	copy(content, file.Content)
	return content, nil
}

// WriteFile 将内容写入文件
func (fs *MemFS) WriteFile(path string, content []byte) error {
	file, err := fs.GetFile(path)
	if err != nil {
		// 如果文件不存在则尝试创建
		file, err = fs.CreateFile(path)
		if err != nil {
			return err
		}
	}

	file.mu.Lock()
	defer file.mu.Unlock()

	if file.IsDirectory {
		return os.ErrInvalid
	}

	file.Content = make([]byte, len(content))
	copy(file.Content, content)
	file.FileSize = int64(len(content))
	file.ModifiedAt = time.Now()
	return nil
}

// Delete 删除文件或目录
func (fs *MemFS) Delete(path string) error {
	if path == "/" {
		return os.ErrPermission
	}

	components := splitPath(path)
	if len(components) == 0 {
		return os.ErrInvalid
	}

	// 在不持有全局锁的情况下获取父级
	parentPathComponents := components[:len(components)-1]
	parentPath := "/" + joinPath(parentPathComponents)
	parent, err := fs.GetFile(parentPath)
	if err != nil {
		return err
	}

	fs.mu.Lock()
	defer fs.mu.Unlock()

	parent.mu.Lock()
	defer parent.mu.Unlock()

	fileName := components[len(components)-1]
	if _, exists := parent.Children[fileName]; !exists {
		return os.ErrNotExist
	}

	delete(parent.Children, fileName)
	return nil
}

// Rename 重命名文件或目录
func (fs *MemFS) Rename(oldPath, newPath string) error {
	if oldPath == "/" || newPath == "/" {
		return os.ErrPermission
	}

	oldComponents := splitPath(oldPath)
	newComponents := splitPath(newPath)
	
	if len(oldComponents) == 0 || len(newComponents) == 0 {
		return os.ErrInvalid
	}

	// 获取旧文件的父目录和文件名
	oldParentPathComponents := oldComponents[:len(oldComponents)-1]
	oldParentPath := "/" + joinPath(oldParentPathComponents)
	oldParent, err := fs.GetFile(oldParentPath)
	if err != nil {
		return err
	}

	// 获取新文件的父目录和文件名
	newParentPathComponents := newComponents[:len(newComponents)-1]
	newParentPath := "/" + joinPath(newParentPathComponents)
	newParent, err := fs.GetFile(newParentPath)
	if err != nil {
		return err
	}

	// 对相同对象只锁定一次
	sameParent := oldParent == newParent

	fs.mu.Lock()
	defer fs.mu.Unlock()

	oldParent.mu.Lock()
	defer oldParent.mu.Unlock()

	// 如果不是同一父目录,则锁定新父目录
	if !sameParent {
		newParent.mu.Lock()
		defer newParent.mu.Unlock()
	}

	oldFileName := oldComponents[len(oldComponents)-1]
	newFileName := newComponents[len(newComponents)-1]

	// 检查旧文件是否存在
	oldFile, exists := oldParent.Children[oldFileName]
	if !exists {
		return os.ErrNotExist
	}

	// 检查新文件是否已存在
	if _, exists := newParent.Children[newFileName]; exists {
		return os.ErrExist
	}

	// 从旧位置移除
	delete(oldParent.Children, oldFileName)

	// 更新文件名
	oldFile.Filename = newFileName
	
	// 更新父指针(如果不是同一父目录)
	if !sameParent {
		oldFile.Parent = newParent
	}
	
	// 添加到新位置
	newParent.Children[newFileName] = oldFile
	
	// 更新修改时间
	oldFile.ModifiedAt = time.Now()

	return nil
}

// ListDir 列出目录内容
func (fs *MemFS) ListDir(path string) ([]*File, error) {
	dir, err := fs.GetFile(path)
	if err != nil {
		return nil, err
	}

	dir.mu.RLock()
	defer dir.mu.RUnlock()

	if !dir.IsDirectory {
		return nil, os.ErrInvalid
	}

	children := make([]*File, 0, len(dir.Children))
	for _, child := range dir.Children {
		child.mu.RLock()
		// 创建副本以避免竞态条件
		copyChild := &File{
		Filename:     child.Filename,
		FileSize:     child.FileSize,
		CreatedAt:    child.CreatedAt,
		ModifiedAt:   child.ModifiedAt,
		IsDirectory:  child.IsDirectory,
	}
		child.mu.RUnlock()
		children = append(children, copyChild)
	}

	return children, nil
}

// joinPath 将路径组件连接成单个路径字符串
func joinPath(components []string) string {
	result := ""
	for i, comp := range components {
		if i > 0 {
			result += "/"
		}
		result += comp
	}
	return result
}

memdisk/webdav.go

go 复制代码
package memdisk

import (
	"context"
	"io"
	"os"
	"time"
	"golang.org/x/net/webdav"
)

// 为File实现os.FileInfo接口
func (f *File) Name() string {
	return f.Filename
}

func (f *File) Size() int64 {
	return f.FileSize
}

func (f *File) Mode() os.FileMode {
	if f.IsDirectory {
		return 0755 | os.ModeDir
	}
	return 0644
}

func (f *File) ModTime() time.Time {
	return f.ModifiedAt
}

func (f *File) IsDir() bool {
	return f.IsDirectory
}

func (f *File) Sys() interface{} {
	return nil
}

// ... 移除兼容性层,现在直接实现Name()和Size() ...

// WebDAVFS实现了webdav.FileSystem接口

type WebDAVFS struct {
	fs *MemFS
}

// NewWebDAVFS创建一个新的WebDAVFS实例
func NewWebDAVFS(fs *MemFS) *WebDAVFS {
	return &WebDAVFS{
		fs: fs,
	}
}

// Mkdir创建一个新目录
func (wfs *WebDAVFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
	_, err := wfs.fs.CreateDir(name)
	return err
}

// OpenFile打开一个文件
func (wfs *WebDAVFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
	// 检查文件是否存在
	file, err := wfs.fs.GetFile(name)
	if err != nil {
		if os.IsNotExist(err) && (flag&os.O_CREATE != 0) {
			// 如果文件不存在且指定了O_CREATE标志,则创建文件
			file, err = wfs.fs.CreateFile(name)
			if err != nil {
				return nil, err
			}
		} else {
			return nil, err
		}
	}

	// 返回WebDAVFile包装器
	return &WebDAVFile{
		file: file,
		pos:  0,
	}, nil
}

// RemoveAll删除文件或目录
func (wfs *WebDAVFS) RemoveAll(ctx context.Context, name string) error {
	return wfs.fs.Delete(name)
}

// Rename重命名文件或目录
func (wfs *WebDAVFS) Rename(ctx context.Context, oldName, newName string) error {
	return wfs.fs.Rename(oldName, newName)
}

// Stat返回文件信息
func (wfs *WebDAVFS) Stat(ctx context.Context, name string) (os.FileInfo, error) {
	file, err := wfs.fs.GetFile(name)
	if err != nil {
		return nil, err
	}
	return file, nil
}

// WebDAVFile实现了webdav.File接口
type WebDAVFile struct {
	file *File
	pos  int64
}

// Close关闭文件
func (wf *WebDAVFile) Close() error {
	return nil // 内存文件无需操作
}

// Read从文件读取
func (wf *WebDAVFile) Read(p []byte) (n int, err error) {
	wf.file.mu.RLock()
	defer wf.file.mu.RUnlock()

	if wf.pos >= int64(len(wf.file.Content)) {
		return 0, io.EOF
	}

	n = copy(p, wf.file.Content[wf.pos:])
	wf.pos += int64(n)
	return n, nil
}

// ReadAt在指定偏移位置从文件读取
func (wf *WebDAVFile) ReadAt(p []byte, off int64) (n int, err error) {
	wf.file.mu.RLock()
	defer wf.file.mu.RUnlock()

	if off >= int64(len(wf.file.Content)) {
		return 0, io.EOF
	}

	n = copy(p, wf.file.Content[off:])
	if n < len(p) {
		err = io.EOF
	}
	return n, err
}

// Seek定位到指定位置
func (wf *WebDAVFile) Seek(offset int64, whence int) (int64, error) {
	wf.file.mu.RLock()
	defer wf.file.mu.RUnlock()

	var newPos int64

	switch whence {
	case io.SeekStart:
		newPos = offset
	case io.SeekCurrent:
		newPos = wf.pos + offset
	case io.SeekEnd:
		newPos = int64(len(wf.file.Content)) + offset
	default:
		return 0, os.ErrInvalid
	}

	if newPos < 0 {
		return 0, os.ErrInvalid
	}

	wf.pos = newPos
	return newPos, nil
}

// Write向文件写入
func (wf *WebDAVFile) Write(p []byte) (n int, err error) {
	wf.file.mu.Lock()
	defer wf.file.mu.Unlock()

	// 计算写入后的新位置
	newPos := wf.pos + int64(len(p))

	// 如需要则调整内容大小
	if newPos > int64(len(wf.file.Content)) {
		// 扩展内容
		newContent := make([]byte, newPos)
		copy(newContent, wf.file.Content)
		wf.file.Content = newContent
	}

	// 将p写入当前位置
	copy(wf.file.Content[wf.pos:newPos], p)

	// 更新位置和大小
	wf.pos = newPos
	wf.file.FileSize = newPos
	wf.file.ModifiedAt = time.Now()

	return len(p), nil
}

// WriteAt在指定偏移位置向文件写入
func (wf *WebDAVFile) WriteAt(p []byte, off int64) (n int, err error) {
	wf.file.mu.Lock()
	defer wf.file.mu.Unlock()

	newPos := off + int64(len(p))
	if newPos > int64(len(wf.file.Content)) {
		// 扩展内容
		newContent := make([]byte, newPos)
		copy(newContent, wf.file.Content)
		wf.file.Content = newContent
	}

	copy(wf.file.Content[off:newPos], p)

	// 如需要则更新大小
	if newPos > wf.file.FileSize {
		wf.file.FileSize = newPos
	}

	wf.file.ModifiedAt = time.Now()

	return len(p), nil
}

// Readdir读取目录条目
func (wf *WebDAVFile) Readdir(count int) ([]os.FileInfo, error) {
	wf.file.mu.RLock()
	defer wf.file.mu.RUnlock()

	if !wf.file.IsDirectory {
		return nil, os.ErrInvalid
	}

	// 将map值转换为切片
	children := make([]os.FileInfo, 0, len(wf.file.Children))
	for _, child := range wf.file.Children {
		children = append(children, child)
	}

	// 处理count参数
	if count <= 0 {
		return children, nil
	}

	if count > len(children) {
		return children, io.EOF
	}

	return children[:count], nil
}

// Readdirnames读取目录条目名称
func (wf *WebDAVFile) Readdirnames(n int) ([]string, error) {
	wf.file.mu.RLock()
	defer wf.file.mu.RUnlock()

	if !wf.file.IsDirectory {
		return nil, os.ErrInvalid
	}

	// 将map键转换为切片
	names := make([]string, 0, len(wf.file.Children))
	for name := range wf.file.Children {
		names = append(names, name)
	}

	// 处理n参数
	if n <= 0 {
		return names, nil
	}

	if n > len(names) {
		return names, io.EOF
	}

	return names[:n], nil
}

// Stat返回文件信息
func (wf *WebDAVFile) Stat() (os.FileInfo, error) {
	return wf.file, nil
}

🧼 注意事项

  • 此项目仅限本地测试,无任何身份验证,请勿暴露到公网!
  • Windows 映射功能仅在 Windows 上有效(废话文学+1)
  • 数据随进程消亡------所以别把情书存进去,除非你想制造"数字失忆"浪漫

🌈 彩蛋

README.txt 末尾的 JjMgx 是作者签名,不是乱码,也不是病毒哈 :)

往期部分文章列表

相关推荐
百锦再20 小时前
第15章 并发编程
android·java·开发语言·python·rust·django·go
虫洞没有虫1 天前
Go语言学习笔记(一)
笔记·go·区块链
wohuidaquan2 天前
AI为何跳过你?GEO中的E-E-A-T权重
go
百锦再2 天前
选择Rust的理由:从内存管理到抛弃抽象
android·java·开发语言·后端·python·rust·go
百锦再2 天前
大话Rust的前生今世
开发语言·后端·rust·go·内存·时间·抽象
俞凡4 天前
Golang 构建网络漏洞扫描器
go
百锦再4 天前
第14章 智能指针
android·java·开发语言·git·rust·go·错误
Mgx4 天前
用 Go 写个“端口扫描器”,100 行代码扫描你家路由器?(别慌,只是看看谁在开门!)
go
mao毛4 天前
go项目适配DTM,gozero已经适配dtm了,goframe项目要怎么适配?
微服务·go