资讯动态

C# TCP通信最简单例程:用TcpListener与TcpClient实现Socket网络编程

发布时间:2026/9/7 7:46:27 来源:尧图企业网站定制
简介面向C#初学者的TCP/IP网络编程入门资源以客户端与服务端的最简通信流程为核心解决新手在Socket编程中无从下手的问题。资源包含ServerFile与ClientFile两个完整项目共55个文件以C#源代码、可执行程序、文本说明、资源文件与项目文件等为主压缩包整体约450KB。已有204人学习下载适合需要快速理解Socket连接、数据收发与基础异常处理的开发者。包内既提供可直接运行的编译版本也有完整C#工程源码便于对照阅读内容涉及TcpListener、TcpClient、IPAddress等核心类型以及System.Net命名空间下的Socket基础用法。目录结构中客户端与服务端清晰分离附带U盘图标、临时文本等辅助文件可帮助读者在Visual Studio中直接打开、调试并观察整个通信过程对于多线程客户端处理、错误处理与数据编码等进阶方向也可基于该示例进一步扩展。 很多刚开始接触C#网络编程的朋友一搜“TCP/IP通信”的资料看到的全是封装了一层又一层的框架、协议、甚至直接上了.NET高层服务组件反而把最底层的通信逻辑给绕晕了。其实在C#里写一个TCP客户端和服务端根本不需要那么复杂。这篇博文就给你一个能直接跑通的“最简单例程”从TcpListener和TcpClient两个核心类入手把通信的基本骨架搭出来。代码量不大但该有的TCP通信要素全都齐了适合做上位机开发、工控数据采集或者只是想搞懂C# Socket通信原理的朋友参考。1. TCP/IP通信的最简模型先搞懂三个关键点在看代码之前建议先把TCP通信的几个核心概念掰扯清楚。很多初学者卡住不是代码写不出来而是对下面的机制理解有偏差导致调试时不知道该查哪里。1.1 客户端和服务端的角色差异TCP通信是典型的“服务端等待连接客户端主动连接”模型。服务端先启动在某个IP地址和端口上监听客户端知道服务端的IP和端口后发起连接请求。连接建立后双方就可以双向收发数据了。这里有个容易误解的点连接建立后服务端和客户端的地位是对等的没有谁可以只发不收。服务端可以主动给客户端发数据客户端也可以主动给服务端发。这个“对等”概念对后面理解读写缓冲区和流对象很重要。1.2 IP地址和端口的作用IP地址定位的是“哪台机器”比如127.0.0.1就是本机回环地址客户端和服务端在同一台电脑上测试时用这个地址。端口定位的是“机器上的哪个程序”。一台电脑上跑了很多程序但端口号是唯一的客户端发来的数据要送给哪个程序靠的就是这个端口。服务端绑定的IP地址是它自己要监听的网卡地址。如果电脑有多个网卡比如工控机上经常有多个网口服务端可以指定只监听其中一个网卡的IP也可以用IPAddress.Any表示监听所有网卡这样客户端无论从哪个网卡访问都能连上。1.3 最简单例程为什么要用TcpListener而不是SocketSystem.Net.Sockets命名空间下有两个层面的API一个是底层的Socket类一个是封装好的TcpClient和TcpListener。TcpClient和TcpListener内部就是对Socket做了一层友好封装简化了连接管理、数据流的获取等操作写起来更直观出错的概率也更低。对于“最简单例程”来说用它们就够了。后面你需要处理粘包拆包、高性能并发服务端时再去深入底层Socket也不迟。2. 服务端例程监听、接受连接、接收数据服务端的核心逻辑就是三步绑定端口开始监听、接受客户端连接、读取客户端发来的数据。下面这个例程把这三步写得很清晰而且加了一些关键注释。using System; using System.Net; using System.Net.Sockets; using System.Text; class TcpServerDemo { static void Main(string[] args) { // 1. 监听本机所有网卡的 8888 端口 IPAddress ip IPAddress.Any; TcpListener listener new TcpListener(ip, 8888); listener.Start(); Console.WriteLine(服务端已启动等待客户端连接...); // 2. 等待客户端连接阻塞 TcpClient client listener.AcceptTcpClient(); Console.WriteLine(客户端已连接 client.Client.RemoteEndPoint.ToString()); // 3. 获取网络流读取客户端数据 NetworkStream stream client.GetStream(); byte[] buffer new byte[1024]; int bytesRead stream.Read(buffer, 0, buffer.Length); string message Encoding.UTF8.GetString(buffer, 0, bytesRead); Console.WriteLine(收到消息 message); // 4. 关闭连接不关闭监听器便于再次连接测试 client.Close(); Console.WriteLine(按任意键退出...); Console.ReadKey(); } }这段代码最关键的地方在于AcceptTcpClient()和stream.Read()这两个阻塞方法。所谓阻塞就是程序执行到这里会停下来等待直到有客户端连接或者有数据到达。这种“同步阻塞”模式在简单例程里是优势逻辑顺序清晰不用处理复杂的异步回调。2.1 为什么服务端要循环Accept上面的例程只能接收一个客户端处理完就结束了。实际项目中服务端通常要能持续接收多个客户端的连接。最简单粗暴的做法就是加一个while(true)循环不断调用AcceptTcpClient()。这是最基础的多客户端处理雏形while (true) { TcpClient client listener.AcceptTcpClient(); Console.WriteLine(客户端已连接 client.Client.RemoteEndPoint.ToString()); NetworkStream stream client.GetStream(); byte[] buffer new byte[1024]; int bytesRead stream.Read(buffer, 0, buffer.Length); string message Encoding.UTF8.GetString(buffer, 0, bytesRead); Console.WriteLine(收到消息 message); client.Close(); }这种循环方式虽然能“多次接收”客户端但依然是“串行”的只有等当前客户端断开才能继续Accept下一个连接。生产环境里肯定不行但在入门例程里先把这种串行逻辑跑通再去理解多线程处理会顺畅很多。2.2 关于Encoding的选用服务端和客户端必须一致代码里用了Encoding.UTF8来解码收到的字节。这里要特别注意服务端用什么编码解码客户端就必须用什么编码发送两者不一致就会出现中文乱码。比如客户端用Encoding.Default中文系统下是GB2312发送服务端用Encoding.UTF8解码中文必乱。这个坑在工控、上位机项目里太常见了建议从一开始就统一用Encoding.UTF8。3. 客户端例程连接服务端、发送数据客户端的逻辑比服务端更简单指定服务端的IP和端口建立连接然后发送数据。using System; using System.Net.Sockets; using System.Text; class TcpClientDemo { static void Main(string[] args) { // 1. 连接服务端本机测试用 127.0.0.1 string serverIp 127.0.0.1; int serverPort 8888; TcpClient client new TcpClient(); client.Connect(serverIp, serverPort); Console.WriteLine(连接服务端成功); // 2. 获取网络流发送数据 NetworkStream stream client.GetStream(); string message Hello, TCP Server!; byte[] data Encoding.UTF8.GetBytes(message); stream.Write(data, 0, data.Length); Console.WriteLine(已发送消息 message); // 3. 关闭连接 client.Close(); Console.WriteLine(按任意键退出...); Console.ReadKey(); } }Connect()方法也是一个阻塞方法。如果服务端没启动、IP地址填错、端口被防火墙拦截Connect()会抛异常。所以实际项目里Connect()通常包在try...catch里捕获异常后提示用户检查服务端是否在线。入门的例程为了看起来清爽可以先不写异常处理但运行出错时你要知道大概率是这三个原因。3.1 为什么用TcpClient而不是直接new SocketTcpClient内部封装了Socket它把连接、断开、获取流这些常见操作简化了。比如获取网络流用底层Socket你得先判断连接状态、处理字节缓冲区代码量会翻倍。虽然TcpClient在.NET Core 2.0之后的某些API也发生了变化比如没有无参构造函数了但连接这个核心逻辑没什么大的改动。3.2 地址填写的两类典型场景本机自测填127.0.0.1跨机测试填服务端电脑的局域网IP比如192.168.1.100。这时候要注意服务端监听的要监听所有网卡也就是IPAddress.Any否则客户端从别的网卡访问会被拒绝。4. 排错思路最小化验证的一种有效方法很多朋友第一次跑通信程序客户端报错或者服务端收不到消息第一反应是“上网搜”。其实最快的方法是用系统自带的telnet命令做最小验证把程序问题跟网络问题剥离开。4.1 用telnet验证服务端是否正常先启动服务端程序然后在命令行里输入telnet 127.0.0.1 8888如果服务端程序正常监听telnet会进入一个黑屏窗口光标闪烁表示TCP连接已经建立。这时候服务端程序的AcceptTcpClient()会返回控制台会打印“客户端已连接”。如果telnet直接提示“连接失败”或者长时间卡住说明问题出在服务端监听环节跟客户端代码无关。Windows系统默认没开telnet客户端的可以在“控制面板 - 启用或关闭Windows功能”里勾选“Telnet客户端”。不想装telnet的话也可以用网络调试助手之类的图形化工具效果一样。4.2 常见报错的含义报错现象可能原因排查方向SocketException: 由于目标计算机积极拒绝服务端没启动或端口不对确认服务端程序在运行且监听端口一致SocketException: 超时防火墙拦截或IP不在同一网段检查Windows防火墙是否放行端口用ping验证网络连通性连接成功但收不到消息编码不一致或数据没发到网络流检查收发双方的Encoding确认发送的是stream.Write而不是Console输出第二次连接失败服务端没有循环Accept参考上文循环结构5. 从例程到实用这4个方向是必然要走的“最简单例程”的意义在于快速跑通TCP通信的流程。如果要在真实项目里用光有上面的代码是不够的下面这些方向是按优先级排列的建议一个一个来。5.1 异常处理和资源释放TcpClient和NetworkStream都实现了IDisposable用using语句包起来确保发生异常时连接资源被正确释放。同时Connect()和Read()之外还要考虑断开重连、服务端主动断开时客户端如何感知的问题。using (TcpClient client new TcpClient()) { // ...连接和收发逻辑 }5.2 多线程支持多客户端把每一个客户端的处理逻辑丢到一个新线程里或者用Task.Run这样服务端可以同时跟多个客户端通信不至于一个客户端卡住就全盘崩溃。这里面会涉及Stream对象的线程安全问题同一个TcpClient的NetworkStream最好不要被多个线程同时读写。5.3 解决粘包和半包TCP是流式协议没有消息边界。客户端连续发送两条消息服务端可能一次Read就全收到了也可能一条消息被拆成两次Read。一般处理方式是在协议里固定消息头长度消息头里带上消息体长度服务端先读够消息头再根据长度读消息体。最简单例程里没有处理这个问题但你要知道这不是“例程写错了”而是TCP本身的设计特性是后续必然要自己解决的一个课题。5.4 异步改造异步方式async/await配合AcceptTcpClientAsync()、ReadAsync()在IO密集型场景下能够用更少的线程服务更多的连接。入门阶段先同步跑通理解了阻塞模型的运行机制之后再转异步很多概念会事半功倍。6. 实际测试的一点经验我在写这篇文章的过程中实际跑了一遍上面的代码。服务端和客户端两个控制台程序在同一个Visual Studio解决方案里右键解决方案属性把“启动项目”设为“多个项目”同时启动服务端和客户端控制台输出顺序非常直观。如果你用的是VS Code或者命令行编译也没问题。先在一个终端跑dotnet run --project TcpServerDemo另外开一个终端跑dotnet run --project TcpClientDemo实测下来发现一个容易误导人的地方客户端Connect()成功之后并不代表服务端一定已经在处理你了。如果服务端卡在一个耗时的操作里没来得及执行AcceptTcpClient()操作系统的TCP协议栈会先把连接完成客户端的Connect()照样返回成功。这种情况在局域网内几乎不会出现但在跨公网或者服务端高负载时会遇到。排查的时候不要理所当然地认为“Connect成功了服务端就一定处理了”。防火墙那条也提一句Windows默认会拦截监听端口的入站连接第一次跑服务端程序时会弹出防火墙提示框记得勾选“允许访问”。不勾的话本机自测没问题跨机测试必超时。这个例程运行顺序是“先服务端后客户端”。如果客户端先启动连接会被拒绝。如果你按照上面的步骤操作差不多十分钟就能看到第一行“收到消息”的日志。后续的扩展方向就看你的具体项目需求了。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价