基于p2p通信开发一个聊天通信软件

https://webrpc.cn/是一家一站式全球P2P通信服务平台,在上面注册一个账户,就会免费获取两个token,使用这个token+提供的sdk就可以快速的开发出自己聊天小软件了.

注册免费获取token:

选择合适平台下载sdk

开发A端:

Go 复制代码
package main

/*
#cgo CFLAGS: -I.
#cgo LDFLAGS: -L. -lwebrpc-Mac
#include "libwebrpc-Mac.h"
#include <stdlib.h>
#include <stddef.h>
*/
import "C"
import (
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"net"
	"time"
	"unsafe"
)

func Now() int64 {
	return time.Now().UnixNano() / 1e6
}

// 开始监听回调数据
func startReceivingCallbackData(webrpc C.GoUintptr, tcpPort int) {
	// 连接到本地TCP服务器
	conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", tcpPort))
	if err != nil {
		log.Printf("连接到回调服务器失败: %v", err)
		return
	}
	defer conn.Close()

	log.Printf("已连接到回调服务器,端口: %d", tcpPort)

	// 持续接收数据
	for {
		// 读取前4个字节:sessionId
		sessionIdBytes := make([]byte, 4)
		_, err := io.ReadFull(conn, sessionIdBytes)
		if err != nil {
			if err == io.EOF {
				log.Println("回调连接已关闭")
			} else {
				log.Printf("读取sessionId失败: %v", err)
			}
			break
		}
		sessionId := binary.BigEndian.Uint32(sessionIdBytes)

		// 读取第5个字节:数据类型 (1=数据流, 2=文件流)
		dataTypeBytes := make([]byte, 1)
		_, err = io.ReadFull(conn, dataTypeBytes)
		if err != nil {
			log.Printf("读取数据类型失败: %v", err)
			break
		}
		dataType := dataTypeBytes[0]

		log.Printf("收到回调数据 - Session ID: %d, 数据类型: %d", sessionId, dataType)

		if dataType == 2 {
			// 数据流
			handleDataStream(webrpc, conn, sessionId)
		} else if dataType == 1 {
			// 文件流
			handleFileStream(conn, sessionId)
		} else {
			log.Printf("未知的数据类型: %d", dataType)
			// 跳过未知类型的数据,继续读取下一个消息
			continue
		}
	}
}

// 处理数据流
func handleDataStream(webrpc C.GoUintptr, conn net.Conn, sessionId uint32) {
	// 读取4个字节的数据流长度
	dataLengthBytes := make([]byte, 4)
	_, err := io.ReadFull(conn, dataLengthBytes)
	if err != nil {
		log.Printf("读取数据流长度失败: %v", err)
		return
	}
	dataLength := binary.BigEndian.Uint32(dataLengthBytes)

	// 读取数据流
	data := make([]byte, dataLength)
	_, err = io.ReadFull(conn, data)
	if err != nil {
		log.Printf("读取数据流内容失败: %v", err)
		return
	}

	// log.Printf("收到数据流 - Session ID: %d, 数据长度: %d", sessionId, dataLength)

	// 这里可以添加你的数据处理逻辑
	// 例如:打印数据内容(如果是文本)
	if dataLength > 0 && dataLength < 1024 { // 限制打印长度
		log.Printf("数据内容: %s", string(data))
	}

	//  回应已经收到(异步执行,不能阻塞回调数据读取)
	go func() {
		msg := "已经收到"
		cmsg := C.CString(msg)
		ret := C.WebrpcClient_SendData(webrpc, C.unsigned(sessionId), cmsg, C.int(len(msg)), 3000)
		log.Println("回复ret", ret)
	}()

}

