首次提交

This commit is contained in:
2025-10-14 17:18:21 +08:00
commit f0155d1e63
54 changed files with 1956 additions and 0 deletions

80
internal/cmd/cmd.go Normal file
View File

@@ -0,0 +1,80 @@
package cmd
import (
"context"
"net"
"time"
"github.com/ayflying/p2p/internal/controller/p2p"
"github.com/ayflying/p2p/internal/service"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gtimer"
)
func init() {
err := Main.AddCommand(&Main, &Debug, &P2p)
if err != nil {
g.Log().Error(gctx.GetInitCtx(), err)
return
}
}
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
parser, err = gcmd.Parse(g.MapStrBool{
"w,ws": true,
"g,gateway": true,
"p,port": true,
})
addr := g.Cfg().MustGet(ctx, "ws.address").String()
ws := parser.GetOpt("ws", addr).String()
//port := parser.GetOpt("port", 0).Int()
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind(
p2p.NewV1(),
)
})
//启动p2p服务端网关
s.Group("/ws", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
err = service.P2P().GatewayStart(ctx, group)
if err != nil {
g.Log().Error(ctx, err)
}
})
//s.SetPort(port)
// 延迟启动
gtimer.SetTimeout(ctx, time.Second*5, func(ctx context.Context) {
g.Log().Debug(ctx, "开始执行客户端")
// 启动p2p客户端
err = service.P2P().Start(ctx, ws)
g.Log().Debugf(ctx, "当前监听端口:%v", s.GetListenedPort())
addrs, _ := net.InterfaceAddrs()
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && ipnet.IP.To4() != nil {
g.Log().Infof(ctx, "访问地址:http://%v:%d", ipnet.IP.String(), s.GetListenedPort())
}
}
})
s.Run()
return nil
},
}
)

63
internal/cmd/debug.go Normal file
View File

@@ -0,0 +1,63 @@
package cmd
import (
"context"
"github.com/dop251/goja"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcmd"
)
type DebugType struct {
Action string `json:"action" dc:"操作渠道"`
Number int64 `json:"number" dc:"数字"`
String string `json:"string" dc:"字符串"`
Json string `json:"json" dc:"符合参数"`
}
var (
Debug = gcmd.Command{
Name: "debug",
Usage: "debug",
Brief: "调试接口",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
g.Log().SetConfigWithMap(g.Map{
"level": "all",
"stdout": true,
})
var req = &DebugType{
Action: parser.GetOpt("a").String(),
Number: parser.GetOpt("n").Int64(),
String: parser.GetOpt("s").String(),
Json: parser.GetOpt("j").String(),
}
g.Log().Debug(ctx, "开始调试了")
g.Log().Debugf(ctx, "开始执行:action:%v,number=%v,string=%v,json=%v", req.Action, req.Number, req.String, req.Json)
var msg any
switch req.Action {
case "js":
vm := goja.New()
if req.String == "" {
req.String = "console.log('hello world');"
}
res, err := vm.RunString(req.String)
if err != nil {
break
}
msg = res.Export()
g.Dump(res.ToNumber())
case "p2p":
// host, err := service.P2P().Start(ctx)
// if err != nil {
// break
// }
// g.Dump(host.ID().String(), host.Addrs())
}
g.Log().Debug(ctx, msg)
return
},
}
)

89
internal/cmd/p2p.go Normal file
View File

