C# TCP套接字通信实战:从粘包拆包到异步多客户端完整指南
1. 从零理解TCP套接字通信到底在做什么1.1 为什么选择TCP而不是UDP很多刚接触网络编程的朋友会问同样是传数据为什么一上来就要学TCP我直接用UDP不是更简单吗这个问题我在带新人的时候被问过不下几十次。答案其实不复杂——TCP帮你处理了“数据到底有没有完整到达”这件事而UDP把这个问题甩给了你自己。打个比方TCP就像寄挂号信对方签收了你才放心UDP就像往楼下扔纸条扔出去就不管了风一吹可能就没了。对于绝大多数业务场景——文件传输、聊天消息、指令下发——你都需要确认数据完整到达所以TCP是默认选择。C#里用TCP做套接字通信核心就是两个类TcpListener服务端监听和TcpClient客户端连接。这两个类封装了底层Socket的复杂性让代码写起来清爽很多。但封装归封装底层发生了什么你心里得有数不然出了问题根本不知道怎么排查。1.2 一个完整的通信链路包含哪些环节服务端要做的事情绑定IP和端口、开始监听、接受客户端连接、收发数据、关闭连接。客户端要做的事情发起连接、收发数据、关闭连接。看起来简单但每个环节都有坑。我见过太多人写完代码发现连不上排查半天发现是防火墙没放行端口也见过数据发出去对方收不到最后发现是没加消息边界导致粘包。这些问题不是代码写错了而是对通信过程的理解不够完整。所以这篇文章我会按照“设计思路→核心细节→完整实操→问题排查”的顺序来讲每一步都告诉你为什么要这么做以及不这么做会出什么问题。你跟着走一遍基本就能独立写出稳定的TCP通信程序了。1.3 适合哪些人看如果你正在学C#网络编程或者工作中需要做一个简单的客户端-服务端通信功能这篇文章就是写给你的。不需要你有多深的底层知识但至少要会写C#的基本语法知道什么是类、什么是异步方法。如果你连async/await都没用过建议先补一下这块不然看后面的异步代码会有点吃力。另外说明一下我这里用的是原生TcpListener和TcpClient没有引入任何第三方库。原因很简单——先把原生的搞明白以后用任何封装库你都能一眼看穿它在干什么。上来就用高级框架出了问题你连排查的方向都没有。2. 核心细节拆解与实操前的关键决策2.1 同步还是异步这个选择决定了你的程序能不能用写TCP通信第一个要做的决策就是用同步方法还是异步方法我刚开始学的时候用的是同步的AcceptTcpClient()和Read()写出来的程序只能同时服务一个客户端第二个客户端连上来就卡住了。后来改成异步问题迎刃而解。同步方式的问题在于AcceptTcpClient()会阻塞当前线程直到有客户端连上来。这意味着你的程序在等待连接的时候什么都做不了。如果在一个UI程序里这么写界面直接卡死。异步方式用AcceptTcpClientAsync()、ReadAsync()、WriteAsync()不会阻塞线程程序在等待的时候还能干别的事。对于服务端来说异步是必须的否则你只能一对一服务。// 同步方式——不推荐用于服务端 TcpClient client listener.AcceptTcpClient(); // 这里会卡住 // 异步方式——推荐 TcpClient client await listener.AcceptTcpClientAsync(); // 不会卡住注意异步方法不是万能的。如果你在异步方法里调用了同步的阻塞方法照样会卡。比如在async方法里用stream.Read()而不是await stream.ReadAsync()那就白改了。2.2 消息边界问题为什么你的数据会粘在一起这是TCP通信里最经典的问题没有之一。TCP是面向流的协议它不关心你发的是什么只负责把字节按顺序送到对面。你发了三条消息对面可能一次收到全部三条也可能分三次收到甚至可能一条消息分两次收到。我举个例子你发了Hello和World两条消息对面可能收到HelloWorld一次也可能收到Hel和loWorld两次。这就是所谓的粘包和拆包问题。解决办法有三种方案做法适用场景复杂度固定长度每条消息固定N个字节不够补空格消息长度固定的场景低分隔符每条消息末尾加特殊字符如\n文本协议消息内不含分隔符低长度前缀消息头4字节表示消息体长度通用场景推荐中我一般推荐长度前缀方案因为它最通用不管消息内容是什么都不会出问题。具体做法是发送时先写4个字节表示消息体长度再写消息体接收时先读4个字节得到长度再按长度读消息体。// 发送先写长度再写内容 byte[] body Encoding.UTF8.GetBytes(message); byte[] lengthPrefix BitConverter.GetBytes(body.Length); await stream.WriteAsync(lengthPrefix, 0, 4); await stream.WriteAsync(body, 0, body.Length); // 接收先读长度再读内容 byte[] lengthBuffer new byte[4]; await ReadExactAsync(stream, lengthBuffer, 4); int bodyLength BitConverter.ToInt32(lengthBuffer, 0); byte[] bodyBuffer new byte[bodyLength]; await ReadExactAsync(stream, bodyBuffer, bodyLength); string message Encoding.UTF8.GetString(bodyBuffer);这里有个关键点ReadAsync不保证一次读完你要求的字节数。你可能请求读4个字节结果只读到2个。所以需要一个ReadExactAsync辅助方法来循环读取直到读满。2.3 端口选择与IP绑定的注意事项服务端绑定的时候用IPAddress.Any表示监听所有网卡端口选1024以上的一般没问题。但有几个坑要注意第一端口被占用的问题。如果你选的端口已经被其他程序占了Start()会抛异常。解决办法是换端口或者用netstat -ano | findstr 端口号找到占用进程处理掉。第二防火墙问题。Windows防火墙默认会拦截外部连接。你在本机测试可能没问题但局域网其他机器连不上八成是防火墙没放行。测试阶段可以先临时关闭防火墙验证正式部署时记得添加入站规则。第三IPAddress.Loopback和IPAddress.Any的区别。Loopback是127.0.0.1只能本机连Any是0.0.0.0所有网卡都能连。本机测试用哪个都行局域网测试必须用Any。2.4 编码格式的统一发送和接收两端必须用同一种编码。我一般用UTF-8因为它是万国码中文英文都不会出问题。如果你一端用UTF-8另一端用GB2312中文就会变乱码。// 两端统一用UTF-8 byte[] data Encoding.UTF8.GetBytes(你好世界); string text Encoding.UTF8.GetString(data);提示Encoding.Default在不同系统上可能不一样不要用它。明确指定UTF-8省得后面出问题找不到原因。3. 完整实操从零搭建一个可用的TCP通信程序3.1 服务端完整实现先来看服务端的完整代码。我把它写成一个控制台程序方便你直接复制运行。using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; class TcpServer { private static readonly ListTcpClient _clients new ListTcpClient(); static async Task Main(string[] args) { int port 8888; TcpListener listener new TcpListener(IPAddress.Any, port); listener.Start(); Console.WriteLine($服务端已启动监听端口 {port}); while (true) { TcpClient client await listener.AcceptTcpClientAsync(); Console.WriteLine($客户端已连接{client.Client.RemoteEndPoint}); _clients.Add(client); _ HandleClientAsync(client); // 不等待让每个客户端独立处理 } } static async Task HandleClientAsync(TcpClient client) { NetworkStream stream client.GetStream(); byte[] lengthBuffer new byte[4]; try { while (true) { // 读取消息长度 int read await ReadExactAsync(stream, lengthBuffer, 4); if (read 0) break; // 客户端断开 int bodyLength BitConverter.ToInt32(lengthBuffer, 0); if (bodyLength 0 || bodyLength 1024 * 1024) break; // 防止异常数据 byte[] bodyBuffer new byte[bodyLength]; read await ReadExactAsync(stream, bodyBuffer, bodyLength); if (read 0) break; string message Encoding.UTF8.GetString(bodyBuffer); Console.WriteLine($收到消息{message}); // 回复客户端 string reply $服务端已收到{message}; byte[] replyBody Encoding.UTF8.GetBytes(reply); byte[] replyLength BitConverter.GetBytes(replyBody.Length); await stream.WriteAsync(replyLength, 0, 4); await stream.WriteAsync(replyBody, 0, replyBody.Length); } } catch (Exception ex) { Console.WriteLine($客户端处理异常{ex.Message}); } finally { _clients.Remove(client); client.Close(); Console.WriteLine(客户端已断开); } } static async Taskint ReadExactAsync(NetworkStream stream, byte[] buffer, int count) { int offset 0; while (offset count) { int read await stream.ReadAsync(buffer, offset, count - offset); if (read 0) return offset; // 连接关闭 offset read; } return offset; } }这段代码有几个关键设计点需要解释第一_ HandleClientAsync(client)前面的下划线表示“我不关心这个任务的返回值”。这样每个客户端连接都会独立运行在自己的任务里不会阻塞主循环接受新连接。第二ReadExactAsync方法解决了ReadAsync可能读不满的问题。它循环读取直到读满指定字节数或者连接关闭。第三bodyLength做了范围检查防止恶意客户端发送一个超大长度导致内存溢出。这个检查在实际项目中非常重要。第四finally块确保无论发生什么异常客户端都会被清理掉不会造成资源泄漏。3.2 客户端完整实现客户端代码相对简单一些主要就是连接、发送、接收、关闭。using System; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; class TcpClientDemo { static async Task Main(string[] args) { string serverIp 127.0.0.1; int port 8888; TcpClient client new TcpClient(); try { await client.ConnectAsync(serverIp, port); Console.WriteLine(已连接到服务端); NetworkStream stream client.GetStream(); while (true) { Console.Write(请输入消息输入exit退出); string input Console.ReadLine(); if (input exit) break; // 发送消息 byte[] body Encoding.UTF8.GetBytes(input); byte[] lengthPrefix BitConverter.GetBytes(body.Length); await stream.WriteAsync(lengthPrefix, 0, 4); await stream.WriteAsync(body, 0, body.Length); // 接收回复 byte[] lengthBuffer new byte[4]; await ReadExactAsync(stream, lengthBuffer, 4); int replyLength BitConverter.ToInt32(lengthBuffer, 0); byte[] replyBuffer new byte[replyLength]; await ReadExactAsync(stream, replyBuffer, replyLength); string reply Encoding.UTF8.GetString(replyBuffer); Console.WriteLine($服务端回复{reply}); } } catch (Exception ex) { Console.WriteLine($连接异常{ex.Message}); } finally { client.Close(); Console.WriteLine(连接已关闭); } } static async Taskint ReadExactAsync(NetworkStream stream, byte[] buffer, int count) { int offset 0; while (offset count) { int read await stream.ReadAsync(buffer, offset, count - offset); if (read 0) return offset; offset read; } return offset; } }客户端的ReadExactAsync和服务端是一样的逻辑实际项目中可以抽到一个公共类里。这里为了让你看得清楚两边都写全了。3.3 测试验证怎么确认真的通了先运行服务端看到“服务端已启动”的输出。再运行客户端看到“已连接到服务端”。然后在客户端输入消息服务端控制台会打印收到的消息客户端会收到服务端的回复。如果你在本机测试用127.0.0.1就行。如果要局域网测试把客户端的serverIp改成服务端机器的局域网IP同时确认服务端防火墙放行了8888端口。测试的时候建议多开几个客户端验证服务端能不能同时处理多个连接。你会发现每个客户端都能正常收发互不影响。这就是异步独立任务的效果。3.4 优雅关闭与资源释放很多人写完功能就不管关闭了结果程序跑久了句柄泄漏、端口占用。正确的关闭流程是客户端先关闭发送流stream.Close()或client.Close()服务端读到0字节知道对面关了然后清理自己的资源服务端关闭对应的TcpClient在C#里TcpClient实现了IDisposable所以最好用using包裹或者确保在finally里调用Close()。NetworkStream也一样不过关闭TcpClient会自动关闭关联的流。注意不要只关NetworkStream不关TcpClient也不要只关TcpClient不关NetworkStream。虽然TcpClient.Close()会顺带关闭流但显式管理更清晰。4. 常见问题与排查技巧实录4.1 连接被拒绝目标机器积极拒绝这是最常见的错误没有之一。报错信息一般是“由于目标计算机积极拒绝无法连接”或者“No connection could be made because the target machine actively refused it”。原因通常有三个服务端没启动、IP或端口写错了、防火墙拦截了。排查步骤先确认服务端程序在运行再看服务端监听的端口和你客户端连接的是不是一致最后检查防火墙。本机测试时如果服务端绑定的是IPAddress.Any客户端连127.0.0.1一般没问题。如果服务端绑定的是某个具体IP客户端连127.0.0.1可能就连不上。4.2 数据发了但对方收不到这种情况一般是消息边界处理有问题。你可能发了数据但对方还在等长度前缀或者长度前缀读到了但消息体没读全。排查方法在发送端打印实际发送的字节数在接收端打印实际接收的字节数。对比一下就能发现问题。如果发送端说发了100字节接收端说只收到50字节那就是ReadAsync没读满需要用ReadExactAsync。还有一种可能是缓冲区没刷新。NetworkStream默认是立即发送的但如果你用了BufferedStream包裹就需要手动Flush()。4.3 中文乱码乱码问题99%是编码不一致导致的。一端用UTF-8另一端用GB2312中文必然乱码。解决办法就是两端统一用UTF-8。还有一种情况是Encoding.Default惹的祸。在某些系统上Encoding.Default是GB2312在另一些系统上是UTF-8。所以永远不要用Encoding.Default明确写Encoding.UTF8。4.4 服务端只能连一个客户端如果你用的是同步的AcceptTcpClient()并且没有为每个客户端启动独立任务那确实只能服务一个客户端。解决办法就是改成异步AcceptTcpClientAsync()并且用_ HandleClientAsync(client)为每个客户端启动独立处理任务。4.5 常见问题速查表问题现象可能原因排查方法解决方案连接被拒绝服务端未启动/端口错误/防火墙检查服务端状态和端口启动服务端/改端口/放行防火墙数据收不全ReadAsync未读满打印实际读取字节数使用ReadExactAsync循环读取中文乱码编码不一致检查两端编码设置统一使用UTF-8只能连一个客户端同步Accept阻塞检查是否用了异步改用AcceptTcpClientAsync程序跑久了卡死资源泄漏/死锁检查Close调用和await确保finally中释放资源局域网连不上绑定Loopback/防火墙检查绑定IP改用IPAddress.Any4.6 几个我踩过的坑第一个坑在async方法里用了.Result或.Wait()导致死锁。这个坑我在早期项目中踩过程序直接卡死不动。后来才知道在异步上下文里不能用同步等待要用await。第二个坑忘了处理客户端异常断开。客户端直接拔网线或者强杀进程服务端的ReadAsync会抛异常。如果不捕获整个服务端可能崩溃。所以HandleClientAsync里一定要用try-catch包住。第三个坑消息长度用了int但没做范围检查。恶意客户端发一个长度为int.MaxValue的消息头服务端直接new byte[int.MaxValue]内存瞬间爆掉。加一个上限检查就能解决。第四个坑端口号选了1024以下的。在Linux上1024以下端口需要root权限在Windows上虽然一般没事但某些系统策略也会限制。所以选1024以上的端口最省心。4.7 性能优化的小技巧如果你需要处理大量并发连接可以考虑以下几点第一用ValueTask替代Task减少分配。不过对于一般项目Task够用了不用过早优化。第二复用缓冲区。每次new byte[]都会产生垃圾回收压力可以用ArrayPoolbyte.Shared来租用缓冲区。第三设置NoDelay。TcpClient.NoDelay true可以禁用Nagle算法减少小数据包的延迟。对于实时性要求高的场景很有用。client.NoDelay true; // 禁用Nagle算法降低延迟第四合理设置超时。TcpClient.ReceiveTimeout和SendTimeout可以防止程序无限等待。不过异步方法不支持这两个属性需要用CancellationToken来实现超时。using var cts new CancellationTokenSource(TimeSpan.FromSeconds(5)); await stream.ReadAsync(buffer, 0, count, cts.Token);这些优化技巧不用一开始就全用上先把基本功能跑通遇到性能瓶颈再逐个加。过早优化是万恶之源这句话在网络编程里同样适用。4.8 从简单通信到实际项目的扩展思路这个简单的TCP通信框架可以扩展成很多东西。比如加一个消息协议层定义不同类型的消息登录、聊天、文件传输用消息类型字段来区分。再比如加一个心跳机制定期发送心跳包检测连接是否存活。还可以加断线重连逻辑客户端检测到连接断开后自动重连。我在实际项目中做过一个基于这套框架的指令下发系统服务端维护所有客户端连接需要下发指令时遍历连接发送。客户端收到指令后执行对应操作并返回结果。整个系统核心就是这篇文章讲的内容加上消息协议和心跳检测就够用了。如果你要传输文件思路也类似只是消息体变成文件分片加一个文件ID和分片序号来保证顺序。接收端按序号重组文件。核心还是长度前缀循环读取那一套。最后再分享一个小技巧调试TCP程序的时候用Wireshark或者tcpdump抓包能看到实际发送的字节流对于排查粘包和编码问题特别有用。不过这个工具需要一点学习成本前期用打印日志的方式也够用了。

