You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go TCP服务器conn.Read异常延迟问题排查求助

远程Go TCP服务器中conn.Read读取GET命令延迟问题

问题现象

  • 本地或同一网络环境运行时无异常,部署到远程服务器后,读取不带末尾空格的GET mykey命令时出现13-20秒的显著延迟;
  • 发送带末尾空格的GET mykey 时无延迟;
  • 无论第一条命令是否延迟,后续所有命令均能正常无延迟执行;
  • 延迟明确出现在conn.Read环节,使用bufio.Reader或bufio.NewScanner均存在相同问题;
  • 补充测试:将GET替换为其他命令名、或客户端与服务器间添加TLS加密连接后,延迟彻底消失。

代码示例

package main

import (
    "bufio"
    "fmt"
    "net"
    "strings"
    "sync"
    "time"
)

type Store struct {
    mu    sync.RWMutex
    store map[string]string
}

func NewStore() *Store {
    return &Store{
        store: make(map[string]string),
    }
}

func (s *Store) Set(key, value string) {
    s.mu.Lock()
    defer s.mu.Unlock()
    s.store[key] = value
}

func (s *Store) Get(key string) (string, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    value, ok := s.store[key]
    return value, ok
}

func handleConnection(conn net.Conn, store *Store) {
    defer conn.Close()
    reader := bufio.NewReader(conn)
    writer := bufio.NewWriter(conn)

    for {
        //conn.SetReadDeadline(time.Now().Add(10 * time.Second))

        startRead := time.Now()
        input, err := reader.ReadString('\n')
        readDuration := time.Since(startRead)
        fmt.Println("Read duration:", readDuration)

        if err != nil {
            fmt.Println("Connection closed or read error:", err)
            return
        }

        input = strings.TrimSpace(input)
        parts := strings.SplitN(input, " ", 3)

        if len(parts) == 0 {
            continue
        }

        command := parts[0]
        switch command {
        case "SET":
            if len(parts) != 3 {
                fmt.Fprintln(writer, "ERROR: Usage: SET <key> <value>")
                writer.Flush()
                continue
            }
            key := parts[1]
            value := parts[2]
            store.Set(key, value)
            fmt.Fprintln(writer, "OK")
            writer.Flush()

        case "GET":
            if len(parts) != 2 {
                fmt.Fprintln(writer, "ERROR: Usage: GET <key>")
                writer.Flush()
                continue
            }
            key := parts[1]
            value, ok := store.Get(key)
            if !ok {
                fmt.Fprintln(writer, "ERROR: Key not found")
            } else {
                fmt.Fprintln(writer, value)
            }
            writer.Flush()

        default:
            fmt.Fprintln(writer, "ERROR: Unknown command")
            writer.Flush()
        }
    }
}

func main() {
    store := NewStore()
    port := ":12345"

    listener, err := net.Listen("tcp", port)
    if err != nil {
        fmt.Println("Error starting server:", err)
        return
    }
    defer listener.Close()

    fmt.Println("Server started on port", port)

    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("Error accepting connection:", err)
            continue
        }

        go handleConnection(conn, store)
    }
}

原因分析

从现象和补充测试结果来看,延迟由**中间代理/防火墙的深度包检测(DPI)**导致:

  • 中间设备会对明文TCP流量中的关键字(如GET,属于HTTP请求的标志性前缀)进行拦截分析,尝试识别是否为HTTP流量并执行对应处理逻辑;
  • 当发送GET mykey时,包内容符合HTTP请求的初始格式特征,触发DPI的HTTP检测流程,比如等待是否有后续HTTP头字段,超时后才放行,从而产生延迟;
  • 末尾加空格后,GET mykey 不符合标准HTTP请求的格式规则,DPI无法匹配HTTP特征,直接放行流量;
  • 替换命令名或启用TLS加密后,流量无法被DPI识别为HTTP请求,因此不再被拦截延迟。

解决方案

可根据实际场景选择以下方案:

  1. 启用TLS加密:对TCP连接添加TLS加密层,让中间设备无法明文解析命令内容,彻底规避DPI拦截。可使用Go标准库tls包实现,修改监听和连接处理逻辑即可;
  2. 修改命令关键字:将GET替换为非HTTP标志性的命令名(如FETCH),避免触发DPI的HTTP检测规则;
  3. 配置中间设备规则:若有权限访问代理/防火墙设备,可添加规则放行该TCP端口的所有流量,跳过DPI检测;
  4. 调整命令格式:强制所有命令添加固定分隔符或调整格式,让DPI无法匹配HTTP请求特征,但这种方式健壮性较差,不推荐作为长期方案。

内容的提问来源于stack exchange,提问作者John G.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 21:23:17