xmdhs
本帖最后由 xmdhs 于 2022-6-29 16:30 编辑

获取和增加局域网服务器列表

众所周知,如果有局域网内有了对局域网开放的游戏,就会显示在多人游戏菜单中,服务器列表里的最底部。



原理

这种点了对局域网开放,就能让局域网内所有其他客户端都能看到的技术,是用到了名为组播的东西。通过加入组播组,所有发向这个组播地址的消息,都会接收到。这样就能做到自动发现对局域网开放的游戏。

而通过从组中监听,和向组发送信息,就能做到不少有趣的事情。

比如说,自动的获取游戏对局域网开放的端口,然后自动的进行端口映射。亦或是向服务器列表中自动的添加内容。

接收

minecraft 对局域网开放后,会每隔 1.5 秒向组播地址 224.0.2.60:4445 发送数据包,数据包内容为 utf-8 格式的字符串,内容如下

  1. [MOTD]motd[/MOTD][AD]port[/AD]

  2. 例子:
  3. [MOTD]新的世界[/MOTD][AD]6432[/AD]
复制代码

通过解析这个数据包,即可获知对局域网开放的游戏。

具体需要做的,是加入组播,然后接收消息,具体需要做的不同的编程语言也不同,可以自行搜索,这里给出 Go 的示例代码。

相关的代码示例

  1. package main

  2. import (
  3.         "context"
  4.         "errors"
  5.         "fmt"
  6.         "net"

  7.         "golang.org/x/net/ipv4"
  8. )

  9. func main() {
  10.         err := Listen(context.TODO(), "224.0.2.60:4445", "0.0.0.0", func(u *net.UDPAddr, b []byte) {
  11.                 fmt.Println(string(b), u.String())
  12.         })
  13.         if err != nil {
  14.                 panic(err)
  15.         }
  16. }

  17. const maxDatagramSize = 8024

  18. func Listen(cxt context.Context, address, laddress string, handler func(*net.UDPAddr, []byte)) error {
  19.         msgCh := make(chan readMsg, 10)
  20.         errCh := make(chan error, 10)

  21.         addr, err := net.ResolveUDPAddr("udp4", address)
  22.         if err != nil {
  23.                 return fmt.Errorf("Listen: %w", err)
  24.         }
  25.         il, err := net.Interfaces()
  26.         if err != nil {
  27.                 return fmt.Errorf("Listen: %w", err)
  28.         }
  29.         if laddress != "0.0.0.0" {
  30.                 lip := net.ParseIP(laddress)
  31.                 var itf *net.Interface
  32.         B:
  33.                 for _, v := range il {
  34.                         addr, _ := v.Addrs()
  35.                         for _, vv := range addr {
  36.                                 if ipnet, ok := vv.(*net.IPNet); ok && ipnet.IP.Equal(lip) {
  37.                                         itf = &v
  38.                                         break B
  39.                                 }
  40.                         }
  41.                 }
  42.                 if itf == nil {
  43.                         return fmt.Errorf("Listen: %w", ErrNotItf)
  44.                 }
  45.                 il = []net.Interface{*itf}
  46.         }

  47.         cxt, cancel := context.WithCancel(cxt)
  48.         defer cancel()

  49.         for _, v := range il {
  50.                 if v.Flags&net.FlagMulticast != net.FlagMulticast || v.Flags&net.FlagUp != net.FlagUp {
  51.                         continue
  52.                 }
  53.                 v := v
  54.                 go read(cxt, &v, addr, errCh, msgCh)
  55.         }
  56.         for {
  57.                 select {
  58.                 case err := <-errCh:
  59.                         return fmt.Errorf("Listen: %w", err)
  60.                 case msg := <-msgCh:
  61.                         handler(msg.addr, msg.msg)
  62.                 case <-cxt.Done():
  63.                         return nil
  64.                 }
  65.         }
  66. }

  67. var ErrNotItf = errors.New("not find interface")

  68. func read(cxt context.Context, itf *net.Interface, addr *net.UDPAddr, eCh chan<- error, msgCh chan<- readMsg) {
  69.         cxt, cancel := context.WithCancel(cxt)
  70.         defer cancel()
  71.         conn, err := net.ListenMulticastUDP("udp", itf, addr)
  72.         doErr := func(err error) {
  73.                 select {
  74.                 case <-cxt.Done():
  75.                 case eCh <- fmt.Errorf("read: %w", err):
  76.                 }
  77.         }
  78.         if err != nil {
  79.                 doErr(err)
  80.                 return
  81.         }
  82.         defer conn.Close()
  83.         go func() {
  84.                 <-cxt.Done()
  85.                 conn.Close()
  86.         }()
  87.         conn.SetReadBuffer(maxDatagramSize)
  88.         pc := ipv4.NewPacketConn(conn)
  89.         if err := pc.SetMulticastLoopback(true); err != nil {
  90.                 doErr(err)
  91.                 return
  92.         }
  93.         for {
  94.                 buffer := make([]byte, maxDatagramSize)
  95.                 numBytes, src, err := conn.ReadFromUDP(buffer)
  96.                 if err != nil {
  97.                         doErr(err)
  98.                         return
  99.                 }
  100.                 select {
  101.                 case msgCh <- readMsg{
  102.                         addr: src,
  103.                         msg:  buffer[:numBytes],
  104.                 }:
  105.                 case <-cxt.Done():
  106.                         return
  107.                 }
  108.         }
  109. }

  110. type readMsg struct {
  111.         addr *net.UDPAddr
  112.         msg  []byte
  113. }