// 处理文件流
func handleFileStream(conn net.Conn, sessionId uint32) {
	// 读取4个字节的文件名长度
	fileNameLengthBytes := make([]byte, 4)
	_, err := io.ReadFull(conn, fileNameLengthBytes)
	if err != nil {
		log.Printf("读取文件名长度失败: %v", err)
		return
	}
	fileNameLength := binary.BigEndian.Uint32(fileNameLengthBytes)

	// 读取文件名
	fileName := make([]byte, fileNameLength)
	_, err = io.ReadFull(conn, fileName)
	if err != nil {
		log.Printf("读取文件名失败: %v", err)
		return
	}

	// 读取4个字节的文件数据长度
	fileDataLengthBytes := make([]byte, 4)
	_, err = io.ReadFull(conn, fileDataLengthBytes)
	if err != nil {
		log.Printf("读取文件数据长度失败: %v", err)
		return
	}
	fileDataLength := binary.BigEndian.Uint32(fileDataLengthBytes)

	// 读取文件数据
	fileData := make([]byte, fileDataLength)
	_, err = io.ReadFull(conn, fileData)
	if err != nil {
		log.Printf("读取文件数据失败: %v", err)
		return
	}

	log.Printf("收到文件流 - Session ID: %d", sessionId)
	log.Printf("文件名: %s, 文件大小: %d 字节", string(fileName), fileDataLength)

	// 这里可以添加你的文件处理逻辑
	// 例如:保存文件到磁盘
	// filePath := fmt.Sprintf("./received_files/%s", string(fileName))
	//
	// // 确保目录存在
	// os.MkdirAll("./received_files", 0755)
	//
	// err = os.WriteFile(filePath, fileData, 0644)
	// if err != nil {
	//     log.Printf("保存文件失败: %v", err)
	// } else {
	//     log.Printf("文件已保存到: %s", filePath)
	// }
}

func main() {
	token := C.CString("") // 你的token
	passwd := C.CString("") // 你的token对应的密码
	permission := C.CString("") // 可以为空,为空时任何知道你token到人都可以与你建立会话连接,并不安全,生产环境上不建议设置空
	defer C.free(unsafe.Pointer(token))
	defer C.free(unsafe.Pointer(passwd))
	defer C.free(unsafe.Pointer(permission))

    // 这里会自动登录,且时异步的
	webrpc := C.WebrpcClient_New(token, passwd, permission)
	fmt.Printf("Client webrpc: %v\n", webrpc)

    // 这里等待登录是否成功?返回1时表示登录成功
	for {
		status := C.WebrpcClient_LoginStatus(webrpc)
		fmt.Printf("Login status: %d\n", int(status))
		if status != 0 {
			break
		}
		time.Sleep(2 * time.Second)
	}

	// 获取回调tcp端口
	tcpPort := C.WebrpcClient_GetReceivePort(webrpc)
	fmt.Printf("回调TCP端口: %d\n", tcpPort)

	// 开始监听回调数据(在goroutine中运行)
	go startReceivingCallbackData(webrpc, int(tcpPort))

	// 定时打印状态
	for {
		log.Println("会话数=", C.WebrpcClient_SessionSize(webrpc))

		time.Sleep(3 * time.Second)
	}

	select {}

	// 关闭客户端
	C.WebrpcClient_Free(webrpc)
}

开发B端:

Go 复制代码
package main

/*
#cgo CFLAGS: -I.
#cgo LDFLAGS: -L. -lwebrpc-Mac
#include "libwebrpc-Mac.h"
#include <stdlib.h>
#include <stddef.h>
*/
import "C"
import (
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"net"
	"time"
	"unsafe"
)

func Now() int64 {
	return time.Now().UnixNano() / 1e6
}

// 开始监听回调数据
func startReceivingCallbackData(tcpPort int) {
	// 连接到本地TCP服务器
	conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", tcpPort))
	if err != nil {
		log.Printf("连接到回调服务器失败: %v", err)
		return
	}
	defer conn.Close()

	log.Printf("已连接到回调服务器,端口: %d", tcpPort)

	// 持续接收数据
	for {
		// 读取前4个字节:sessionId
		sessionIdBytes := make([]byte, 4)
		_, err := io.ReadFull(conn, sessionIdBytes)
		if err != nil {
			if err == io.EOF {
				log.Println("回调连接已关闭")
			} else {
				log.Printf("读取sessionId失败: %v", err)
			}
			break
		}
		sessionId := binary.BigEndian.Uint32(sessionIdBytes)

		// 读取第5个字节:数据类型 (1=数据流, 2=文件流)
		dataTypeBytes := make([]byte, 1)
		_, err = io.ReadFull(conn, dataTypeBytes)
		if err != nil {
			log.Printf("读取数据类型失败: %v", err)
			break
		}
		dataType := dataTypeBytes[0]

		// log.Printf("收到回调数据 - Session ID: %d, 数据类型: %d", sessionId, dataType)

		if dataType == 2 {
			// 数据流
			handleDataStream(conn, sessionId)
		} else if dataType == 1 {
			// 文件流
			handleFileStream(conn, sessionId)
		} else {
			log.Printf("未知的数据类型: %d", dataType)
			// 跳过未知类型的数据,继续读取下一个消息
			continue
		}
	}
}

