尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
C# TCP/IP最简例程:TcpClient与TcpListener服务端客户端互通指南
简介面向C#初学者的TCP/IP通信例程包内含服务端与客户端两个独立完整模块清晰演示了传输控制协议下如何通过TcpListener、TcpClient和Socket类完成建立连接、发送数据与接收响应的全过程适合刚刚接触网络编程、希望快速跑通首个通信程序的开发者。rar压缩包共55个文件大小仅450KB以C#源文件、可直接运行的exe可执行程序以及解决方案和工程配置文件为主同时包含少量资源文件、调试符号与文本说明既能在Visual Studio中打开并编译也能直接运行体验。目前已有206人学习/下载。整套示例将代码按客户端和服务端分为ClientFile、ServerFile两个独立目录读者可以对照源码理解绑定地址、启动监听、接受连接、连接远程端、发送与接收数据等关键步骤。压缩包内还提供编译好的服务端.exe和客户端.exe方便先运行观察效果再对照代码学习其中还带有简单的界面示例降低了TCP/IP编程的入门门槛是一份轻量但完整的起步资料。1. TCP/IP C# 最简单例程客户端和服务端成对跑通这份资源是一套 TCP/IP 的 C# 最简单例程客户端和服务端成对给出新建两个控制台工程把代码粘贴进去就能通信。做上位机联调的人常遇到这种尴尬设备文档写着 TCP 通信、端口也填了数据却是乱的刚学 C# 的读者用 Socket 写一长串代码连不上最后发现只是监听地址写成了回环地址没改。这套例程就是先把“能收发数据”这条链路最短路径走通再谈协议、并发和性能。服务端用 TcpListener 监听端口客户端用 TcpClient 主动连接双方通过 NetworkStream 读写字节。代码量不大但覆盖了建立连接、发送请求、接收响应、关闭连接四个核心步骤。适合三类人一是 C# 网络编程新手想有个能跑通的最小工程二是做上位机采集的工程师要验证设备 TCP 端口通不通三是做课程设计的学生需要一个客户端与服务端互相通信的骨架。至于异步、粘包、多客户端后面章节会用同一份代码往外扩展。2. 先看选型与原理为什么最简例程用 TcpListener 和 TcpClient2.1 从 Socket 到 TcpClient封装替你做了什么TCP/IP 在 .NET 里的核心类是 System.Net.Sockets.Socket。这个类功能很全但直接用它写一个能跑的 TCP 服务端要处理 Bind、Listen、Accept 三个方法还要指明 AddressFamily.InterNetwork、SocketType.Stream、ProtocolType.Tcp 三个枚举少一个连接就建立不起来。TcpListener 和 TcpClient 是建立在 Socket 之上的简化封装TcpListener 把 Bind 和 Listen 合并进构造与 Start()AcceptTcpClient() 返回一个已连接套接字TcpClient 的 Connect() 方法内部完成地址解析、协议选择和三次握手。使用封装类不意味着失去控制力。TcpClient 暴露了 Client 属性底层 Socket 还在那里需要设置 KeepAlive、LingerState、NoDelay 时仍然可以直接操作。我见过有人为了“更底层”硬用 Socket 写业务代码量翻倍排错时反而要在更多环节里找问题。对一份要给学员和一线工程师复现的例程来说TcpListener/TcpClient 让新手能看懂每一行在干嘛也让熟手能快速替换成自己的协议解析逻辑。稍微展开连接的本质一次 TCP 连接由“本地 IP、本地端口、远端 IP、远端端口”四元组唯一确定。服务端监听端口是固定的 9000但每个已接受的客户端都会由系统分配一个临时端口所以一个监听端口能同时对应多个连接。这也是为什么 Accept 出来的 TcpClient 不能直接复用监听者的原因。2.2 同步阻塞模型联调阶段最好用同步模型下Accept 和 Read 调用都会让当前线程挂起。服务端执行 listener.AcceptTcpClient() 时会停在那里直到一个客户端完成三次握手客户端执行 stream.Read() 时也会停在那里直到收到任何字节或连接断开。代码看起来像是“一条线”走到底实际上每个阻塞点都是在等网络事件。这种模型的优点是可以单步调试断点打在 Accept 前你能确认代码确实执行到了监听断点打在 Read 后你能确认收到了数据。对于排查“服务端有没有起来”“数据有没有到网卡”这类问题同步模型远比回调模型直观。缺点是阻塞期间线程什么都干不了一个客户端占一个线程几千个连接时线程开销和上下文切换会拖垮性能。所以这份最简例程的应用场景是一次验证一条链路或者只服务少量客户端。如果真要上并发我通常先把每个客户端交给一个 Task 或 Thread 处理代码大概是在 while 循环里 new Thread(HandleClient).Start(client)HandleClient 里面就是上面那套 Receive/Write 逻辑再往后才考虑用 async/await 重写避免线程堆积。2.3 三个必懂的参数约定地址、端口和防火墙地址有三种写法要分清。127.0.0.1 只会命中本机回环接口外网和局域网设备永远连不进来0.0.0.0C# 里写作 IPAddress.Any监听所有网卡接口最省事但调试时不好区分流量来自哪块网卡192.168.x.x 这种具体内网 IP 只监听指定网卡适合机房多网卡的机器。我一般本机自测用 127.0.0.1设备联调用 IPAddress.Any 或实际内网 IP。端口选择有个常见误区业务代码里用 9000、8080 这种知名端口容易和内网其他服务撞车。按 IANA 的建议49152 到 65535 是动态/私有端口临时联调用这个区间最稳。1024 以下端口在 Windows 上经常需要管理员权限不是首选。另外客户端连接时的端口号必须和服务端监听端口完全一致一个数字都不能差端口不一致报的往往是“目标计算机积极拒绝”而不是“超时”这个报错可以直接定位问题。防火墙是第三个隐藏变量。Windows 默认会拦截未经允许的入站连接第一次运行服务端时系统可能弹一次防火墙授权框。很多刚入门的同事点了“取消”之后请求被系统悄悄丢掉服务端控制台没有任何异常客户端却一直连接超时。写完服务端代码先别急着怀疑程序先在防火墙入站规则里看一眼有没有当前程序的放行条目。提示局域网联调前先互 ping 一次确认两台机器网络通、防火墙没拦 ICMP再谈 TCP 层的问题不然会混淆排查方向。3. 服务端实现TcpListener 监听、Accept 与字节流收发3.1 控制台工程结构与监听启动新建控制台工程后把文件里的默认模板替换成下面代码。目标框架选 .NET 6 或更高即可也可以选 .NET Framework 4.8代码不需要改using System; using System.Net; using System.Net.Sockets; using System.Text; namespace TcpServerDemo { internal class Program { static void Main(string[] args) { TcpListener listener new TcpListener(IPAddress.Any, 9000); listener.Start(); Console.WriteLine(服务端已启动监听端口 9000 ...); while (true) { TcpClient client listener.AcceptTcpClient(); Console.WriteLine(客户端接入 client.Client.RemoteEndPoint); NetworkStream stream client.GetStream(); byte[] buffer new byte[1024]; int len stream.Read(buffer, 0, buffer.Length); string message Encoding.UTF8.GetString(buffer, 0, len); Console.WriteLine(收到消息 message); byte[] response Encoding.UTF8.GetBytes(服务端应答已收到你的消息); stream.Write(response, 0, response.Length); client.Close(); } } } }我解释一下 listener 这块逻辑。new TcpListener(IPAddress.Any, 9000) 让监听套接字绑定到所有网卡的 9000 端口listener.Start() 开始接受连接请求AcceptTcpClient() 返回的是一个已经完成握手的连接对象它对应一条独立的 TCP 连接拥有自己的收发缓冲区。简单说listener 是看门人client 才是真正干活的人。有一个细节值得注意AcceptTcpClient() 是阻塞调用如果在调试时程序卡在这一行而没有任何日志恰恰说明代码是正常的只是在等客户端进来。新手经常误以为程序死掉了直接结束进程其实这时候应该先去启动客户端。3.2 接收请求并回写应答一个完整的请求-应答周期接下来看收发。服务端先执行 stream.Read读客户端发来的字节。这里用的重载是 Read(byte[] buffer, int offset, int size)返回值是本次实际读到的字节数可能比 buffer 小也可能为 0连接正常关闭还可能抛异常连接被重置。不能拿 buffer.Length 当实际数据长度否则转换字符串时会带上多余的空字节。我习惯先写“服务端先 Read 后 Write”再对应客户端“先 Write 后 Read”两侧顺序一配对就是一个完整的请求-应答周期。文档这类同步通信模式下大多数设备也是这个套路上位机发请求帧设备回响应帧谁先等、谁先发必须按协议走。回写应答后用 client.Close() 关闭连接。这里有个取舍关闭连接是最简单的让对端感知通信结束的方式但不是所有协议都允许通信完就断开。如果要保持长连接循环读写就不要在单次应答后 Close而是把 Read/Write 放进 while 循环。这一点与设备是一次连接多次交互还是一问一答强相关联调前先看设备手册。3.3 服务端参数速查与调整方向参数示例值说明监听地址IPAddress.Any监听所有网卡回环自测用 127.0.0.1监听端口9000与客户端一致生产建议 49152-65535缓冲区1024 字节单条报文的最大预期长度编码UTF-8收发必须一致ASCII/GB2312 按设备来关闭方式client.Close()通知对端连接结束释放句柄buffer 大小是调整时最容易忽视的参数。如果设备单帧报文超过 1024 字节Read 只能读回前 1024 字节后面数据会留在内核缓冲区等你下次读如果接收方 Read 时机不对还会出现“半包”。改大 buffer 不是万能药最稳的方案是先用足够大的缓冲循环读完再按协议帧的结束符或长度字段切包。这个在第 6 章会继续讲。多客户端扩展方面最简做法是 Accept 到 client 后丢到后台线程每个连接一个线程去循环读写互不阻塞。要注意关闭逻辑客户端断开后 Read 返回 0 或抛异常一定要在 finally 里 Close否则句柄泄漏。更优雅的方案是 async/await让线程不被连接数拖垮但那是进阶内容不在这份最简例程里展开。4. 客户端实现TcpClient 连接、发送与超时处理4.1 三步走连接、发送、接收客户端代码更短核心就三个动作。先连接再发请求最后等响应using System; using System.Net.Sockets; using System.Text; namespace TcpClientDemo { internal class Program { static void Main(string[] args) { TcpClient client new TcpClient(); client.Connect(127.0.0.1, 9000); Console.WriteLine($已连接服务端 {client.Client.RemoteEndPoint}); NetworkStream stream client.GetStream(); byte[] data Encoding.UTF8.GetBytes(客户端消息你好服务端); stream.Write(data, 0, data.Length); byte[] buffer new byte[1024]; int len stream.Read(buffer, 0, buffer.Length); string response Encoding.UTF8.GetString(buffer, 0, len); Console.WriteLine(服务端响应 response); client.Close(); } } }Connect() 里传的是字符串地址C# 内部会做 DNS 解析localhost、127.0.0.1、192.168.x.x 都可以写。连接成功后 GetStream() 拿到网络流Write 把字节发出去Read 阻塞等应答最后 Close。这里要再次强调Read 的返回值才是有效数据长度转字符串时用 buffer 前 len 个字节而不是整个 buffer。这个例子里客户端先写后读和服务端先读后写的顺序正好配对。如果两边都先读连接建立后谁都不发数据两个进程都会卡在 Read 上看起来像死锁。这是 TCP 联调里最常见的低级错误遇到时先检查代码执行顺序。4.2 给客户端加超时不加就是黑匣子TcpClient 默认的 ReceiveTimeout 是 0表示无限等待。一旦服务端出了问题客户端 Read 会一直挂住界面上没有日志、没有异常程序就像进了黑匣子。要避免这种状况连接建立后先设置两个超时client.ReceiveTimeout 3000; // 3 秒收不到数据就抛异常 client.SendTimeout 3000; // 3 秒发不出去就抛异常ReceiveTimeout 作用于 ReadSendTimeout 作用于 Write单位是毫秒。设了超时后Read 到期会抛 IOException需要包一层 try/catch 做重连或报错提示。注意这个属性不影响 Connect()连接超时要用 client.ConnectAsync() 配合 Task.WhenAny 实现或者使用带超时参数的 Connect 重载否则连一个不存在的 IP 时可能干等几十秒才返回。4.3 上位机场景很多设备才是服务端聊一个实际场景做上位机的人往往第一反应是写服务端因为觉得“我的软件要接收数据”。但在工控和数据采集场合大多数设备才是服务端。比如 Modbus TCP 设备默认监听 502 端口某些传感器、拧紧枪、扭矩仪会监听厂商自定义端口上位机反而是主动连上去的客户端。这种情况下例程的客户端代码稍加改动就能用连接成功后按设备协议周期发送请求帧再接收响应帧解析。很多设备支持长连接所以不用每次请求都新建 TcpClient建立一次连接后循环收发就行。需要留意的是设备通常只允许有限个客户端连接调试时别同时开多个测试工具不然设备会拒绝新连接。我调试扭矩仪时踩过这个坑现象是连接偶尔成功偶尔失败最后发现是之前的测试客户端没关干净。5. 常见问题与排查五条踩坑记录与排查工具组合5.1 服务端一启动就报“只允许使用一次”端口被占用现象服务端执行 listener.Start() 时直接抛 SocketException提示“通常每个套接字地址协议/网络地址/端口只允许使用一次”。原因端口已经被占用。可能是上一次运行的服务端进程没退出也可能是别的程序恰好用了同一端口。调试时按 CtrlC 结束控制台程序不代表进程立刻清理干净有时候残留进程还会占着端口。解决先查占用进程再决定杀进程还是换端口。命令行执行 netstat -ano | findstr 9000看最后一列 PID然后去任务管理器结束对应进程不想杀进程就直接改端口比如把 9000 换成 52001客户端同步改。5.2 本机回环能连、局域网连不上防火墙与监听地址现象服务端在本机跑着用 127.0.0.1 连一切正常换成局域网 IP 就连不上客户端一直超时或拒绝。原因两类。一是服务端监听地址只写了 127.0.0.1没监听外网网卡二是监听用的是 IPAddress.Any但 Windows 防火墙拦截了入站连接。前一类是代码问题后一类是系统策略问题。解决服务端改成 IPAddress.Any 再重启如果还不行检查防火墙入站规则里有没有当前程序的允许规则。很多情况下控制台程序第一次监听时弹出的防火墙对话框被点了取消之后不会再弹第二次需要手动去“Windows Defender 防火墙”添加入站规则放行 TCP 端口。5.3 Read 一直接收不到数据程序像进了黑匣子现象连接建立成功日志也打印了“已连接”但 Read 一直不返回程序卡住没有超时也没有异常。原因最常见的有两种。一是双方都先 Read等对方先发数据形成互相等待二是对方的数据根本没按预期发出比如服务端写的是响应客户端却在等请求结果两边报文没对上。另一个隐蔽原因是半包数据到了内核缓冲区但 Read 的条件不满足。解决先确认日志里“已连接”出现再确认对端调用过 Write最后在阻塞的 Read 之前打一条日志缩小范围。调试阶段给 Read 设置超时超时后打印缓冲区状态和连接状态能省大量时间。如果怀疑半包用进程抓包工具看数据是否真的到了网卡。5.4 中文变成问号乱码编码不一致现象用网口调试助手发给服务端中文服务端打印出来全是问号或者两个 C# 程序之间收发中文显示乱码。原因客户端编码和服务端编码不一致。有人用 Encoding.UTF8 发送有人用 Encoding.DefaultWindows 下是 GB2312/GBK还有人用 ASCIIASCII 根本表示不了中文转出来必然是问号。解决统一编码。C# 程序之间通信直接用 UTF-8 最稳因为 .NET 字符串内部就是 UTF-16转 UTF-8 字节不会丢信息。如果对接的是老旧设备或仪器协议里写了 ASCII 或 GB2312就按协议来但客户端和服务端必须完全一致。编码问题不容易从肉眼看出我一般会在报文日志里同时打印十六进制字节串一目了然。5.5 服务端重启太快报“地址已在使用”TIME_WAIT现象服务端关闭后立刻重新启动偶尔报“只允许使用一次”的错误但等一两分钟再启动又正常了。原因主动关闭连接的一方会进入 TIME_WAIT 状态端口默认要等 2 到 4 分钟才能复用。如果服务端主动 Close而客户端没有先关闭服务端端口会短暂被系统占用。解决联调时最快是等一会再启动也可以不主动做服务端 Close让客户端先断开非要立即重启可以在 Socket 上设置 ReuseAddress 选项代码里通过 listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true) 设置但注意这可能让旧连接的数据落到新连接上生产环境慎用。5.6 telnet 和 netstat先看链路断在哪一环排查 TCP 问题我习惯先看链路状态再查代码。telnet 能测端口通不通netstat 能看连接状态。客户端机器上执行 telnet 192.168.1.100 9000如果端口开着命令行会变成空白或提示连接成功如果立即退出并报“无法打开到主机的连接”说明端口不通或防火墙拦截。服务端机器上执行 netstat -ano | findstr 9000能看到 LISTENING 和 ESTABLISHED 两种状态前者说明监听正常后者说明有客户端连了进来。这两条命令一分钟内就能定位“是防火墙问题、服务端没启动、还是连接根本没建立”比看代码猜效率高得多。6. 进阶验证给例程加上长度前缀与心跳检测6.1 TCP 是流不是消息先解决粘包半包最简例程里一次 Read 对应一次发送数据量小、频率低时能跑通但高频连续发送时就会出问题。TCP 不保证消息边界发送端连续 Write 两次接收端可能一次 Read 就把两段数据都读回来这叫粘包反过来一条消息拆成两次 Read 才读完这叫半包。网上把这个当玄学其实本质就是读写缓冲区和网络分片的不同步。最简单的方案是给每条消息加长度前缀。发送端先写一个 4 字节的 Int32 表示包体长度再写包体接收端先凑够 4 字节解析长度再按长度循环读包体。这样无论底层怎么粘包拆包应用层都能还原出完整消息。6.2 用 4 字节长度前缀做分包接收端的核心逻辑可以写成这样// 先完整读出 4 字节长度头 byte[] lenBuf new byte[4]; int read 0; while (read 4) { int n stream.Read(lenBuf, read, 4 - read); if (n 0) return; // 连接已断开 read n; } int bodyLen BitConverter.ToInt32(lenBuf, 0); // 再按长度读包体 byte[] body new byte[bodyLen]; read 0; while (read bodyLen) { int n stream.Read(body, read, bodyLen - read); if (n 0) return; read n; }这里两个 while 循环都是“凑够指定长度才继续”能同时消化粘包和半包。发送端对应写先 BitConverter.GetBytes(body.Length) 写进去再写 body。要注意大小端BitConverter 在 Windows 上是小端如果对端设备协议用大端发送前要把字节数组 Reverse 一下这是设备联调里最容易翻车的字节序问题。从那次被一个扭矩采集项目坑过之后我每次给客户发 TCP 例程都会强制自己把客户端和服务端各跑一遍再用 netstat 确认端口状态最后用循环发包压一遍粘包场景才交付。这套习惯帮我挡掉了不少售后问题。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

