文章的目的为了记录使用C# 开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
推荐链接:
开源 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.效果演示
一、源码分析
- 构造函数 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>(); // 初始化客户端列表
}
}
- 启动服务器 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}");
}
}
- 停止服务器 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}")));
}
}
- 客户端监听循环 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; // 发生异常时退出循环
}
}
}
- 客户端通信处理 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(); // 关闭客户端连接
}
}
- 消息发送 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}");
}
}
- 日志记录 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(); // 自动滚动到最新内容
}
}
-
窗体关闭事件 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();
// 更新UI
btnStart.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();
}
// 更新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}")));
}
}
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;
}
}
}
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);
// 更新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);
}
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和端口,验证接收数据和发送数据