@@ -0,0 +1,89 @@
package cmd
import (
"context"
"github.com/ayflying/p2p/internal/service"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcmd"
)
// p2pHelpDescription 定义P2P命令的详细帮助信息
const p2pHelpDescription = `
P2P连接工具使用帮助:
模式1: 网关服务器
功能: 拥有外网IP接收客户端连接协助P2P打洞
命令: p2p -a gateway
模式2: 客户端
功能: 连接到网关,通过打洞实现与其他客户端的长连接通讯
命令: p2p -a client --gateway 网关ID
高级功能:
客户端间连接: p2p --mode client --gateway 网关ID --action connect --target 目标客户端ID
发送消息: p2p -mode client --gateway 网关ID --action send --target 目标客户端ID --message "消息内容"
`
var (
// P2p 命令定义了P2P连接工具的入口命令
// 遵循GoFrame的Command对象定义规范包含名称、用法、简短描述和执行函数
P2p = gcmd.Command{
// Name 为命令名称
Name: "p2p",
// Usage 描述命令的基本用法
Usage: "p2p [options]",
// Brief 提供命令的简短功能描述
Brief: "P2P连接工具支持网关和客户端模式实现NAT穿透和点对点通信",
// Description 提供命令的详细描述和使用帮助
Description: p2pHelpDescription,
// Func 为命令的执行函数,接收上下文和参数解析器
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := g.Server()
// 配置日志输出
g.Log().SetConfigWithMap(g.Map{
"level": "all",
"stdout": true,
})
parser, err = gcmd.Parse(g.MapStrBool{
"m,mode": true,
"g,gateway": true,
"a,action": true,
"t,target": true,
"m,message": true,
})
// 获取运行模式参数
mode := parser.GetOpt("mode").String()
// 根据不同模式调用服务层对应的方法
switch mode {
case "gateway":
// 启动网关服务器模式
g.Log().Debug(ctx, "开始执行gatway")
s.Group("/ws", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
service.P2P().GatewayStart(ctx, group)
})
s.Run()
case "client":
// 获取客户端模式所需的参数
g.Log().Debug(ctx, "开始执行client")
//addrs := []string{"/ip4/127.0.0.1/tcp/51888", "/ip4/192.168.50.173/tcp/51888"}
//addr := "/ip4/192.168.50.173/tcp/51888/p2p/12D3KooWJKBB9bF9MjqgsFYUUsPBG249FDq7a3ZdaYc9iw8G78JQ"
//addrs := "WyIvaXA0LzEyNy4wLjAuMS90Y3AvNTE4ODgiLCIvaXA0LzE5Mi4xNjguNTAuMTczL3RjcC81MTg4OCJd"
wsStr := "ws://192.168.50.173:51888/ws"
err = service.P2P().Start(ctx, wsStr)
default:
// 显示帮助信息
g.Log().Info(ctx, p2pHelpDescription)
}
return
},
}
)

View File

@@ -0,0 +1 @@
package consts

3
internal/consts/p2p.go Normal file
View File

@@ -0,0 +1,3 @@
package consts
const ()

View File

@@ -0,0 +1,5 @@
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================
package hello

View File

@@ -0,0 +1,16 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package hello
import (
"github.com/ayflying/p2p/api/hello"
)
type ControllerV1 struct{}
func NewV1() hello.IHelloV1 {
return &ControllerV1{}
}

View File

@@ -0,0 +1,13 @@
package hello
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/ayflying/p2p/api/hello/v1"
)
func (c *ControllerV1) Hello(ctx context.Context, req *v1.HelloReq) (res *v1.HelloRes, err error) {
g.RequestFromCtx(ctx).Response.Writeln("Hello World!")
return
}

View File

@@ -0,0 +1,5 @@
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================
package p2p

View File

@@ -0,0 +1,15 @@
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================
package p2p
import (
"github.com/ayflying/p2p/api/p2p"
)
type ControllerV1 struct{}
func NewV1() p2p.IP2PV1 {
return &ControllerV1{}
}

View File

@@ -0,0 +1,13 @@
package p2p
import (
"context"
"github.com/ayflying/p2p/api/p2p/v1"
"github.com/ayflying/p2p/internal/service"
)
func (c *ControllerV1) Connect(ctx context.Context, req *v1.ConnectReq) (res *v1.ConnectRes, err error) {
err = service.P2P().DiscoverAndConnect(req.TargetID)
return
}

View File

@@ -0,0 +1,13 @@
package p2p
import (
"context"
"github.com/ayflying/p2p/api/p2p/v1"
"github.com/ayflying/p2p/internal/service"
)
func (c *ControllerV1) Send(ctx context.Context, req *v1.SendReq) (res *v1.SendRes, err error) {
err = service.P2P().SendData(req.TargetID, []byte(req.Data))
return
}

0
internal/dao/.gitkeep Normal file
View File

0
internal/logic/.gitkeep Normal file
View File

9
internal/logic/logic.go Normal file
View File

@@ -0,0 +1,9 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package logic
import (
_ "github.com/ayflying/p2p/internal/logic/p2p"
)

View File