相关新闻

从图灵测试到深度学习:AI六十年发展脉络与三次热潮解析

从图灵测试到深度学习:AI六十年发展脉络与三次热潮解析

简介:这份文档面向人工智能初学者与需要梳理学科脉络的开发者,系统讲解人工智能的概念界定与六十余年发展历程,帮助读者建立从图灵测试到深度学习时代的整体认知框架。资源为单个docx文档,压缩包约201KB,内容以文字笔记…

2026/10/18 1:38:48 阅读更多 →
C# TCP套接字通信实战:从设计到调试的完整指南

C# TCP套接字通信实战:从设计到调试的完整指南

1. 为什么我至今还在用TCP套接字做通信1.1 一个被低估的经典方案做C#开发十几年,从WinForm时代一路做到现在的跨平台应用,我经手的通信模块少说也有几十个。每次有新同事问我“两个程序之间怎么传数据”,我第一反应永远是TCP套接字。不是因为…

2026/10/18 1:38:48 阅读更多 →
萌新入缸养鱼从入门到懵圈

萌新入缸养鱼从入门到懵圈

文章目录鱼缸基本信息怎么搬鱼缸鱼缸安置打二氧化碳日常打理水草认识鱼萌新加入养鱼大军,初心是整个鱼缸骗自家的猫咪喝水,于是接收了本地鱼友的现成鱼缸。于是开始了养鱼日常。鱼缸基本信息 超白玻璃鱼缸尺寸 50x27x30 1.【造景】 黄梨木 水草若干&…

