152 lines
3.0 KiB
Go
152 lines
3.0 KiB
Go
package udp
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"runtime"
|
|
"sync"
|
|
)
|
|
|
|
type Server struct {
|
|
bindAddr string
|
|
routingTable *RoutingTable
|
|
|
|
conns []*net.UDPConn
|
|
wg sync.WaitGroup
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
}
|
|
|
|
func NewServer(bindAddr string) *Server {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
return &Server{
|
|
bindAddr: bindAddr,
|
|
routingTable: NewRoutingTable(),
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
}
|
|
}
|
|
|
|
func (s *Server) Router() *RoutingTable {
|
|
return s.routingTable
|
|
}
|
|
|
|
func (s *Server) Run() error {
|
|
if s.bindAddr == "" {
|
|
return fmt.Errorf("bind address is empty")
|
|
}
|
|
|
|
workerCount := runtime.NumCPU()
|
|
|
|
conns, err := listenUDP(s.bindAddr, workerCount)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot listen on address: %w", err)
|
|
}
|
|
if len(conns) == 0 {
|
|
return fmt.Errorf("no UDP connections created")
|
|
}
|
|
s.conns = conns
|
|
|
|
for _, conn := range s.conns {
|
|
_ = conn.SetReadBuffer(8 * 1024 * 1024)
|
|
_ = conn.SetWriteBuffer(8 * 1024 * 1024)
|
|
}
|
|
|
|
fmt.Println("[udp] listening on", s.bindAddr, "with", len(s.conns), "worker(s)")
|
|
|
|
for workerID, conn := range s.conns {
|
|
s.wg.Add(1)
|
|
go s.workerLoop(workerID, conn)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) Stop() {
|
|
s.cancel()
|
|
for _, c := range s.conns {
|
|
_ = c.Close()
|
|
}
|
|
s.wg.Wait()
|
|
}
|
|
|
|
func (s *Server) workerLoop(workerID int, conn *net.UDPConn) {
|
|
defer s.wg.Done()
|
|
|
|
buf := make([]byte, 1500)
|
|
fmt.Println("[udp] worker", workerID, "started")
|
|
|
|
for {
|
|
select {
|
|
case <-s.ctx.Done():
|
|
fmt.Println("[udp] worker", workerID, "stopped")
|
|
return
|
|
default:
|
|
n, addr, err := conn.ReadFromUDP(buf)
|
|
if err != nil {
|
|
if s.ctx.Err() != nil {
|
|
return
|
|
}
|
|
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
|
continue
|
|
}
|
|
fmt.Printf("[udp] worker %d: read error: %v\n", workerID, err)
|
|
continue
|
|
}
|
|
|
|
// Le worker qui lit est aussi celui qui traite et qui écrit.
|
|
s.handlePacket(conn, buf[:n], addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
// handlePacket reçoit maintenant directement la conn du worker.
|
|
// Aucun hop supplémentaire : le paquet reste dans la goroutine/conn du worker.
|
|
func (s *Server) handlePacket(conn *net.UDPConn, data []byte, addr *net.UDPAddr) {
|
|
if len(data) == 0 {
|
|
return
|
|
}
|
|
|
|
pt := PacketType(data[0])
|
|
|
|
switch pt {
|
|
case PacketTypePing:
|
|
// exemple simple : echo du ping
|
|
_, _ = conn.WriteToUDP([]byte{byte(PacketTypePing)}, addr)
|
|
|
|
case PacketTypeConnect:
|
|
if len(data) < 2 {
|
|
return
|
|
}
|
|
channelID := string(data[1:])
|
|
s.routingTable.Add(channelID, addr)
|
|
|
|
case PacketTypeDisconnect:
|
|
if len(data) < 2 {
|
|
return
|
|
}
|
|
channelID := string(data[1:])
|
|
s.routingTable.Remove(channelID, addr)
|
|
|
|
case PacketTypeVoiceData:
|
|
if len(data) < 2 {
|
|
return
|
|
}
|
|
channelID := string(data[1:]) // à adapter selon ton vrai format
|
|
recipients := s.routingTable.GetAddrs(channelID)
|
|
|
|
for _, dst := range recipients {
|
|
// optionnel: ne pas renvoyer à la source
|
|
if dst.IP.Equal(addr.IP) && dst.Port == addr.Port {
|
|
continue
|
|
}
|
|
_, _ = conn.WriteToUDP(data, dst)
|
|
}
|
|
|
|
default:
|
|
// type inconnu -> ignore
|
|
}
|
|
}
|