@@ -0,0 +1,326 @@
package p2p
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/glog"
"github.com/google/uuid"
"github.com/gorilla/websocket"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/peerstore"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/multiformats/go-multiaddr"
)
// 客户端
type Client struct {
ctx context.Context
Id string
gatewayURL string
host host.Host
wsConn *websocket.Conn // WebSocket连接
peers map[string]peer.ID // 存储已连接的节点
}
func (s *sP2P) Start(ctx context.Context, wsStr string) (err error) {
hostObj, err := s.createLibp2pHost(ctx, 0)
if err != nil {
g.Log().Error(ctx, err)
}
//defer hostObj.Close()
// 创建客户端实例
s.client = &Client{
ctx: ctx,
Id: uuid.New().String(),
gatewayURL: wsStr,
host: hostObj,
peers: make(map[string]peer.ID),
}
// 设置流处理函数处理P2P消息
hostObj.SetStreamHandler(ProtocolID, s.handleStream)
// 连接网关WebSocket
if err = s.connectGateway(); err != nil {
glog.Fatalf(ctx, "连接网关失败: %v", err)
}
// 注册到网关
if err = s.register(); err != nil {
glog.Fatalf(ctx, "注册到网关失败: %v", err)
}
// 启动网关消息接收协程
go s.receiveGatewayMessages()
g.Log().Infof(ctx, "已连接网关成功客户端ID: %s", s.client.Id)
//g.Log().Infof(ctx,"当前地址http://127.0.0.1/")
//select {
//case <-ctx.Done():
//}
return
}
// 创建libp2p主机
func (s *sP2P) createLibp2pHost(ctx context.Context, port int) (host.Host, error) {
// 配置监听地址
listenAddr := fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", port)
// 创建主机
h, err := libp2p.New(
libp2p.ListenAddrStrings(listenAddr),
libp2p.DefaultTransports,
libp2p.DefaultMuxers,
libp2p.DefaultSecurity,
)
return h, err
}
// 连接网关WebSocket
func (s *sP2P) connectGateway() (err error) {
conn, _, err := websocket.DefaultDialer.Dial(s.client.gatewayURL, nil)
if err != nil {
return fmt.Errorf("WebSocket连接失败: %v", err)
}
//defer conn.Close()
s.client.wsConn = conn
return
}
// 注册到网关
func (s *sP2P) register() error {
// 收集地址信息
addrs := make([]string, len(s.client.host.Addrs()))
for i, addr := range s.client.host.Addrs() {
addrs[i] = addr.String()
}
// 构建注册消息
msg := GatewayMessage{
Type: MsgTypeRegister,
From: s.client.Id,
Data: gjson.MustEncode(g.Map{
"peer_id": s.client.host.ID().String(),
"addrs": addrs,
}),
}
return s.sendToGateway(msg)
}
// 发现并连接目标节点
func (s *sP2P) DiscoverAndConnect(targetID string) error {
// 发送发现请求
msg := GatewayMessage{
Type: MsgTypeDiscover,
From: s.client.Id, // 发送方是自己
To: "gateway", // 接收方是网关
Data: gjson.MustEncode(g.Map{
"target_id": targetID,
}),
}
if err := s.sendToGateway(msg); err != nil {
return err
}
return nil
}
// 处理P2P流
func (s *sP2P) handleStream(stream network.Stream) {
defer stream.Close()
peerID := stream.Conn().RemotePeer().String()
glog.Infof(ctx, "收到来自 %s 的连接", peerID)
// 读取数据
buf := make([]byte, 1024)
n, err := stream.Read(buf)
if err != nil {
glog.Errorf(ctx, "读取流数据失败: %v", err)
return
}
g.Log().Debugf(ctx, "收到来自 %s 的消息: %v> ", peerID, string(buf[:n]))
}
// 发送数据到目标节点
func (s *sP2P) SendData(targetID string, data []byte) error {
peerID, exists := s.client.peers[targetID]
if !exists {
return fmt.Errorf("未找到目标节点 %s 的连接", targetID)
}
// 创建流
stream, err := s.client.host.NewStream(gctx.New(), peerID, protocol.ID("/p2p-chat/1.0.0"))
if err != nil {
return err
}
defer stream.Close()
// 发送数据
_, err = stream.Write(data)
return err
}
// 处理网关的打洞请求
func (s *sP2P) handlePunchRequest(data json.RawMessage) error {
var punchData struct {
FromID string `json:"from_id"`
PeerID string `json:"peer_id"`
Addrs []string `json:"addrs"`
}
if err := json.Unmarshal(data, &punchData); err != nil {
return err
}
// 解析PeerID
peerID, err := peer.Decode(punchData.PeerID)
if err != nil {
return err
}
// 解析地址
addrs := make([]multiaddr.Multiaddr, len(punchData.Addrs))
for i, addrStr := range punchData.Addrs {
addr, err := multiaddr.NewMultiaddr(addrStr)
if err != nil {
glog.Errorf(ctx, "解析地址失败: %v", err)
continue
}
addrs[i] = addr
}
// 添加到peerstore
s.client.host.Peerstore().AddAddrs(peerID, addrs, peerstore.PermanentAddrTTL)
// 立即尝试连接(关键:协调时机)
glog.Infof(ctx, "收到打洞请求,尝试连接 %s", punchData.FromID)
go func() {
time.Sleep(500 * time.Millisecond) // 稍微延迟,确保双方都准备好
if err := s.client.host.Connect(ctx, peer.AddrInfo{
ID: peerID,
Addrs: addrs,
}); err != nil {
glog.Errorf(ctx, "打洞连接失败: %v", err)
return
}
glog.Infof(ctx, "成功连接到 %s", punchData.FromID)
s.client.peers[punchData.FromID] = peerID
}()
return nil
}
// 发送消息到网关
func (s *sP2P) sendToGateway(msg GatewayMessage) (err error) {
data, err := json.Marshal(msg)
if err != nil {
return err
}
err = s.client.wsConn.WriteMessage(websocket.TextMessage, data)
return
}
// 接收网关消息
func (s *sP2P) receiveGatewayMessages() {
for {
_, data, err := s.client.wsConn.ReadMessage()
if err != nil {
glog.Errorf(ctx, "接收网关消息失败: %v", err)
return
}
var msg GatewayMessage
if err = json.Unmarshal(data, &msg); err != nil {
glog.Errorf(ctx, "解析网关消息失败: %v", err)
continue
}
// 验证消息是否发给自己to必须是当前客户端ID或空
if msg.To != "" && msg.To != s.client.Id {
g.Log().Debugf(ctx, "忽略非本客户端的消息from=%s, to=%s", msg.From, msg.To)
continue
}
// 处理不同类型消息
switch msg.Type {
case MsgTypeRegisterAck:
glog.Infof(ctx, "注册成功")
case MsgTypeDiscoverAck:
var msgData struct {
Found bool `json:"found"`
PeerID string `json:"peer_id,omitempty"`
Addrs []string `json:"addrs,omitempty"`
}
if err = gjson.DecodeTo(msg.Data, &msgData); err != nil {
g.Log().Errorf(ctx, "解析发现响应失败: %v", err)
continue
}
if !msgData.Found {
fmt.Println("未找到目标节点")
continue
}
// 解析并连接目标节点
peerID, err := peer.Decode(msgData.PeerID)
if err != nil {
glog.Errorf(ctx, "解析PeerID失败: %v", err)
continue
}
addrs := make([]multiaddr.Multiaddr, len(msgData.Addrs))
for i, addrStr := range msgData.Addrs {
addr, err := multiaddr.NewMultiaddr(addrStr)
if err != nil {
glog.Errorf(ctx, "解析地址失败: %v", err)
continue
}
addrs[i] = addr
}
s.client.host.Peerstore().AddAddrs(peerID, addrs, peerstore.PermanentAddrTTL)
// 立即尝试连接
go func(targetPeerID peer.ID, targetID string) {
time.Sleep(500 * time.Millisecond) // 协调时机
if err = s.client.host.Connect(ctx, peer.AddrInfo{
ID: targetPeerID,
Addrs: addrs,
}); err != nil {
g.Log().Errorf(ctx, "连接失败: %v", err)
return
}
g.Log().Infof(ctx, "成功连接到目标节点:%v", targetID)
s.client.peers[targetID] = targetPeerID
}(peerID, msg.To)
case MsgTypePunchRequest:
err = s.handlePunchRequest(msg.Data)
case MsgTypeError:
var data struct {
Error string `json:"error"`
}
json.Unmarshal(msg.Data, &data)
glog.Errorf(ctx, "网关错误: %s", data.Error)
}
}
}

View File

@@ -0,0 +1 @@
package p2p

View File

@@ -0,0 +1,268 @@
package p2p
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/glog"
"github.com/gorilla/websocket"
)
var (
Ws *websocket.Conn
)
// 客户端连接信息
type ClientConn struct {
ID string
PeerID string
Addrs []string
Conn *websocket.Conn
LastActive time.Time
}
// 消息结构
type GatewayMessage struct {
Type MsgType `json:"type"`
From string `json:"from"`
To string `json:"to,omitempty"`
Data json.RawMessage `json:"data,omitempty"`
}
func (s *sP2P) GatewayStart(ctx context.Context, group *ghttp.RouterGroup) (err error) {
var wsUpGrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
//// CheckOrigin allows any origin in development
//// In production, implement proper origin checking for security
//CheckOrigin: func(r *http.Request) bool {
// return true
//},
//// Error handler for upgrade failures
//Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) {
// // Implement error handling logic here
//},
}
group.Bind(func(r *ghttp.Request) {
// Upgrade HTTP connection to WebSocket
ws, err2 := wsUpGrader.Upgrade(r.Response.Writer, r.Request, nil)
if err2 != nil {
r.Response.Write(err2.Error())
return
}
defer ws.Close()
// Message handling loop
for {
_, data, _err := ws.ReadMessage()
if _err != nil {
//g.Log().Errorf(ctx, "读取消息失败: %v", err)
//s.sendError(ws, err.Error())
break
}
var msg GatewayMessage
if err = json.Unmarshal(data, &msg); err != nil {
//g.Log().Error(ctx, "消息格式错误")
s.sendError(ws, "消息格式错误")
continue
}
// 处理不同类型的消息
switch msg.Type {
case MsgTypeRegister:
s.handleRegister(ws, msg)
case MsgTypeDiscover:
s.handleDiscover(ws, msg)
default:
g.Log().Error(ctx, "未知消息类型: %s", msg.Type)
}
}
// Log connection closure
g.Log().Infof(ctx, "websocket %v connection closed", ws.RemoteAddr())
})
return
}
// 处理注册请求
func (s *sP2P) handleRegister(conn *websocket.Conn, msg GatewayMessage) {
if msg.From == "" {
g.Log().Error(ctx, "客户端ID不能为空")
return
}
var data struct {
PeerID string `json:"peer_id"`
Addrs []string `json:"addrs"`
}
if err := json.Unmarshal(msg.Data, &data); err != nil {
s.sendError(conn, "注册数据格式错误")
return
}
// 保存客户端信息
client := &ClientConn{
ID: msg.From,
PeerID: data.PeerID,
Addrs: data.Addrs,
Conn: conn,
LastActive: time.Now(),
}
s.lock.Lock()
s.Clients[msg.From] = client
s.lock.Unlock()
glog.Infof(ctx, "客户端 ip=%s,%s 注册成功PeerID: %s", conn.RemoteAddr(), msg.From, data.PeerID)
// 发送注册成功响应
err := s.sendMessage(conn, GatewayMessage{
Type: MsgTypeRegisterAck,
Data: json.RawMessage(`{"success": true, "message": "注册成功"}`),
})
if err != nil {
s.sendError(conn, err.Error())
}
}
// 清理超时客户端
func (s *sP2P) cleanupClients(ctx context.Context) {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
now := time.Now()
s.lock.Lock()
for id, client := range s.Clients {
if now.Sub(client.LastActive) > 60*time.Second {
client.Conn.Close()
delete(s.Clients, id)
glog.Infof(ctx, "清理超时客户端: %s", id)
}
}
s.lock.Unlock()
}
}
}
// 发送错误消息
func (s *sP2P) sendError(conn *websocket.Conn, errMsg string) {
s.sendMessage(conn, GatewayMessage{
Type: "error",
Data: json.RawMessage(fmt.Sprintf(`{"error": "%s"}`, errMsg)),
})
}
// 发送消息
func (s *sP2P) sendMessage(conn *websocket.Conn, msg GatewayMessage) error {
data, err := json.Marshal(msg)
if err != nil {
glog.Errorf(gctx.New(), "序列化消息失败: %v", err)
return err
}
return conn.WriteMessage(websocket.TextMessage, data)
}
// 处理发现请求
func (s *sP2P) handleDiscover(conn *websocket.Conn, msg GatewayMessage) {
if msg.From == "" {
s.sendError(conn, "消息缺少发送方IDfrom")
return
}
var data struct {
TargetID string `json:"target_id"`
}
if err := json.Unmarshal(msg.Data, &data); err != nil {
s.sendError(conn, "发现请求格式错误需包含target_id")
return
}
if data.TargetID == "" {
s.sendError(conn, "目标ID不能为空")
return
}
// 获取请求方和目标方信息
s.lock.RLock()
//fromClient, fromExists := s.Clients[msg.From]
//targetClient, targetExists := s.Clients[data.TargetID]
fromClient := s.Clients[msg.From] // 发送方msg.From
targetClient := s.Clients[data.TargetID] // 目标方data.TargetID
s.lock.RUnlock()
//if !fromExists {
// s.sendError(conn, "请先注册")
// return
//}
// 更新活动时间
s.lock.Lock()
fromClient.LastActive = time.Now()
s.lock.Unlock()
if targetClient != nil {
// 目标不存在
s.sendMessage(conn, GatewayMessage{
Type: MsgTypeDiscoverAck,
From: "gateway",
To: msg.From,
//Data: json.RawMessage(`{"found": false}`),
Data: gjson.MustEncode(g.Map{
"found": false,
}),
})
return
}
// 向请求方发送目标信息
s.sendMessage(conn, GatewayMessage{
Type: MsgTypeDiscoverAck,
From: "gateway", // 发送方是网关
To: msg.From, // 接收方是原请求方
Data: gjson.MustEncode(g.Map{
"found": true,
"peer_id": targetClient.PeerID,
//"addrs": s.getAddrsJSON(targetClient.Addrs),
"addrs": targetClient.Addrs,
}),
})
// 向目标方发送打洞请求(协调时机)
s.sendMessage(targetClient.Conn, GatewayMessage{
Type: MsgTypePunchRequest,
From: msg.From, // 发送方是原请求方
To: data.TargetID, // 接收方是目标方
Data: gjson.MustEncode(g.Map{
"from_id": msg.From,
"peer_id": fromClient.PeerID,
//"addrs": s.getAddrsJSON(fromClient.Addrs),
"addrs": targetClient.Addrs,
}),
})
}
// 获取地址JSON字符串
func (s *sP2P) getAddrsJSON(addrs []string) string {
strs := make([]string, len(addrs))
for i, addr := range addrs {
strs[i] = fmt.Sprintf("%q", addr)
}
return fmt.Sprintf("[%s]", strings.Join(strs, ","))
}

56
internal/logic/p2p/p2p.go Normal file
View File

@@ -0,0 +1,56 @@
package p2p
import (
"sync"
"github.com/ayflying/p2p/internal/service"
"github.com/gogf/gf/v2/os/gctx"
"github.com/libp2p/go-libp2p/core/protocol"
)
var (
ctx = gctx.New()
)
// 常量定义
const (
ProtocolID protocol.ID = "/ay/1.0.0"
DefaultPort = 51888
)
type MsgType string
const (
MsgTypeRegister = "register" // 客户端注册
MsgTypeRegisterAck = "register_ack" // 注册响应
MsgTypeDiscover = "discover" // 发现目标客户端
MsgTypeDiscoverAck = "discover_ack" // 发现响应
MsgTypeConnectionReq = "connection_req" // 连接请求通知
MsgTypePunchRequest = "punch_request"
MsgTypeError = "error" // 错误消息
)
// 注册请求数据
type RegisterData struct {
ClientID string `json:"client_id"`
}
// sP2P 结构体实现 IP2P 接口
type sP2P struct {
Clients map[string]*ClientConn // 客户端ID -> 连接
lock sync.RWMutex
client *Client
}
// New 创建一个新的 P2P 服务实例
func New() *sP2P {
return &sP2P{
Clients: make(map[string]*ClientConn),
client: &Client{},
}
}
func init() {
service.RegisterP2P(New())
}

0
internal/model/.gitkeep Normal file
View File

View File

View File

View File

@@ -0,0 +1 @@
package packed

View File

38
internal/service/p_2_p.go Normal file
View File

@@ -0,0 +1,38 @@
// ================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// You can delete these comments if you wish manually maintain this interface file.
// ================================================================================
package service
import (
"context"
"github.com/gogf/gf/v2/net/ghttp"
)
type (
IP2P interface {
Start(ctx context.Context, wsStr string) (err error)
// 发现并连接目标节点
DiscoverAndConnect(targetID string) error
// 发送数据到目标节点
SendData(targetID string, data []byte) error
GatewayStart(ctx context.Context, group *ghttp.RouterGroup) (err error)
}
)
var (
localP2P IP2P
)
func P2P() IP2P {
if localP2P == nil {
panic("implement not found for interface IP2P, forgot register?")
}
return localP2P
}
func RegisterP2P(i IP2P) {
localP2P = i
}