开源 C# 快速开发(八)通讯--Tcp服务器端
文章的目的为了记录使用C# 开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 C# 快速开发(一)基础知识
开源 C# 快速开发(二)基础控件
开源 C# 快速开发(三)复杂控件
开源 C# 快速开发(四)自定义控件--波形图
开源 C# 快速开发(五)自定义控件--仪表盘
开源 C# 快速开发(六)自定义控件--圆环
开源 C# 快速开发(七)通讯--串口
开源 C# 快速开发(八)通讯--Tcp服务器端
开源 C# 快速开发(九)通讯--Tcp客户端
开源 C# 快速开发(十)通讯--http客户端
推荐链接:
开源 C# .net mvc 开发(一)WEB搭建_c#部署web程序-CSDN博客
开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-CSDN博客
开源 C# .net mvc 开发(三)WEB内外网访问-CSDN博客
开源 C# .net mvc 开发(四)工程结构、页面提交以及显示-CSDN博客
开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-CSDN博客
开源 C# .net mvc 开发(六)发送邮件、定时以及CMD编程-CSDN博客
开源 C# .net mvc 开发(七)动态图片、动态表格和json数据生成-CSDN博客
开源 C# .net mvc 开发(八)IIS Express轻量化Web服务器的配置和使用-CSDN博客
开源 C# .net mvc 开发(九)websocket--服务器与客户端的实时通信-CSDN博客
本章节主要内容是:Tcp服务器端程序,基于Windows Forms和串口控件实现。
目录:
1.源码分析
2.所有源码
3.效果演示
一、源码分析
1. 构造函数 TcpServerForm()
功能分析:
初始化窗体组件(通过InitializeComponent())
创建空的客户端连接列表,用于存储所有连接的TCP客户端
初始化服务器状态为停止状态(isServerRunning = false)
public partial class TcpServerForm : Form
{private TcpListener tcpListener;private Thread listenerThread;private List<TcpClient> connectedClients;private bool isServerRunning = false;public TcpServerForm(){InitializeComponent();connectedClients = new List<TcpClient>(); // 初始化客户端列表}
}
2. 启动服务器 btnStart_Click(object sender, EventArgs e)
详细流程:
输入验证:检查IP地址和端口号格式是否正确
启动监听:创建TcpListener实例并调用Start()方法
线程创建:创建后台线程专门处理客户端连接请求
UI更新:禁用启动按钮,启用停止和发送按钮,更新状态标签
日志记录:记录服务器启动成功信息
private void btnStart_Click(object sender, EventArgs e)
{try{// 验证IP地址输入if (!IPAddress.TryParse(txtIP.Text, out IPAddress ipAddress)){MessageBox.Show("请输入有效的IP地址");return;}// 验证端口号输入(1-65535)if (!int.TryParse(txtPort.Text, out int port) || port < 1 || port > 65535){MessageBox.Show("请输入有效的端口号(1-65535)");return;}// 创建并启动TCP监听器tcpListener = new TcpListener(ipAddress, port);tcpListener.Start();isServerRunning = true;// 创建并启动监听线程listenerThread = new Thread(new ThreadStart(ListenForClients));listenerThread.IsBackground = true; // 设置为后台线程(主程序退出时自动终止)listenerThread.Start();// 更新UI控件状态btnStart.Enabled = false;btnStop.Enabled = true;btnSend.Enabled = true;lblStatus.Text = $"服务器已启动 - {ipAddress}:{port}";AddLog($"服务器启动成功,监听 {ipAddress}:{port}");}catch (Exception ex){MessageBox.Show($"启动服务器失败: {ex.Message}");}
}
3. 停止服务器 StopServer()
关键技术点:
lock关键字:防止多线程同时访问connectedClients集合
Invoke方法:确保跨线程UI更新的安全性
异常处理:使用空catch块忽略关闭连接时的异常
private void StopServer()
{try{isServerRunning = false; // 设置停止标志// 停止TCP监听器tcpListener?.Stop();// 线程安全地关闭所有客户端连接lock (connectedClients) // 使用lock确保线程安全{foreach (var client in connectedClients){try{client.Close(); // 关闭客户端连接}catch { } // 忽略关闭过程中的异常}connectedClients.Clear(); // 清空客户端列表}// 使用Invoke确保UI操作在UI线程上执行Invoke(new Action(() =>{btnStart.Enabled = true;btnStop.Enabled = false;btnSend.Enabled = false;lblStatus.Text = "服务器已停止";lstClients.Items.Clear(); // 清空客户端列表显示AddLog("服务器已停止");}));}catch (Exception ex){Invoke(new Action(() => AddLog($"停止服务器时出错: {ex.Message}")));}
}
4. 客户端监听循环 ListenForClients()
重要特性:
阻塞调用:AcceptTcpClient()会阻塞线程直到有客户端连接
一客户一线程:每个客户端连接都有独立的处理线程
实时UI更新:新连接立即显示在界面上
private void ListenForClients()
{while (isServerRunning) // 主循环,检查服务器运行状态{try{// 阻塞等待客户端连接(同步方法)TcpClient client = tcpListener.AcceptTcpClient();// 为每个客户端创建独立的处理线程Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));clientThread.IsBackground = true;clientThread.Start(client); // 将客户端对象传递给线程// 线程安全地添加客户端到列表lock (connectedClients){connectedClients.Add(client);}// 更新UI显示新连接的客户端Invoke(new Action(() =>{string clientInfo = $"{((IPEndPoint)client.Client.RemoteEndPoint).Address}:{((IPEndPoint)client.Client.RemoteEndPoint).Port}";lstClients.Items.Add(clientInfo);AddLog($"客户端连接: {clientInfo}");}));}catch (Exception ex){if (isServerRunning) // 只有在服务器运行时才记录错误{Invoke(new Action(() => AddLog($"接受客户端连接时出错: {ex.Message}")));}break; // 发生异常时退出循环}}
}
5. 客户端通信处理 HandleClientComm(object client)
数据处理流程:
获取网络流:通过GetStream()获取用于读写的数据流
循环读取:持续读取客户端发送的数据
编码转换:将字节数组转换为UTF-8字符串
异常处理:处理网络中断等异常情况
资源清理:在finally块中确保连接正确关闭
private void HandleClientComm(object client)
{TcpClient tcpClient = (TcpClient)client;NetworkStream clientStream = tcpClient.GetStream(); // 获取网络流byte[] buffer = new byte[4096]; // 4KB接收缓冲区int bytesRead;try{while (isServerRunning && tcpClient.Connected){// 阻塞读取客户端数据bytesRead = clientStream.Read(buffer, 0, buffer.Length);if (bytesRead == 0) // 连接已关闭break;// 将字节数据转换为字符串(UTF-8编码)string receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead);// 在UI线程上更新接收到的消息Invoke(new Action(() =>{string clientInfo = $"{((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address}:{((IPEndPoint)tcpClient.Client.RemoteEndPoint).Port}";AddLog($"来自 {clientInfo}: {receivedData}");}));}}catch (Exception ex){if (isServerRunning){Invoke(new Action(() => AddLog($"处理客户端通信时出错: {ex.Message}")));}}finally // 确保资源清理{// 从客户端列表中移除lock (connectedClients){connectedClients.Remove(tcpClient);}// 更新UI显示客户端断开Invoke(new Action(() =>{string clientInfo = $"{((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address}:{((IPEndPoint)tcpClient.Client.RemoteEndPoint).Port}";// 从列表框中移除客户端for (int i = 0; i < lstClients.Items.Count; i++){if (lstClients.Items[i].ToString() == clientInfo){lstClients.Items.RemoveAt(i);break;}}AddLog($"客户端断开连接: {clientInfo}");}));tcpClient.Close(); // 关闭客户端连接}
}
6. 消息发送 btnSend_Click(object sender, EventArgs e)
广播机制:
遍历所有客户端:向列表中的每个客户端发送相同消息
连接状态检查:发送前验证客户端是否仍然连接
自动清理:移除已断开连接的客户端
private void btnSend_Click(object sender, EventArgs e)
{if (string.IsNullOrWhiteSpace(txtSend.Text)){MessageBox.Show("请输入要发送的数据");return;}try{byte[] data = Encoding.UTF8.GetBytes(txtSend.Text); // 字符串转字节数组lock (connectedClients){List<TcpClient> disconnectedClients = new List<TcpClient>();foreach (var client in connectedClients){try{if (client.Connected){NetworkStream stream = client.GetStream();stream.Write(data, 0, data.Length); // 发送数据stream.Flush(); // 立即发送缓冲区数据}else{disconnectedClients.Add(client); // 记录已断开的客户端}}catch{disconnectedClients.Add(client); // 记录发送失败的客户端}}// 清理已断开的客户端连接foreach (var disconnectedClient in disconnectedClients){connectedClients.Remove(disconnectedClient);disconnectedClient.Close();}}AddLog($"发送到所有客户端: {txtSend.Text}");txtSend.Clear(); // 清空发送文本框}catch (Exception ex){MessageBox.Show($"发送数据失败: {ex.Message}");}
}
7. 日志记录 AddLog(string message)
线程安全机制:
InvokeRequired:检测当前是否在非UI线程
Invoke:将调用封送到UI线程执行
递归调用确保最终在UI线程上执行实际操作
private void AddLog(string message)
{if (txtReceive.InvokeRequired) // 检查是否需要在UI线程上执行{txtReceive.Invoke(new Action<string>(AddLog), message); // 递归调用}else{txtReceive.AppendText($"[{DateTime.Now:HH:mm:ss}] {message}\r\n"); // 添加时间戳txtReceive.ScrollToCaret(); // 自动滚动到最新内容}
}
8. 窗体关闭事件 OnFormClosing(FormClosingEventArgs e)
protected override void OnFormClosing(FormClosingEventArgs e)
{StopServer(); // 确保服务器正确停止base.OnFormClosing(e); // 调用基类方法
}
二、所有源码
TcpServerForm .Designer.cs界面代码
using System;
using System.Drawing;
using System.Windows.Forms;namespace TcpServerApp
{partial class TcpServerForm{/*/// <summary>/// Required designer variable./// </summary>private System.ComponentModel.IContainer components = null;/// <summary>/// Clean up any resources being used./// </summary>/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>protected override void Dispose(bool disposing){if (disposing && (components != null)){components.Dispose();}base.Dispose(disposing);}#region Windows Form Designer generated code/// <summary>/// Required method for Designer support - do not modify/// the contents of this method with the code editor./// </summary>private void InitializeComponent(){this.components = new System.ComponentModel.Container();this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;this.ClientSize = new System.Drawing.Size(800, 450);this.Text = "TcpServerForm";}#endregion*/private System.ComponentModel.IContainer components = null;private TextBox txtIP;private TextBox txtPort;private Button btnStart;private Button btnStop;private TextBox txtSend;private Button btnSend;private TextBox txtReceive;private ListBox lstClients;private Label lblStatus;private Label label1;private Label label2;private Label label3;private Label label4;private Label label5;protected override void Dispose(bool disposing){if (disposing && (components != null)){components.Dispose();}base.Dispose(disposing);}private void InitializeComponent(){this.components = new System.ComponentModel.Container();this.txtIP = new TextBox();this.txtPort = new TextBox();this.btnStart = new Button();this.btnStop = new Button();this.txtSend = new TextBox();this.btnSend = new Button();this.txtReceive = new TextBox();this.lstClients = new ListBox();this.lblStatus = new Label();this.label1 = new Label();this.label2 = new Label();this.label3 = new Label();this.label4 = new Label();this.label5 = new Label();// 窗体设置this.Text = "TCP服务器";this.Size = new Size(800, 600);this.StartPosition = FormStartPosition.CenterScreen;// IP标签和文本框this.label1.Location = new Point(20, 20);this.label1.Size = new Size(80, 20);this.label1.Text = "IP地址:";this.txtIP.Location = new Point(100, 20);this.txtIP.Size = new Size(150, 20);this.txtIP.Text = "127.0.0.1";// 端口标签和文本框this.label2.Location = new Point(270, 20);this.label2.Size = new Size(80, 20);this.label2.Text = "端口:";this.txtPort.Location = new Point(320, 20);this.txtPort.Size = new Size(80, 20);this.txtPort.Text = "8080";// 启动按钮this.btnStart.Location = new Point(420, 18);this.btnStart.Size = new Size(80, 25);this.btnStart.Text = "启动服务器";this.btnStart.Click += new EventHandler(this.btnStart_Click);// 停止按钮this.btnStop.Location = new Point(510, 18);this.btnStop.Size = new Size(80, 25);this.btnStop.Text = "停止服务器";this.btnStop.Enabled = false;this.btnStop.Click += new EventHandler(this.btnStop_Click);// 状态标签this.lblStatus.Location = new Point(20, 60);this.lblStatus.Size = new Size(400, 20);this.lblStatus.Text = "服务器未启动";// 客户端列表标签和列表框this.label3.Location = new Point(20, 100);this.label3.Size = new Size(100, 20);this.label3.Text = "已连接客户端:";this.lstClients.Location = new Point(20, 120);this.lstClients.Size = new Size(250, 200);// 接收数据标签和文本框this.label4.Location = new Point(20, 340);this.label4.Size = new Size(100, 20);this.label4.Text = "接收数据:";this.txtReceive.Location = new Point(20, 360);this.txtReceive.Multiline = true;this.txtReceive.ScrollBars = ScrollBars.Vertical;this.txtReceive.Size = new Size(750, 100);this.txtReceive.ReadOnly = true;// 发送数据标签和文本框this.label5.Location = new Point(20, 480);this.label5.Size = new Size(100, 20);this.label5.Text = "发送数据:";this.txtSend.Location = new Point(20, 500);this.txtSend.Multiline = true;this.txtSend.ScrollBars = ScrollBars.Vertical;this.txtSend.Size = new Size(650, 50);// 发送按钮this.btnSend.Location = new Point(680, 500);this.btnSend.Size = new Size(90, 50);this.btnSend.Text = "发送";this.btnSend.Enabled = false;this.btnSend.Click += new EventHandler(this.btnSend_Click);// 添加控件到窗体this.Controls.AddRange(new Control[] {this.txtIP, this.txtPort, this.btnStart, this.btnStop,this.txtSend, this.btnSend, this.txtReceive, this.lstClients,this.lblStatus, this.label1, this.label2, this.label3,this.label4, this.label5});}}
}
TcpServerForm.cs源码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;using System.Net;
using System.Net.Sockets;using System.Threading;namespace TcpServerApp
{public partial class TcpServerForm : Form{private TcpListener tcpListener;private Thread listenerThread;private List<TcpClient> connectedClients;private bool isServerRunning = false;public TcpServerForm(){InitializeComponent();connectedClients = new List<TcpClient>();}private void btnStart_Click(object sender, EventArgs e){try{// 验证输入if (!IPAddress.TryParse(txtIP.Text, out IPAddress ipAddress)){MessageBox.Show("请输入有效的IP地址");return;}if (!int.TryParse(txtPort.Text, out int port) || port < 1 || port > 65535){MessageBox.Show("请输入有效的端口号(1-65535)");return;}// 启动服务器tcpListener = new TcpListener(ipAddress, port);tcpListener.Start();isServerRunning = true;// 启动监听线程listenerThread = new Thread(new ThreadStart(ListenForClients));listenerThread.IsBackground = true;listenerThread.Start();// 更新UIbtnStart.Enabled = false;btnStop.Enabled = true;btnSend.Enabled = true;lblStatus.Text = $"服务器已启动 - {ipAddress}:{port}";AddLog($"服务器启动成功,监听 {ipAddress}:{port}");}catch (Exception ex){MessageBox.Show($"启动服务器失败: {ex.Message}");}}private void btnStop_Click(object sender, EventArgs e){StopServer();}private void StopServer(){try{isServerRunning = false;// 停止监听tcpListener?.Stop();// 断开所有客户端连接lock (connectedClients){foreach (var client in connectedClients){try{client.Close();}catch { }}connectedClients.Clear();}// 更新UIInvoke(new Action(() =>{btnStart.Enabled = true;btnStop.Enabled = false;btnSend.Enabled = false;lblStatus.Text = "服务器已停止";lstClients.Items.Clear();AddLog("服务器已停止");}));}catch (Exception ex){Invoke(new Action(() => AddLog($"停止服务器时出错: {ex.Message}")));}}private void ListenForClients(){while (isServerRunning){try{// 接受客户端连接TcpClient client = tcpListener.AcceptTcpClient();// 为新客户端创建处理线程Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));clientThread.IsBackground = true;clientThread.Start(client);// 添加到客户端列表lock (connectedClients){connectedClients.Add(client);}// 更新UIInvoke(new Action(() =>{string clientInfo = $"{((IPEndPoint)client.Client.RemoteEndPoint).Address}:{((IPEndPoint)client.Client.RemoteEndPoint).Port}";lstClients.Items.Add(clientInfo);AddLog($"客户端连接: {clientInfo}");}));}catch (Exception ex){if (isServerRunning){Invoke(new Action(() => AddLog($"接受客户端连接时出错: {ex.Message}")));}break;}}}private void HandleClientComm(object client){TcpClient tcpClient = (TcpClient)client;NetworkStream clientStream = tcpClient.GetStream();byte[] buffer = new byte[4096];int bytesRead;try{while (isServerRunning && tcpClient.Connected){bytesRead = clientStream.Read(buffer, 0, buffer.Length);if (bytesRead == 0)break;string receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead);// 更新UIInvoke(new Action(() =>{string clientInfo = $"{((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address}:{((IPEndPoint)tcpClient.Client.RemoteEndPoint).Port}";AddLog($"来自 {clientInfo}: {receivedData}");}));}}catch (Exception ex){if (isServerRunning){Invoke(new Action(() => AddLog($"处理客户端通信时出错: {ex.Message}")));}}finally{// 客户端断开连接lock (connectedClients){connectedClients.Remove(tcpClient);}Invoke(new Action(() =>{string clientInfo = $"{((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address}:{((IPEndPoint)tcpClient.Client.RemoteEndPoint).Port}";// 从列表中移除for (int i = 0; i < lstClients.Items.Count; i++){if (lstClients.Items[i].ToString() == clientInfo){lstClients.Items.RemoveAt(i);break;}}AddLog($"客户端断开连接: {clientInfo}");}));tcpClient.Close();}}private void btnSend_Click(object sender, EventArgs e){if (string.IsNullOrWhiteSpace(txtSend.Text)){MessageBox.Show("请输入要发送的数据");return;}try{byte[] data = Encoding.UTF8.GetBytes(txtSend.Text);lock (connectedClients){List<TcpClient> disconnectedClients = new List<TcpClient>();foreach (var client in connectedClients){try{if (client.Connected){NetworkStream stream = client.GetStream();stream.Write(data, 0, data.Length);stream.Flush();}else{disconnectedClients.Add(client);}}catch{disconnectedClients.Add(client);}}// 移除已断开的客户端foreach (var disconnectedClient in disconnectedClients){connectedClients.Remove(disconnectedClient);disconnectedClient.Close();}}AddLog($"发送到所有客户端: {txtSend.Text}");txtSend.Clear();}catch (Exception ex){MessageBox.Show($"发送数据失败: {ex.Message}");}}private void AddLog(string message){if (txtReceive.InvokeRequired){txtReceive.Invoke(new Action<string>(AddLog), message);}else{txtReceive.AppendText($"[{DateTime.Now:HH:mm:ss}] {message}\r\n");txtReceive.ScrollToCaret();}}protected override void OnFormClosing(FormClosingEventArgs e){StopServer();base.OnFormClosing(e);}}
}
三、效果演示
使用网络调试助手进行调试,设置好ip和端口,验证接收数据和发送数据