插件加载与激活失败:IAR、web boot与MusicFree排查指南

插件加载与激活失败:IAR、web boot与MusicFree排查指南

如果你最近在搜索引擎里只敲了 plugins 这一个词,大概率正面对下面三个场景之一:刚装好的嵌入式开发环境里多了一个 plugins 目录,不知道它到底是干嘛的;某个 Web 类应用启动时刷出一条以 "failed to load plugins web boot:…

📅 2026/10/4 23:08:33
Chrome DevTools MCP 实战完整教程:把 MCP 配置改到 TaoToken 的调试链路

Chrome DevTools MCP 实战完整教程:把 MCP 配置改到 TaoToken 的调试链路

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

📅 2026/10/4 23:08:33
openrig 配置编排指南:Claude Code 与 Codex 多模型环境装配实践

openrig 配置编排指南:Claude Code 与 Codex 多模型环境装配实践

1. 从零认识 openrig:它到底解决什么问题第一次看到 openrig 这个名字,很多人会以为是某个硬件支架项目,毕竟 rig 在英文里有“装配、支架”的意思。但结合 Claude Code、Codex、YAML、Node.js 这一串关键词,答案就清晰了&#xf…

📅 2026/10/4 23:03:32
MORE NEWS

更多资讯

📰

原创性如何?8款AI论文工具排行榜,毕业护航利器!

