godoos 内网聊天机制详解

GodoOS 是一款轻量级的云桌面系统,旨在为用户提供高效、安全的网络通信环境。其内网聊天功能不仅支持文本消息的实时传输,还具备文件传输的能力。本文将详细介绍 godoos 内网聊天机制的核心原理和技术实现。

内网聊天机制概述

godoos 的内网聊天机制基于 UDP 协议,通过定期检查在线用户并维护一个在线用户列表,确保消息只能发送给当前在线的用户。此外,文件传输功能需要用户明确同意接收,以保证数据的安全性和用户的隐私。

ARP 发现在线 IP

  • godoos 使用 ARP 协议定期扫描局域网内的所有设备,发现在线的 IP 地址。
  • 这一过程通过 CheckOnlines 函数实现,该函数每 15 秒(可配置)调用一次 CheckOnline 方法,检查客户端是否已断开连接。
func getArpCacheIPs() ([]string, error) {
	var cmd *exec.Cmd
	var out []byte
	var err error

	switch runtime.GOOS {
	case "windows":
		cmd = exec.Command("arp", "-a")
	case "linux":
		cmd = exec.Command("arp", "-n")
	case "darwin": // macOS
		cmd = exec.Command("arp", "-l", "-a")
	default:
		return nil, fmt.Errorf("unsupported operating system: %v", runtime.GOOS)
	}

	out, err = cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("error executing arp command: %v", err)
	}

	lines := strings.Split(string(out), "\n")
	var ips []string

	for _, line := range lines {
		fields := strings.Fields(line)
		if len(fields) >= 2 {
			ip := fields[0]
			if ip != "<incomplete>" && net.ParseIP(ip) != nil {
				ips = append(ips, ip)
			}
		}
	}

	return ips, nil
}

维护在线用户列表

  • 所有在线的用户信息被存储在 OnlineUsers 字典中,包括主机名、IP 地址、最后活跃时间等。
  • 当用户发送心跳消息(类型为 heartbeat)时,系统会更新该用户的在线状态。
func concurrentGetIpInfo(ips []string) {
	// 获取本地 IP 地址
	hostips, err := libs.GetValidIPAddresses()
	if err != nil {
		log.Printf("failed to get local IP addresses: %v", err)
		return
	}
	// 获取 ARP 缓存中的 IP 地址
	validIPs, err := getArpCacheIPs()
	if err != nil {
		log.Printf("failed to get ARP cache IPs: %v", err)
		return
	}
	var wg sync.WaitGroup
	maxConcurrency := runtime.NumCPU()

	semaphore := make(chan struct{}, maxConcurrency)

	failedIPs := make(map[string]bool)

	for _, ip := range ips {
		if containArr(hostips, ip) || failedIPs[ip] || !containArr(validIPs, ip) {
			continue
		}

		wg.Add(1)
		semaphore <- struct{}{}

		go func(ip string) {
			defer wg.Done()
			defer func() { <-semaphore }()
			err := sendUDPPacket(ip)
			if err != nil {
				log.Printf("Failed to send packet to IP %s: %v", ip, err)
				failedIPs[ip] = true // 标记失败的 IP
			}
		}(ip)
	}

	wg.Wait()
}

func CheckOnline() {
	// 清除 OnlineUsers 映射表
	CleanOnlineUsers()

	ips := libs.GetChatIPs()
	// 启动并发处理
	concurrentGetIpInfo(ips)

	log.Printf("online users: %v", OnlineUsers)
}

func CleanOnlineUsers() {
	OnlineUsers = make(map[string]UserStatus)
}

消息传输

UDP 服务器

  • godoos 在 UDP 端口 56780 上启动一个监听服务,接收来自客户端的消息。
  • 接收到的消息首先进行 JSON 解析,提取出消息类型、发送方 IP 地址等信息。
