news 2026/10/23 2:54:55

java实现Windows 命名管道:千万别在读取时“死等”

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
java实现Windows 命名管道:千万别在读取时“死等”

1.代码背景

博主想实现一个跨平台通信机制来连接LSP服务端,想通过unix domian socket来实现这个。在java实现客户端连接时,出现了问题,JDK 原生搞不定 Windows 命名管道,Java 的网络库只认 Socket 家族,不认“文件”家族。所以想直接访问这个通道,就必须用 Kernel32 。

2. 问题分析

  • 描述 :在 Windows 上,如果用一个线程 ReadFile 阻塞等数据,主线程想 WriteFile 发消息时会直接卡死。博主在测试过程中,发现能收到服务端的通知,但是在发送消息后,服务端无法收到。经过判断,是在发送时卡住,然后研究出具体原因。由此记录一下过程
  • 原因 :同步模式下,读和写共用同一个句柄锁。读不完,锁不放,写不进。
  • 避坑指南 :在 Windows 环境下, 必须先“轮询”再“读取” 。
    • 使用 PeekNamedPipe 检查是否有数据。
    • 没数据就 Thread.sleep(20) ,让出句柄锁。
  • 选择同步的理由
    • 代码简洁度 :重叠 I/O 在 Java/JNA 里的实现非常臃肿,容易引入内存泄漏或指针错误。
    • 兼容性 :LSP 服务端通常一次只允许一个连接,双句柄方案可能会导致服务端报错“管道忙”。

3.代码实现

