C# 语言进阶(十)TCP网络
本篇核心知识点TCP 三次握手完整流程、TCP 四次挥手完整流程、阻塞 IO 缺陷、多线程解决阻塞、网络通用 DLL 库封装、服务端 / 客户端 Socket 分层封装、项目复用方案、网络异常崩溃处理、面试高频网络考点一、TCP 三次握手连接建立1. 核心概念TCP 是面向连接可靠协议客户端调用Connect()、服务端调用Accept()底层自动完成三次握手对上层代码透明无需手动发送同步包作用确认双方收发能力防止失效连接请求占用服务端资源。2. 关键标识释义SY同步标志发起连接请求ACK确认标志回复对方收到数据3. 分步完整流程第一次握手客户端→服务端客户端调用 Connect发送 SY 同步报文客户端状态变为SY_SENT向服务端发起连接申请。第二次握手服务端→客户端服务端处于 Listen 监听状态收到 SY 报文回复SYACK报文同步自身连接意愿并确认收到客户端请求服务端进入SY_RCVD状态。第三次握手客户端→服务端客户端收到服务端双标志报文单独回复 ACK 确认报文双方切换至ESTABLISHED稳定传输状态握手完成可正常收发数据。4. 工程对应代码触发点客户端Socket.Connect()内部封装完整三次握手服务端Socket.Accept()阻塞等待握手完成后才返回客户端套接字。5. 拓展为什么需要三次握手防止历史失效连接报文延迟到达服务端建立无效连接占用端口双向验证发送、接收通道正常保障后续数据可靠传输。二、TCP 四次挥手连接断开1. 核心概念TCP 全双工通道收发通道独立关闭时双方需分别告知对方不再发送数据共四次报文交互调用Socket.Close()底层自动触发挥手流程。2. 分步完整流程第一次挥手主动关闭方客户端 / 服务端调用 Close发送 FIN 终止报文进入FIN_WAIT_1状态表示本方不再发送新数据但仍能接收对方剩余消息。第二次挥手被动方回复 ACK被动端收到 FIN回复 ACK 确认报文进入CLOSE_WAIT此时被动端可继续发送未传输完毕数据。第三次挥手被动方发 FIN被动端所有数据发送完毕发送 FIN 报文告知对方无数据再发送。第四次挥手主动方 ACK 确认主动端收到 FIN回复 ACK等待一段时间无报文后彻底释放套接字资源连接完全关闭。3. 代码层面问题直接关闭客户端进程会强制断开跳过完整挥手流程服务端 Receive 读取数据返回 0未做判断会直接抛出异常导致程序崩溃。4. 拓展四次挥手必要性全双工读写分离一方发完数据不代表另一方数据传输完毕必须分两次告知终止发送保证缓冲区残留数据全部接收。三、阻塞 IO 致命缺陷基础单线程网络问题1. 阻塞方法清单Accept()、Receive()均为阻塞方法线程执行时暂停直到有客户端连接 / 收到数据才恢复运行。2. 单线程致命问题服务端主线程卡在Accept()已有客户端发消息无法处理收发逻辑阻塞时无法同时处理新客户端接入客户端 Receive 阻塞无法手动输入新消息发送单个客户端断开会直接导致整个服务端程序崩溃。3. 解决方案多线程分离收发逻辑概念每一条客户端连接分配独立子线程处理收发子线程内部阻塞不会干扰主线程 / 其他客户端主线程仅负责监听新连接。运行逻辑主线程循环 Accept 捕获新客户端 Socket每拿到客户端套接字新建线程专门处理该客户端消息收发子线程内循环 Receive、业务处理阻塞仅影响自身线程。代码片段多线程服务端基础// 单个客户端独立消息处理线程 static void ClientHandle(Socket clientSocket){ byte[] buffer new byte[1024]; while (true){ int len clientSocket.Receive(buffer); if (len 0){ // 客户端断开连接 Console.WriteLine(客户端下线); clientSocket.Close(); break; } string msg System.Text.Encoding.UTF8.GetString(buffer, 0, len); Console.WriteLine(收到消息 msg); // 回发消息 byte[] sendBuf System.Text.Encoding.UTF8.GetBytes(服务端已收到 msg); clientSocket.Send(sendBuf); } } // 服务主线程 static void Main(){ Socket server new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint ep new IPEndPoint(IPAddress.Any, 8080); server.Bind(ep); server.Listen(100); Console.WriteLine(服务端启动等待连接...); while (true){ Socket client server.Accept(); // 主线程仅阻塞监听 // 新开线程处理客户端互不阻塞 new Thread(() ClientHandle(client)).Start(); Console.WriteLine(新客户端接入); } }四、通用网络 DLL 库封装项目复用核心1. 封装目的分离网络底层代码与业务逻辑服务端控制台、Unity 客户端共用同一套网络逻辑避免每个项目重复编写 Socket 连接、收发、编码代码统一异常处理、断线检测减少重复 bug。2. VS 项目区分控制台程序输出 exe 可执行程序独立运行类库项目Class Library输出.dll动态链接库无 Main 入口仅提供可调用类3. 库分层规范命名空间隔离MyNet.ServerTCP 服务端封装类MyNet.ClientTCP 客户端封装类4. 服务端封装类完整结构using System; using System.Net; using System.Net.Sockets; using System.Threading; ​ namespace MyNet.Server{ public class TcpServer{ private Socket _serverSocket; private int _port; // 构造函数绑定指定端口 public TcpServer(int port){ _port port; _serverSocket new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint ep new IPEndPoint(IPAddress.Any, _port); _server.Bind(ep); } // 启动监听主线程阻塞监听新连接开子线程 public void Start(){ _serverSocket.Listen(100); Console.WriteLine($服务端启动端口{_port}); while (true){ Socket client _serverSocket.Accept(); Thread t new Thread(() HandleClient(client)); t.IsBackground true; t.Start(); } } // 单个客户端消息循环处理 private void HandleClient(Socket client){ byte[] buf new byte[1024]; while (true){ int recvLen; try{ recvLen client.Receive(buf); } catch{ break; } if (recvLen 0) break; string msg Encoding.UTF8.GetString(buf, 0, recvLen); Console.WriteLine(客户端消息 msg); // 回包逻辑 byte[] send Encoding.UTF8.GetBytes($服务回复:{msg}); client.Send(send); } client.Close(); Console.WriteLine(客户端断开连接); } } }5. 客户端封装类核心结构namespace MyNet.Client{ public class TcpClient{ private Socket _clientSocket; private string _ip; private int _port; public TcpClient(string ip, int port){ _ip ip; _port port; _clientSocket new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); } // 建立连接底层自动三次握手 public bool Connect(){ try{ IPEndPoint ep new IPEndPoint(IPAddress.Parse(_ip), _port); _clientSocket.Connect(ep); // 新开线程持续接收服务端推送消息 new Thread(RecvLoop).Start(); return true; } catch{ Console.WriteLine(连接失败); return false; } } // 发送字符串消息封装 public void SendMsg(string msg){ byte[] data Encoding.UTF8.GetBytes(msg); _clientSocket.Send(data); } // 后台持续接收消息 private void RecvLoop(){ byte[] buf new byte[1024]; while (true){ int len _clientSocket.Receive(buf); if (len 0) break; string res Encoding.UTF8.GetString(buf, 0, len); Console.WriteLine(服务端返回 res); } } // 主动关闭连接触发四次挥手 public void Close(){ _clientSocket.Close(); } } }6. DLL 使用方式编译类库项目生成MyNet.dll控制台 / Unity 项目右键引用该 dll头部添加using MyNet.Server;直接实例化类调用方法。五、字符串与字节数组转换规范1. 转换核心类System.Text.Encoding.UTF8发送字符串 → byte 数组string text 玩家登录; byte[] sendBuffer Encoding.UTF8.GetBytes(text); socket.Send(sendBuffer);接收byte 数组 → 字符串Receive 返回的len是实际收到字节长度必须截取有效字节避免多余空字符byte[] recvBuf new byte[1024]; int realLen socket.Receive(recvBuf); string res Encoding.UTF8.GetString(recvBuf, 0, realLen);拓展统一编码规范项目全部使用 UTF8避免 GBK/Unicode 混用导致中文乱码。六、网络异常与断线容错处理1. 崩溃根源客户端强制关闭、网络波动时Receive()、Send()会抛出 Socket 异常无 try-catch 捕获直接程序终止。2. 标准容错方案所有 Socket 读写操作包裹try-catch捕获异常后主动关闭套接字、退出循环。容错代码示例private void HandleClient(Socket client){ byte[] buf new byte[1024]; while (true){ int len; try{ len client.Receive(buf); } catch (SocketException ex){ Console.WriteLine(网络异常 ex.Message); break; } if (len 0) break; // 业务处理逻辑 } client.Close(); }七、实战交互流程控制台长连接聊天启动服务端 TcpServer开始监听端口客户端调用Connect()建立 TCP 长连接三次握手完成客户端控制台输入文字调用SendMsg()发送服务端子线程收到消息自动回复客户端后台线程持续打印服务端回复客户端调用Close()主动断开执行四次挥手正常释放资源。八、重点TCP 三次握手每一步报文、作用为什么不能两次握手TCP 四次挥手完整流程为什么不能三次关闭Connect/Accept/Receive阻塞带来的单线程缺陷多线程解决方案类库 DLL 封装优势服务端 / 客户端分层设计Socket 收发字节数组编码转换UTF8 使用规范客户端异常下线程序崩溃解决方案try-catch 线程隔离IPAddress.Any 与 127.0.0.1 区别Any 监听本机所有网卡仅本地测试用回环地址。

相关新闻