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