2026/10/18 1:38:48 阅读更多 →

最新新闻

NPU/TPU硬件加速本质:软硬协同的决策框架与落地实践

NPU/TPU硬件加速本质:软硬协同的决策框架与落地实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 2:13:56 阅读更多 →
20倍光学变焦迷你机芯CM8210AP:小体积如何兼顾远距离高清观测

20倍光学变焦迷你机芯CM8210AP:小体积如何兼顾远距离高清观测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 2:13:56 阅读更多 →
智能穿戴APP开发服务商怎么选?四个维度评估硬件适配能力

智能穿戴APP开发服务商怎么选?四个维度评估硬件适配能力

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 2:13:56 阅读更多 →
MT5风控插件开发实战:架构、性能优化与稳定性部署

MT5风控插件开发实战:架构、性能优化与稳定性部署

1. 风控插件的定位与整体设计思路1.1 为什么风控插件是MT5搭建后的第一优先级MT5服务器部署完成、VPS稳了、交易终端连上了,这只能算完成了1%。真正决定这套平台能不能长期跑下去的关键,是风控插件。我见过太多团队在搭建阶段兴奋得不行,觉得…

2026/10/18 2:13:56 阅读更多 →
RH850 GHS编译链路全解析:从.c到.elf的工程实践

RH850 GHS编译链路全解析:从.c到.elf的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 2:13:56 阅读更多 →
2026年AI接口聚合服务深度横评:从调用稳定性看词元之河与硅基流动的中小团队适配度

2026年AI接口聚合服务深度横评:从调用稳定性看词元之河与硅基流动的中小团队适配度

2026年5月完成的一轮 AI 接口聚合服务实测横评,方法论为 72 小时连续压测、12 类典型调用场景模拟与 37 位一线开发者访谈。行业背景值得关注:据《中国AI基础设施白皮书(2026Q1)》,超过 68% 的中小技术团队在大模型应用…

2026/10/18 2:12:56 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →