基于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

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

相关推荐
无所事事O_o5 小时前
加密过程及原理浅析
java·加密
宁小法1 天前
Linux上 log日志很大,如何获取部分内容?
linux·日志文件·传输
深念Y6 天前
当加密遇见分布式:Web3、去中心化与元宇宙的底层逻辑
分布式·web3·去中心化·区块链·元宇宙·加密·价值
linkedbyte11 天前
P2P直播系统
音视频·p2p·obs·obs直播·pcdn
少游33915 天前
哈尔滨工业大学csapp大作业《程序人生-Hello’s P2P》
程序人生·职场和发展·p2p
qq_2837200515 天前
Python 模块精讲:hashlib — MD5、SHA 加密(3500 字完整版)
python·加密·md5·hashlib·sha 加密
JZZC222 天前
2.OSPF P2P 网络类型
计算机网络·p2p·ensp·ospf
十五年专注C++开发23 天前
cpolar(极点云): 一款主流的内网穿透工具
linux·windows·cpolar·穿透
做萤石二次开发的哈哈25 天前
萤石开放平台音视频 P2P 能力核心是什么?
网络·网络协议·p2p