C# TCP C/S程序校验客户端输入执行操作失效问题排查
C# TCP客户端/服务端应用问题修复
功能需求
使用 C# 开发客户端/服务端应用(client server app),需实现以下功能:
- 客户端发送消息(client sends a msg)后,服务端将消息原样回传(server sends it back)给客户端
- 服务端校验(check)客户端发送的内容并执行对应逻辑:比如客户端发送
"chrome"时,服务端匹配成功后启动chrome.exe进程。
现存故障
代码运行时存在两个异常:
- 客户端发送
chrome指令时,服务端始终弹出客户端已断开连接提示(client disconnected message),进程启动、消息回传逻辑均未执行 - 尝试将现有控制台程序转换为*Windows窗体应用(windows forms app)*时出现大量编译/运行错误,程序无法正常启动。
现有实现代码
服务端代码(server code)
using System; using System.Net.Sockets; using System.IO; using System.Threading; using System.Net; using System.Text; namespace consoleTcpServer { class Program { class ConnectionHandler { private Socket client; private NetworkStream ns; private StreamReader reader; private StreamWriter writer; private static int connections = 0; //The constructor take the accepted client as argument public ConnectionHandler(Socket client) { this.client = client; } public void HandleConnection() { try { ns = new NetworkStream(client); reader = new StreamReader(ns); writer = new StreamWriter(ns); connections++; Console.WriteLine("New client accepted: {0} active connections", connections); writer.WriteLine("Welcome to my server"); writer.Flush(); string input; while ((input = reader.ReadLine()).Length != 0) { if (input.Contains("chrome")) { System.Diagnostics.Process.Start("chrome.exe", "www.google.com"); } else if (input.Contains("fox")) { System.Diagnostics.Process.Start("firefox.exe", "www.facebook.com"); } Console.WriteLine(input); writer.WriteLine(input); writer.Flush(); } // ns.Close(); // client.Close(); connections--; Console.WriteLine("Client disconnected: {0} active connections", connections); } catch (Exception) { connections--; Console.WriteLine("Client disconnected: {0} active connections", connections); } } } static void Main(string[] args) { Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint localEP = new IPEndPoint(IPAddress.Any, 9050); server.Bind(localEP); server.Listen(10); Console.WriteLine("Waiting for a client"); while (true) { try { Socket client = server.Accept(); ConnectionHandler handler = new ConnectionHandler(client); Thread thread = new Thread(new ThreadStart(handler.HandleConnection)); thread.Start(); } catch (Exception) { Console.WriteLine("Connection failed .."); } //client.Close(); //server.Shutdown(); } } } }
客户端代码(client code)
using System; using System.Net; using System.IO; using System.Net.Sockets; using System.Threading; using System.Text; namespace consoleTcpClient { class Program { static void Main(string[] args) { // Console.WriteLine("Hello World!"); Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9050); client.Connect(remoteEP); NetworkStream stream = new NetworkStream(client); StreamReader reader = new StreamReader(stream); StreamWriter writer = new StreamWriter(stream); String input = reader.ReadLine(); Console.WriteLine(input); String line = null; while (true) { Console.Write("Enter Message for Server <Enter to Stop >: "); line = Console.ReadLine(); //writing for server writer.WriteLine(line); writer.Flush(); if (line.Length != 0) { line = "Echo: " + reader.ReadLine(); Console.WriteLine(line); } } // client.Close(); } } }
故障原因与修复方案
1. 发送chrome指令触发断连提示
核心原因有两个:
- 读取循环存在空引用漏洞:服务端
while ((input = reader.ReadLine()).Length != 0)没有判断ReadLine()返回值,当TCP连接断开时ReadLine()会返回null,直接访问.Length会抛出空引用异常,进入catch块打印断连提示。 - 进程启动逻辑无异常处理:大部分设备不会把Chrome、Firefox的路径加入系统环境变量,直接调用
Process.Start("chrome.exe")会抛出“找不到文件”异常,同样会触发catch块的断连逻辑。
修复方式:
- 修改读取循环判断逻辑,先校验
ReadLine()返回值不为null再判断长度 - 进程启动逻辑单独加try-catch,优先使用浏览器的默认安装路径,找不到路径时给客户端返回错误提示
- 把资源释放逻辑移到finally块,确保连接断开时Socket、网络流能被正确释放,避免连接数统计错误。
修复后的服务端连接处理核心逻辑参考:
public void HandleConnection() { try { ns = new NetworkStream(client); reader = new StreamReader(ns); writer = new StreamWriter(ns); connections++; Console.WriteLine($"New client accepted: {connections} active connections"); writer.WriteLine("Welcome to my server"); writer.Flush(); string input; // 先判断ReadLine返回值是否为null while ((input = reader.ReadLine()) != null && input.Length != 0) { try { if (input.Contains("chrome")) { // 替换为设备上Chrome的实际路径,或通过注册表查找安装路径 System.Diagnostics.Process.Start(@"C:\Program Files\Google\Chrome\Application\chrome.exe", "www.google.com"); } else if (input.Contains("fox")) { System.Diagnostics.Process.Start(@"C:\Program Files\Mozilla Firefox\firefox.exe", "www.facebook.com"); } } catch (Exception ex) { writer.WriteLine($"Execute command failed: {ex.Message}"); writer.Flush(); Console.WriteLine($"Command execute error: {ex.Message}"); continue; } Console.WriteLine(input); writer.WriteLine(input); writer.Flush(); } } catch (Exception ex) { Console.WriteLine($"Connection error: {ex.Message}"); } finally { // 统一释放资源 reader?.Close(); writer?.Close(); ns?.Close(); client?.Close(); connections--; Console.WriteLine($"Client disconnected: {connections} active connections"); } }
2. 控制台程序转WinForm报错
核心原因是控制台程序和WinForm程序的运行模型差异,修复时注意三点:
- 不要把TCP监听、消息接收的阻塞逻辑放到UI线程:原有
Accept()、ReadLine()都是阻塞方法,直接放到UI线程会导致界面假死,要把网络逻辑放到独立后台线程运行。 - 跨线程更新UI必须用控件的
Invoke方法:后台线程不能直接操作窗体上的文本框、标签等控件,否则会抛出跨线程访问异常。 - 项目配置要匹配WinForm要求:给
Main方法添加[STAThread]特性,添加System.Windows.Forms、System.Drawing程序集引用,将项目输出类型改为“Windows应用程序”,不要保留控制台的输入输出逻辑,所有日志、交互都通过窗体控件实现。
内容的提问来源于stack exchange,提问作者ektfaa
相关产品推荐
相关产品推荐