packageorg.example.lspClient;importcom.sun.jna.platform.win32.Kernel32;importcom.sun.jna.platform.win32.WinBase;importcom.sun.jna.platform.win32.WinNT;importcom.sun.jna.ptr.IntByReference;importorg.tinylog.Logger;importjava.io.*;importjava.net.StandardProtocolFamily;importjava.net.UnixDomainSocketAddress;importjava.nio.ByteBuffer;importjava.nio.channels.SocketChannel;importjava.nio.charset.StandardCharsets;importjava.nio.file.Path;importjava.util.Arrays;importjava.util.function.Consumer;/** * 跨平台 UDS 传输层,具有 LSP 协议感知能力 */publicclassUdsTransportimplementsCloseable{privatefinalbooleanisWindows;privateWinNT.HANDLE hPipe;privateSocketChannelchannel;privatefinalByteArrayOutputStreambuffer=newByteArrayOutputStream();privateConsumer<String>messageListener;privatevolatilebooleanrunning=true;publicUdsTransport(StringsocketPath)throwsIOException{Stringos=System.getProperty("os.name").toLowerCase();this.isWindows=os.contains("win");if(isWindows){connectWindowsPipe(socketPath);}else{connectUnixSocket(socketPath);}startReadingThread();}publicvoidsetOnMessage(Consumer<String>listener){this.messageListener=listener;}privatevoidconnectWindowsPipe(StringpipeName)throwsIOException{this.hPipe=Kernel32.INSTANCE.CreateFile(pipeName,WinNT.GENERIC_READ|WinNT.GENERIC_WRITE,0,null,WinNT.OPEN_EXISTING,0,null);if(WinBase.INVALID_HANDLE_VALUE.equals(hPipe)){interror=Kernel32.INSTANCE.GetLastError();thrownewIOException("Failed to connect to Named Pipe: "+pipeName+" (Error: "+error+")");}}privatevoidconnectUnixSocket(StringsocketPath)throwsIOException{this.channel=SocketChannel.open(StandardProtocolFamily.UNIX);this.channel.connect(UnixDomainSocketAddress.of(Path.of(socketPath)));}publicvoidsendMessage(StringjsonContent)throwsIOException{byte[]body=jsonContent.getBytes(StandardCharsets.UTF_8);Stringheader="Content-Length: "+body.length+"\r\n\r\n";writeRaw(header.getBytes(StandardCharsets.US_ASCII));writeRaw(body);}privatevoidwriteRaw(byte[]bytes)throwsIOException{if(isWindows){IntByReferencewritten=newIntByReference();booleansuccess=Kernel32.INSTANCE.WriteFile(hPipe,bytes,bytes.length,written,null);if(!success){interr=Kernel32.INSTANCE.GetLastError();thrownewIOException("WriteFile failed with error: "+err);}if(written.getValue()<bytes.length){Logger.warn("Partial write detected: "+written.getValue()+"/"+bytes.length);return;}}else{ByteBuffernioBuffer=ByteBuffer.wrap(bytes);while(nioBuffer.hasRemaining()){intwritten=channel.write(nioBuffer);if(written==0){thrownewIOException("Channel write stalled");}}}Logger.info("Wrote bytes to: "+Arrays.toString(bytes));}privatevoidstartReadingThread(){Threadthread=newThread(()->{byte[]readBuffer=newbyte[8192];try{while(running){intn=readToBuffer(readBuffer);if(n>0){synchronized(buffer){buffer.write(readBuffer,0,n);}processBuffer();}else{// 给WriteFile 竞争句柄锁的机会Thread.sleep(20);}}}catch(Exceptione){if(running)System.err.println("Transport read error: "+e.getMessage());}},"UdsTransport-Reader");thread.setDaemon(true);thread.start();}privateintreadToBuffer(byte[]target)throwsIOException{if(isWindows){IntByReferenceavail=newIntByReference();// 轮询是否有数据booleanpeekOk=Kernel32.INSTANCE.PeekNamedPipe(hPipe,null,0,null,avail,null);if(!peekOk){interr=Kernel32.INSTANCE.GetLastError();if(err==109)thrownewEOFException("Pipe closed");return0;}if(avail.getValue()==0){return0;// 当前没数据,返回 0 让出 CPU 和句柄锁}IntByReferenceread=newIntByReference();// 此时 ReadFile 不会阻塞,因为它已经知道有数据了booleansuccess=Kernel32.INSTANCE.ReadFile(hPipe,target,target.length,read,null);if(!success){interr=Kernel32.INSTANCE.GetLastError();if(err==109)thrownewEOFException("Pipe closed");if(err==234)returntarget.length;// ERROR_MORE_DATAthrownewIOException("ReadFile failed: "+err);}returnread.getValue();}else{ByteBuffernioBuf=ByteBuffer.wrap(target);intn=channel.read(nioBuf);if(n==-1)thrownewEOFException("Socket closed");returnn;}}privatevoidprocessBuffer(){synchronized(buffer){while(true){byte[]data=buffer.toByteArray();if(data.length==0)break;StringtempStr=newString(data,StandardCharsets.US_ASCII);intheaderEnd=tempStr.indexOf("\r\n\r\n");if(headerEnd==-1)break;intcontentLength=-1;String[]lines=tempStr.substring(0,headerEnd).split("\r\n");for(Stringline:lines){if(line.toLowerCase().startsWith("content-length:")){contentLength=Integer.parseInt(line.substring(15).trim());}}if(contentLength==-1)break;inttotalSize=headerEnd+4+contentLength;if(data.length<totalSize)break;byte[]bodyBytes=newbyte[contentLength];System.arraycopy(data,headerEnd+4,bodyBytes,0,contentLength);Stringmessage=newString(bodyBytes,StandardCharsets.UTF_8);// 异步回调if(messageListener!=null){messageListener.accept(message);}byte[]remaining=newbyte[data.length-totalSize];System.arraycopy(data,totalSize,remaining,0,remaining.length);buffer.reset();try{buffer.write(remaining);}catch(IOExceptionignored){}}}}@Overridepublicvoidclose()throwsIOException{running=false;if(isWindows){if(hPipe!=null)Kernel32.INSTANCE.CloseHandle(hPipe);}else{if(channel!=null)channel.close();}}}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 5:44:42

【vtkPolyDataPointSampler 】——多边形数据点采样技术详解

VTK核心类解析:vtkPolyDataPointSampler 多边形数据点采样技术详解 简单说,vtkPolyDataPointSampler 就是 VTK里的 “点生成器”—— 专门给 3D 模型(比如三角形、多边形组成的物体)表面或内部,按需求打一堆采样点,生成的点云能直接用在后续的可视化、建模或计算里。 一…

作者头像 李华
网站建设 2026/10/20 5:44:42

电子签章选型指南:云巨头生态服务与垂直专业厂商的六大维度解析

随着电子签章应用在市场越来越普及和受追捧&#xff0c;超级大厂也相继推出了自己的电子签章产品&#xff0c;如华为的华为云电子签、阿里的阿里云电子签、腾讯的腾讯电子签服务。那这些大厂推出的电子签章产品和服务与传统第三方电子签公司北京安证通有什么相同和区别呢&#…

作者头像 李华
网站建设 2026/10/20 22:14:14

具身智能:行业成果与核心困难点全解析

具身智能作为 AI 与机器人融合的前沿领域&#xff0c;正从实验室走向产业应用&#xff0c;呈现出技术突破与落地挑战并存的发展态势。以下是当前行业成果与核心困难点的系统梳理。 一、行业成果&#xff1a;技术突破与场景落地加速 1. 核心技术突破 技术维度关键成果典型案例…

作者头像 李华
网站建设 2026/10/22 3:34:00

大模型微调全攻略:定制专属模型,简单得像打开浏览器

文章系统介绍大模型微调生态系统&#xff0c;详述多种开源(Qwen、DeepSeek等)和闭源(GPT、Claude等)模型特点及适用场景&#xff0c;对比全参数、LoRA/QLoRA等微调方法的算力需求与效果。重点推荐LLaMA-Factory Online平台&#xff0c;通过其可视化界面&#xff0c;开发者无需编…

作者头像 李华
网站建设 2026/10/21 0:46:19

⓫⁄₇ ⟦ OSCP ⬖ 研记 ⟧ Windows权限提升 ➱ 滥用Windows服务提权(上)

郑重声明&#xff1a;本文所涉安全技术仅限用于合法研究与学习目的&#xff0c;严禁任何形式的非法利用。因不当使用所导致的一切法律与经济责任&#xff0c;本人概不负责。任何形式的转载均须明确标注原文出处&#xff0c;且不得用于商业目的。 &#x1f50b; 点赞 | 能量注入…

作者头像 李华
网站建设 2026/10/22 9:04:02

博弈思维是可以学习的

博弈思维是可以学习的很多人提起博弈思维&#xff0c;总会下意识觉得它是“天生的智慧”——是那些善于算计、懂得权衡的人&#xff0c;与生俱来的能力&#xff0c;普通人难以企及。但事实上&#xff0c;博弈思维从来不是天赋的专属&#xff0c;它和读书、写字、思考一样&#…

作者头像 李华