论文选题无从下手,文献综述抓耳挠腮,格式排版反复修改? 别担心!AI论文工具正成为学术写作的新帮手。本文将从内容原创性、文献整合能力、格式规范性和查重通过率四大维度,深度测评8款热门AI论文生成工具,助…

📰

自用布林布顶布底支撑线阻力年季竖线上市均线月线日线见画线

{BOLL20} MID:MA(C,20); VAR11:POW((C-MID),2); VAR12:MA(VAR11,20); VAR13:SQRT(VAR12); UPPER:MID2*VAR13; LOWER:MID-2*VAR13; BOLL:REF(MID,1),COLORMAGENTA; UB:REF(UPPER,1),COLOR00FFFF; LB:REF(LOWER,1),COLORFF00FF; DRAWTEXT(COUNT(L>UB,3)>3,H*1.01,布顶),COL…

📰

ANet通信管理机对接OneNET物联网平台:MQTT协议实现设备上云与远程控制

1. 打通设备上云通路:ANet 通信管理机对接 OneNET 物联网平台详解 1.1 为什么要在 ANet 和 OneNET 之间搭一条数据通路 工业现场的设备种类多、协议杂,PLC、仪表、传感器各说各话,想把它们的数据统一送到云端,中间需要一个“翻译…

📰

