• go: 如何编写一个正确的udp服务端


    udp的服务端有一个大坑,即如果收包不及时,在系统缓冲写满后,将大量丢包。

    在网上通常的示例中,一般在for循环中执行操作逻辑。这在生产环境将是一个隐患。是的,俺就翻车了。

    go强大简易的并发能力可以用在处理udp数据上。

    	PoolSizeUDP := 1472
    	listener, err := net.ListenUDP("udp", &net.UDPAddr{
    		IP:   net.ParseIP(listenIP),
    		Port: port,
    	})
    	if err != nil {
    		logrus.Fatalf("RunUdpServer failed to listen: %v", err)
    		return nil
    	}
    	// 如果还不行,考虑把系统的buffer设大一点
    	// listener.SetReadBuffer(1024 * 1024 * 8)
    	// listener.SetWriteBuffer(1024 * 1024 * 8)
    	var data = make([]byte, PoolSizeUDP)
    	chLimit := make(chan int, 64) // 最多创建64个协程,避免内存爆炸
    	for {
    		select {
    		case <-ctx.Done():
    			return nil
    		default:
    		}
    		n, addr, err := listener.ReadFromUDP(data)
    		if err != nil {
    			logrus.Errorf("RunUdpServer ReadFromUDP err: %v", err)
    			continue
    		}
    		raw := make([]byte, n) // 重点注意,每次循环都必须创建新的raw变量,否则踩内存
    		copy(raw, data[:n])
    		chLimit <- 1
    		go func(udpMsg []byte) {
    			// 拿 udpMsg 做点什么
    			defer func() {
    				<-chLimit
    			}()
    			DoSth(udpMsg)
    		}(raw)
    	}
    
    折叠

    注意点:

    1. data可以在循环外创建,复用即可。每次ReadFromUDP并不会受到上次数据残留的影响。
    2. 不要在for中执行重逻辑,避免等待太久时间udp大量丢包。所以每次收到udpMsg,都交给go协程来处理。
    3. raw必须每次在循环内创建,否则在后面的go并发会踩内存。
    4. SetReadBuffer这个配置很有用

    更新:上面的示例为了避免在后续的go中,有不可控的异步操作引用了数据导致踩内存,每次收消息都分配了新的[]byte

    raw := make([]byte, n) // 重点注意,每次循环都必须创建新的raw变量,否则踩内存
    copy(raw, data[:n])
    

    经大佬提醒,这其实是一个不小的开销。如果go中执行的行为可控,引入sync.Pool可以很方便的做内存复用。

    var udpBytesPool = sync.Pool{
    	New: func() any {
    		return make([]byte, PoolSizeUDP)
    	},
    }
    ......
    	for {
    		data := udpBytesPool.Get().([]byte)
    		n, addr, err := listener.ReadFromUDP(data)
    		if err != nil {
    			logrus.Errorf("RunUdpServer ReadFromUDP err: %v", err)
    			continue
    		}
    		go func(){
    			defer udpBytesPool.Put(data) // 注意,在协程退出执行这个操作时,一定确认 data不会再被引用了
    		// do sth
    		}()
    	}
    
  • 相关阅读:
    使用 diffusers 训练你自己的 ControlNet 🧨
    SpringBoot - SpringBoot整合Flyway实现数据库的迁移
    220V交流转直流的简易电源设计
    如何使用ArcGIS Pro提取河网水系
    一个重量级HTTP api的304优化分析与突发失效问题解决
    【Java】Java 代码注释是代码逻辑的灵魂
    Linux执行脚本报错:-bash: ./bin/start.sh: /bin/bash^M: 坏的解释器: 没有那个文件或目录
    一致性哈希算法
    【广州华锐互动】气象卫星监测AR互动教学软件为气象学习带来更多乐趣
    切换npm的版本
  • 原文地址:https://www.cnblogs.com/superpigwin/p/16424617.html