func UdpServer() {
	// 监听 UDP 端口
	listener, err := net.ListenPacket("udp", ":56780")
	if err != nil {
		log.Fatalf("error setting up listener: %v", err)
	}
	defer listener.Close()

	log.Println("UDP server started on :56780")

	// 监听 UDP 请求
	for {
		buffer := make([]byte, 1024)

		n, remoteAddr, err := listener.ReadFrom(buffer)
		if err != nil {
			log.Printf("error reading from UDP: %v", err)
			continue
		}

		log.Printf("Received UDP packet from %v: %s", remoteAddr, buffer[:n])
		// 从 remoteAddr 获取 IP 地址
		udpAddr, ok := remoteAddr.(*net.UDPAddr)
		if !ok {
			log.Printf("unexpected address type: %T", remoteAddr)
			continue
		}
		ip := udpAddr.IP.String()
		// 解析 UDP 数据
		var udpMsg UdpMessage
		err = json.Unmarshal(buffer[:n], &udpMsg)
		if err != nil {
			log.Printf("error unmarshalling UDP message: %v", err)
			continue
		}
		udpMsg.IP = ip

		if udpMsg.Type == "heartbeat" {
			UpdateUserStatus(udpMsg.IP, udpMsg.Hostname)
			continue
		}

		if udpMsg.Type == "image" {
			filePath, err := ReceiveImg(udpMsg)
			if err != nil {
				log.Printf("error receiving image: %v", err)
				continue
			}
			udpMsg.Message = filePath
		}
		// 添加消息到 UserMessages
		AddMessage(udpMsg)

	}
}

消息处理

  • 根据消息类型,系统会调用相应的处理函数。例如,心跳消息用于更新用户状态,文件传输请求需要用户确认。
  • 所有消息会被存储在 UserMessages 字典中,以便后续查询和展示。

文件传输

  1. 文件发送请求

    • 用户发起文件发送请求时,系统会生成一条类型为 fileSending 的消息,并发送给目标用户。
    • 目标用户接收到请求后,可以选择接受或拒绝。
  2. 文件传输确认

    • 如果目标用户同意接收文件,系统会生成一条类型为 fileAccessed 的消息,通知发送方开始传输文件。
    • 文件传输完成后,系统会返回文件路径给发送方,确保文件传输成功。
  3. 文件传输取消

    • 用户也可以取消文件发送请求,系统会生成一条类型为 fileCannel 的消息,通知目标用户取消操作。

func HandlerApplySendFile(w http.ResponseWriter, r *http.Request) {
	var msg UdpMessage
	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&msg); err != nil {
		http.Error(w, "Invalid request body", http.StatusBadRequest)
		return
	}
	defer r.Body.Close()
	hostname, err := os.Hostname()
	if err != nil {
		libs.ErrorMsg(w, "HandleMessage error")
		return
	}
	msg.Hostname = hostname
	msg.Time = time.Now()
	msg.Type = "fileSending"
	SendToIP(msg)
	libs.SuccessMsg(w, nil, "请求文件发送成功")
}
func HandlerCannelFile(w http.ResponseWriter, r *http.Request) {
	var msg UdpMessage
	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&msg); err != nil {
		http.Error(w, "Invalid request body", http.StatusBadRequest)
		return
	}
	defer r.Body.Close()
	hostname, err := os.Hostname()
	if err != nil {
		libs.ErrorMsg(w, "HandleMessage error")
		return
	}
	msg.Hostname = hostname
	msg.Time = time.Now()
	msg.Type = "fileCannel"
	SendToIP(msg)
	libs.SuccessMsg(w, nil, "请求文件发送成功")
}
func HandlerAccessFile(w http.ResponseWriter, r *http.Request) {
	var msg UdpMessage
	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&msg); err != nil {
		libs.ErrorMsg(w, "Invalid request body")
		return
	}
	defer r.Body.Close()
	hostname, err := os.Hostname()
	if err != nil {
		libs.ErrorMsg(w, "HandleMessage error")
		return
	}
	msg.Hostname = hostname
	msg.Time = time.Now()
	msg.Type = "fileAccessed"
	SendToIP(msg)
	path, err := downloadFiles(msg)
	if err != nil {
		libs.ErrorMsg(w, "HandleMessage error")
		return
	}
	res := map[string]interface{}{
		"path": path,
		"msg":  msg.Message,
	}
	libs.SuccessMsg(w, res, "接收文件成功")
}

安全与隐私

  • 用户确认机制:文件传输需要目标用户明确同意,确保数据的安全性和用户的隐私。
  • 在线状态检查:系统定期检查在线用户,确保消息只能发送给当前在线的用户,避免无效消息的传输。

结语

godoos 的内网聊天机制通过高效的在线用户检测和严格的消息处理流程,确保了内网通信的实时性和安全性。无论是文本消息还是文件传输,用户都能享受到便捷、可靠的通信体验。未来,godoos 将继续优化这一机制,为用户提供更加完善的内网通信解决方案。

posted @ 2024-12-12 21:49  xpbb  阅读(8)  评论(0编辑  收藏  举报