通达信底部吸筹

Var1:(CLOSELOWHIGH)/3; Var2:SUM((Var1-REF(LOW,1)-(HIGH-Var1))*VOL/100000/(HIGH-LOW),0); Var3:EMA(Var2,1); Var4:Var3; Var5:MA(Var3,12); Var6:MA(Var3,26); Var7:(Var4-Var5)*60; 主力拉升: IF(Var7>0.05,Var7,0); Var8:(Var5-Var6)*30; 均线: IF(Var8>0.05,Var8…

📰

AutoJsPro自建服务器:脚本分发与授权校验实战

简介:这份资源面向希望为 AutoJsPro 搭建私有服务器、实现远程控制等高级自动化功能的 Android 脚本开发者,尤其适合具备一定 Node.js 与网络基础的中级用户。压缩包共 9 个文件,约 344.42MB,涵盖 apk 安装包、mp4 视频教程、txt …

📰

译文排版为什么不会破:RetainPDF 字体缩放与文本密度估算算法完整指南

译文排版为什么不会破:RetainPDF 字体缩放与文本密度估算算法完整指南 【免费下载链接】retain-pdf 在保留版面、公式与结构的前提下进行 PDF 翻译,适用于科研与技术文档 项目地址: https://gitcode.com/gh_mirrors/re/retain-pdf 一、PDF 翻译最…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