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))
问题分析
- 你的Golang服务器未监听TCP端口:当前代码采用LSP标准的stdio模式,通过进程的标准输入输出(
os.Stdin/os.Stdout)进行通信,并没有绑定任何TCP端口,所以找不到4389端口的监听进程。 - 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
相关产品推荐
相关产品推荐

