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

Golang LSP服务器监听端口不明,如何正确连接客户端?

Golang LSP服务器连接问题排查与解决

问题背景

我用Golang写了一个简单的LSP服务器,代码能运行,但不清楚它在监听哪个端口,用Python脚本调用也失败了。原以为是4389端口,但测试后不对。

我的Golang LSP服务器代码

package main

import (
    "context"
    "fmt"
    "os"
    "sync"

    "github.com/sourcegraph/jsonrpc2"
)

type LSPServer struct {
    // 对称连接
    conn jsonrpc2.Conn

    // 连接可用性检查锁
    connMutex sync.Mutex

    // 关闭标记
    shutdown bool
}

func NewLSPServer() *LSPServer {
    return &LSPServer{}
}

func (s *LSPServer) Initialize(ctx context.Context) error {
    // 待实现
    return nil
}

func (s *LSPServer) Handle(context.Context, *jsonrpc2.Conn, *jsonrpc2.Request) (result interface{}, err error) {
    fmt.Println("Handling request...")
    // 待实现
    return nil, nil
}

func (s *LSPServer) Serve(ctx context.Context) {
    fmt.Println("Starting LSP server...")
    // 疑问:服务器在监听哪个端口?原以为是4389

    // 创建jsonrpc2流处理器
    handler := jsonrpc2.HandlerWithError(s.Handle)

    // 启动连接
    <-jsonrpc2.NewConn(
        context.Background(),
        jsonrpc2.NewBufferedStream(os.Stdin, jsonrpc2.VSCodeObjectCodec{}),
        handler).DisconnectNotify()
}

func main() {
    // 创建LSP服务器实例
    server := NewLSPServer()
    server.Serve(context.Background())
}

我的Python测试脚本

import json
import requests

def rpc_call(url, method, args):
    headers = {'content-type': 'application/json'}
    payload = {
        "method": method,
        "params": [args],
        "jsonrpc": "2.0",
        "id": 1,
    }
    response = requests.post(url, data=json.dumps(payload), headers=headers).json()
    return response['result']

url = 'http://localhost:4389/'

emailArgs = {'To': 'demo@example.com','Subject': 'Hello', 'Content': 'Hi!!!'}
smsArgs = {'Number': '381641234567', 'Content': 'Sms!!!'}
print(rpc_call(url, 'email.SendEmail', emailArgs))
print(rpc_call(url, 'sms.SendSMS', smsArgs))

问题分析

  1. 你的Golang服务器未监听TCP端口:当前代码采用LSP标准的stdio模式,通过进程的标准输入输出(os.Stdin/os.Stdout)进行通信,并没有绑定任何TCP端口,所以找不到4389端口的监听进程。
  2. Python脚本协议不匹配:脚本用requests.post发送HTTP请求,但LSP的stdio模式是直接通过进程间的流传输JSON-RPC消息,HTTP协议与JSON-RPC流协议不兼容,导致调用失败。

解决方法

方法一:改为TCP端口监听模式

修改Golang代码,让服务器监听指定TCP端口(比如4389),这样可以通过TCP直接连接:

修改后的Golang Serve函数(需添加net包导入)

import (
    // ... 原有导入
    "net"
)

func (s *LSPServer) Serve(ctx context.Context) {
    fmt.Println("Starting LSP server on port 4389...")
    
    // 监听TCP端口4389
    listener, err := net.Listen("tcp", ":4389")
    if err != nil {
        fmt.Printf("Failed to listen: %v\n", err)
        return
    }
    defer listener.Close()

    handler := jsonrpc2.HandlerWithError(s.Handle)

    // 循环接受客户端连接
    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Printf("Accept failed: %v\n", err)
            continue
        }
        // 为每个连接启动独立的JSON-RPC会话
        go func() {
            rpcConn := jsonrpc2.NewConn(
                ctx,
                jsonrpc2.NewBufferedStream(conn, jsonrpc2.VSCodeObjectCodec{}),
                handler,
            )
            <-rpcConn.DisconnectNotify()
            conn.Close()
        }()
    }
}

适配的Python TCP客户端脚本

import json
import socket

def rpc_call(host, port, method, args):
    # 构造JSON-RPC请求,添加换行符作为消息分隔
    payload = json.dumps({
        "method": method,
        "params": [args],
        "jsonrpc": "2.0",
        "id": 1,
    }) + "\n"

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((host, port))
        s.sendall(payload.encode('utf-8'))
        # 读取服务器响应
        response = s.recv(1024).decode('utf-8')
        return json.loads(response)['result']

host = 'localhost'
port = 4389

emailArgs = {'To': 'demo@example.com','Subject': 'Hello', 'Content': 'Hi!!!'}
smsArgs = {'Number': '381641234567', 'Content': 'Sms!!!'}
print(rpc_call(host, port, 'email.SendEmail', emailArgs))
print(rpc_call(host, port, 'sms.SendSMS', smsArgs))

方法二:保持stdio模式,进程间通信

如果要遵循LSP标准的stdio模式,Python脚本需要启动Golang服务器进程,通过stdin/stdout直接通信:

Python stdio客户端脚本

import json
import subprocess

def rpc_call(process, method, args):
    # LSP标准用\r\n作为消息分隔符
    payload = json.dumps({
        "method": method,
        "params": [args],
        "jsonrpc": "2.0",
        "id": 1,
    }) + "\r\n"
    process.stdin.write(payload.encode('utf-8'))
    process.stdin.flush()
    
    # 读取服务器响应
    response_line = process.stdout.readline().decode('utf-8')
    return json.loads(response_line)['result']

# 启动编译后的Golang服务器进程
server_process = subprocess.Popen(
    ['./your-lsp-server-binary'],  # 替换为你的二进制文件名
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE
)

emailArgs = {'To': 'demo@example.com','Subject': 'Hello', 'Content': 'Hi!!!'}
smsArgs = {'Number': '381641234567', 'Content': 'Sms!!!'}
try:
    print(rpc_call(server_process, 'email.SendEmail', emailArgs))
    print(rpc_call(server_process, 'sms.SendSMS', smsArgs))
finally:
    server_process.terminate()

额外提示

  • LSP有标准生命周期,客户端必须先发送initialize请求,服务器响应后才能处理自定义方法,建议补充Initialize函数实现,以及在Handle函数中处理initialize、initialized等标准方法。
  • stdio模式适合IDE插件场景,TCP模式更方便独立服务的测试与调用。

内容的提问来源于stack exchange,提问作者Matteo Possamai

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 22:05:28