复制代码

二进制

windows server.zip (741.36 KB, 下载次数: 7)

运行后,打开游戏,进入存档,点对局域网开放,就能在这个程序中看到相应的内容了。

发送

如果需要给服务器列表添加内容,需要做的事就简单不少,只需要向 224.0.2.60:4445 发送 udp 数据包即可,内容和上面提到的一样。

演示代码

  1. package main

  2. import (
  3.     "fmt"
  4.     "math/rand"
  5.     "net"
  6.     "os"
  7.     "strconv"
  8.     "sync"
  9.     "time"
  10. )

  11. func main() {
  12.     for i := 0; i < 100; i++ {
  13.         port := r.Intn(9999) + 1
  14.         go sendPing(randStr(10, []byte(atext)), strconv.Itoa(port))
  15.     }
  16.     os.Stdin.Read(make([]byte, 1))
  17. }

  18. type arand struct {
  19.     *rand.Rand
  20.     *sync.Mutex
  21. }

  22. var r = arand{
  23.     Rand:  rand.New(rand.NewSource(time.Now().Unix())),
  24.     Mutex: &sync.Mutex{},
  25. }

  26. const atext = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%`

  27. func randStr(n int, words []byte) string {
  28.     b := make([]byte, n)
  29.     r.Lock()
  30.     for i := range b {
  31.         b[i] = words[r.Intn(len(words))]
  32.     }
  33.     r.Unlock()
  34.     return string(b)
  35. }

  36. func sendPing(motd, port string) {
  37.     dstAddr, err := net.ResolveUDPAddr("udp", "224.0.2.60:4445")
  38.     if err != nil {
  39.         panic(err)
  40.     }
  41.     srcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0}
  42.     conn, err := net.ListenUDP("udp", srcAddr)
  43.     if err != nil {
  44.         fmt.Println(err)
  45.     }
  46.     msg := []byte(fmt.Sprintf(`[MOTD]%s[/MOTD][AD]%s[/AD]`, motd, port))
  47.     for {
  48.         _, err := conn.WriteToUDP(msg, dstAddr)
  49.         if err != nil {
  50.             panic(err)
  51.         }
  52.         time.Sleep(2 * time.Second)
  53.     }
  54. }
复制代码

二进制

windows client.zip (696.73 KB, 下载次数: 2)

运行后,打开游戏,点击多人联机,底部就会有一堆虚假的局域网服务器了。


紫熙
我看不懂,但我大受震撼.jpg

TheSmiler_Zuny
确实大受震撼,一个都看不懂但我觉得很牛逼

第一页 上一页 下一页 最后一页