// 处理数据流
func handleDataStream(conn net.Conn, sessionId uint32) {
	// 读取4个字节的数据流长度
	dataLengthBytes := make([]byte, 4)
	_, err := io.ReadFull(conn, dataLengthBytes)
	if err != nil {
		log.Printf("读取数据流长度失败: %v", err)
		return
	}
	dataLength := binary.BigEndian.Uint32(dataLengthBytes)

	// 读取数据流
	data := make([]byte, dataLength)
	_, err = io.ReadFull(conn, data)
	if err != nil {
		log.Printf("读取数据流内容失败: %v", err)
		return
	}

	// log.Printf("收到数据流 - Session ID: %d, 数据长度: %d", sessionId, dataLength)

	// 这里可以添加你的数据处理逻辑
	// 例如:打印数据内容(如果是文本)
	if dataLength > 0 && dataLength < 1024 { // 限制打印长度
		log.Printf("数据内容: %s", string(data))
	}

	// 示例:将数据写入文件
	// filename := fmt.Sprintf("received_data_%d_%d.bin", sessionId, time.Now().Unix())
	// err = os.WriteFile(filename, data, 0644)
	// if err != nil {
	//     log.Printf("保存数据到文件失败: %v", err)
	// } else {
	//     log.Printf("数据已保存到文件: %s", filename)
	// }
}

// 处理文件流
func handleFileStream(conn net.Conn, sessionId uint32) {
	// 读取4个字节的文件名长度
	fileNameLengthBytes := make([]byte, 4)
	_, err := io.ReadFull(conn, fileNameLengthBytes)
	if err != nil {
		log.Printf("读取文件名长度失败: %v", err)
		return
	}
	fileNameLength := binary.BigEndian.Uint32(fileNameLengthBytes)

	// 读取文件名
	fileName := make([]byte, fileNameLength)
	_, err = io.ReadFull(conn, fileName)
	if err != nil {
		log.Printf("读取文件名失败: %v", err)
		return
	}

	// 读取4个字节的文件数据长度
	fileDataLengthBytes := make([]byte, 4)
	_, err = io.ReadFull(conn, fileDataLengthBytes)
	if err != nil {
		log.Printf("读取文件数据长度失败: %v", err)
		return
	}
	fileDataLength := binary.BigEndian.Uint32(fileDataLengthBytes)

	// 读取文件数据
	fileData := make([]byte, fileDataLength)
	_, err = io.ReadFull(conn, fileData)
	if err != nil {
		log.Printf("读取文件数据失败: %v", err)
		return
	}

	log.Printf("收到文件流 - Session ID: %d", sessionId)
	log.Printf("文件名: %s, 文件大小: %d 字节", string(fileName), fileDataLength)

	// 这里可以添加你的文件处理逻辑
	// 例如:保存文件到磁盘
	// filePath := fmt.Sprintf("./received_files/%s", string(fileName))
	//
	// // 确保目录存在
	// os.MkdirAll("./received_files", 0755)
	//
	// err = os.WriteFile(filePath, fileData, 0644)
	// if err != nil {
	//     log.Printf("保存文件失败: %v", err)
	// } else {
	//     log.Printf("文件已保存到: %s", filePath)
	// }
}

func main() {
	token := C.CString("") // 你的token
	passwd := C.CString("") // 你的token对应的密码
	permission := C.CString("") // 这里可以为空,为空时所有人都可以跟您建立会话连接,不安全,生产不建议
	defer C.free(unsafe.Pointer(token))
	defer C.free(unsafe.Pointer(passwd))
	defer C.free(unsafe.Pointer(permission))

    // 这里创建WebrpcClient底层会自动登录
	webrpc := C.WebrpcClient_New(token, passwd, permission)
	fmt.Printf("Client handle: %v\n", webrpc)
    // 这里等带WebrpcClient_LoginStatus返回1就表示登录成功
	for {
		status := C.WebrpcClient_LoginStatus(webrpc)
		fmt.Printf("Login status: %d\n", int(status))
		if status != 0 {
			break
		}
		time.Sleep(2 * time.Second)
	}

	// 获取回调tcp端口
	tcpPort := C.WebrpcClient_GetReceivePort(webrpc)
	fmt.Printf("回调TCP端口: %d\n", tcpPort)

	// 开始监听回调数据(在goroutine中运行)
	go startReceivingCallbackData(int(tcpPort))

	// 与A建立会话
	log.Printf("Establishing session with A...")
	start := Now()
	toToken := C.CString("") // 你要与谁建立连接?目标token
	defer C.free(unsafe.Pointer(toToken))
	sessionPermission := C.CString("") // 如果对方登录时是空权限,那么你也可以直接与之建立连接
	defer C.free(unsafe.Pointer(sessionPermission))

    // 开始与目标创建会话 sessionId== 0的话说会话创建失败,sessionId>0说明会话成功建立
	sessionId := C.WebrpcClient_OpenSession(webrpc, toToken, sessionPermission)
	fmt.Printf("OpenSession to A, sessionId: %d, elapsed: %d ms\n",  uint32(sessionId), (Now() - start))

	size := C.WebrpcClient_SessionSize(webrpc)
	fmt.Printf("Session size: %d\n", uint16(size))

	// 向A发送文件
	start1 := Now()
	filePath := C.CString("/Users/a1/Downloads/Trae-darwin-arm64.dmg") // 你自己随便发送一个文件
	defer C.free(unsafe.Pointer(filePath))
	status := C.WebrpcClient_SendFile(webrpc, sessionId, filePath)
	log.Printf("发送文件完成 result: %d, elapsed: %v\n", int(status), Now()-start1)

	// 向A发送Hello 你好啊小明的消息
	if sessionId != 0 {
		msg := "Hello 你好啊小明"
		cmsg := C.CString(msg)
		defer C.free(unsafe.Pointer(cmsg))
		timeOut := C.longlong(3000) // 超时3秒

		for i := 0; i < 1; i++ {
			go func() {
				for {
					start := time.Now()
					ret := C.WebrpcClient_SendData(webrpc, sessionId, cmsg, C.int(len(msg)), timeOut)
					elapsed := time.Since(start)
					fmt.Printf("SendData result: %d, elapsed: %v\n", int(ret), elapsed)
					time.Sleep(time.Second)
				}

			}()

		}

	}

	select {}

	// 关闭客户端
	C.WebrpcClient_Free(webrpc)
}

这样两端的代码就开发完成了,底层的p2p穿透通信webrpc会自行完成,并且是quic加密通信的,会话内容安全可靠.

程序运行例子

A端:

B端的例子:

这是一个机遇webrpc的p2p通信组件完成一个文件发送和消息内容发送到对端到例子.如果想了解更多p2p文件传输的实现可参考项目https://github.com/xiaoming-software/File2File-Desktop/tree/main

声明:本人仅是技术爱好者,任何人不得引用这些技术用于非法项目开发,违者自行负责一切后果.

相关推荐
勇敢牛牛_1 天前
Zeplyn:通过P2P构建服务共享网络
网络·网络协议·p2p·服务
换个昵称都难2 天前
webrtc RTC_P2P源码解析
asp.net·webrtc·p2p
HwJack204 天前
HarmonyOS APP开发终结“户外运动数据失踪”的玄学:玩透穿戴设备 P2P 穿透与心跳保活的心法
华为·harmonyos·p2p
资源分享交流4 天前
OmniGet:一个更省事的跨平台下载器,支持 yt-dlp、BT、磁力和 P2P 传输
网络·网络协议·p2p
梁辰兴5 天前
计算机网络基础:在 P2P 对等方中搜索对象
网络·计算机网络·计算机·p2p·计算机网络基础·梁辰兴
梁辰兴6 天前
计算机网络基础:P2P 文件分发的分析
网络·计算机网络·计算机·p2p·计算机网络基础·梁辰兴·文件分发分析
梁辰兴6 天前
计算机网络基础:具有全分布式结构的 P2P 文件共享程序
网络·分布式·计算机网络·p2p·计算机网络基础·梁辰兴·文件共享程序
ai_coder_ai7 天前
论P2P计算关键技术与应用
网络·网络协议·p2p
梁辰兴8 天前
计算机网络基础:具有集中目录服务器的 P2P 工作方式
服务器·网络·计算机网络·计算机·p2p·计算机网络基础·梁辰兴
network_tester1 个月前
自动驾驶系统TSN时延测试:从理论到实践的关键解析
网络·人工智能·网络协议·tcp/ip·自动驾驶·信息与通信·p2p