当前位置: 首页 > news >正文

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();}}}
http://www.jsqmd.com/news/318195/

相关文章:

  • 必看!2026年十大溯源码好用的产品推荐榜单,引领安全追溯新风尚
  • YMatrix Anonymizer 上线:轻松实现字段级灵活脱敏!
  • Java开发者效率革命——飞算JavaAI,告别无效debug,实现准点下班自由
  • 完整教程:谷歌杀疯了,发布AI IDE,免费使用最新的Gemini3
  • Redshift vs Octane 深度对比:哪款渲染器更适合 Cinema 4D 工作流程?
  • 本地商家做小红书:自运营or代运营,看这篇就够了
  • Element Plus Menu组件,实现点击目录而不是叶子节点也可以跳转,且支持高亮状态更新
  • 多大模型 API 统一调用解决方案:6 个 GitHub 开源项目深度推荐
  • 2026年环保移动公厕厂家最新推荐:免水冲移动公厕/环保移动公厕/移动厕所/选择指南
  • 股票资金流实时数据接口分享
  • Kimi K2.5 模型上线 AtomGit,全能 Agent 模型,视觉理解、代码和思考 All in One
  • 2026年在线浓度计行业深度解析与厂家推荐:豆浆/酒精/尿素/切削液/乳化液/盐水在线浓度计头部厂家
  • JavaScript 中 Proxy 的 apply 捕获器(trap)的语法和具体用法
  • AtomGit 开源雷达 第 1 期:这些开源项目,正在被开发者偷偷使用
  • 电容漏电流的测量方式
  • 低空经济产业动态——2025 年度盘点与趋势观察
  • 2026年北京肝肿瘤诊疗机构top5推荐指南:北京甲状腺肿瘤/北京直肠肿瘤/北京红斑狼疮/北京结肠肿瘤/北京肾肿瘤/选择指南
  • 2026中国软件供应链安全产业全景图谱与核心厂商能力分析
  • 腾讯轻量云服务器的优点
  • 2026年高口碑卫生无纺布和包装无纺布生产厂家推荐,助你轻松选择优质产品!
  • 电子签章选型指南:云巨头生态服务与垂直专业厂商的六大维度解析
  • 跨境直播必看:深度对比Whatnot与Tiktok两个直播电商平台的核心差异
  • 什么情况下使用腾讯云服务器
  • 2026国内最新衣柜专用板材十大公司推荐!山东等地优质板材品牌权威榜单发布,环保品质双优助力高品质家居生活
  • 锂电池建模仿真:等效电路模型
  • 最长的白色段
  • 【vtkPolyDataPointSampler 】——多边形数据点采样技术详解
  • 四川市场调查优质机构推荐榜:成都找人公司电话/成都找人电话/找人电话/四川市场调查公司电话/四川市场调查电话/四川找人公司/选择指南
  • 2026年高铁广告公司哪家好:五大专业优质高铁广告公司权威盘点
  • Tailwind CSS