详情

首页手游攻略 使用C# WinForm实现方式一个TCP服务端与客户端实用指南

使用C# WinForm实现方式一个TCP服务端与客户端实用指南

佚名 2026-10-23 16:50:01

平时做技术实践时,很多问题不是概念不会,而是细节没串起来。拿“采用C# WinForm实现方法一个TCP服务端与客户端”来说,它看着像小点,放到项目里常会牵出环境、配置、兼容性和维护成本。下面按实际采用顺序,把思路、关键写法和容易踩坑的地方讲清楚,便于大家直接对照操作。

1. 引言

在这个场景下,在网络编程里,TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在 C# 桌面应用开发中,基于 WinForm 实现 TCP 客户端与服务端通信是很常用的需求,比如局域网聊天工具、远程控制、数据采集上报等场景。

实际处理时,本文将带领大家从零开始,采用 C# 和 WinForm 分别实现一个 TCP 服务端与客户端,涵盖连接管理、消息收发、异常处理等核心知识点,同时附上完整可运行的示例。

2. 准备工作

2.1 开发环境

  • 操作系统:Windows 10/11
  • 开发工具:Visual Studio 2019 或更高版本(本示例采用 Visual Studio 2022)
  • 框架:.NET Framework 4.7.2 或 .NET 6/8(WinForm 均兼容)

2.2 新建项目

  1. 打开 Visual Studio,选择「新建新项目」。
  2. 搜索并选择「Windows 窗体应用」模板。
  3. 项目名称可命名为 TcpDemo,选择目标框架后点击「新建」。

3. TCP 编程核心类

理解这一步时,在 .NET 里,TCP 编程主要依赖 System.Net.Sockets 命名空间下的以下核心类:

类名说明
TcpListener服务端客户端连接请求
TcpClient客户端连接服务端,或服务端接受连接后得到的客户端对象
NetworkStream网络数据流,用来读写数据
StreamReader / StreamWriter基于文本的流读写辅助类

4. TCP 服务端实现

4.1 界面设计

在 WinForm 窗体上拖入以下控件:

  • 一个 TextBox(端口号输入框,命名为 txtPort)
  • 一个 Button(启动/停止按钮,命名为 btnStart)
  • 一个 TextBox(消息输入框,命名为 txtMsg)
  • 一个 Button(发送按钮,命名为 btnSend)
  • 一个 ListBox 或 RichTextBox(日志显示区域,命名为 lstLog)

4.2 服务端核心代码

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace TcpDemo
{
    public partial class ServerForm : Form
    {
        private TcpListener _listener;
        private TcpClient _client;
        private NetworkStream _stream;
        private Thread _listenThread;
        private Thread _receiveThread;
        private bool _isRunning;

        public ServerForm()
        {
            InitializeComponent();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            if (!_isRunning)
            {
                StartServer();
            }
            else
            {
                StopServer();
            }
        }

        private void StartServer()
        {
            try
            {
                int port = int.Parse(txtPort.Text.Trim());
                _listener = new TcpListener(IPAddress.Any, port);
                _listener.Start();
                _isRunning = true;
                btnStart.Text = "停止";
                AppendLog($"服务端已启动,端口:{port}");

                _listenThread = new Thread(ListenClient);
                _listenThread.IsBackground = true;
                _listenThread.Start();
            }
            catch (Exception ex)
            {
                MessageBox.Show("启动失败:" + ex.Message);
            }
        }

        private void ListenClient()
        {
            while (_isRunning)
            {
                try
                {
                    // 阻塞等待客户端连接
                    _client = _listener.AcceptTcpClient();
                    AppendLog($"客户端已连接:{_client.Client.RemoteEndPoint}");

                    _stream = _client.GetStream();

                    // 启动接收线程
                    _receiveThread = new Thread(ReceiveData);
                    _receiveThread.IsBackground = true;
                    _receiveThread.Start();
                }
                catch (Exception ex)
                {
                    if (_isRunning)
                    {
                        AppendLog("异常:" + ex.Message);
                    }
                }
            }
        }

        private void ReceiveData()
        {
            byte[] buffer = new byte[1024];
            try
            {
                while (_isRunning && _client.Connected)
                {
                    int length = _stream.Read(buffer, 0, buffer.Length);
                    if (length > 0)
                    {
                        string msg = Encoding.UTF8.GetString(buffer, 0, length);
                        AppendLog($"收到客户端消息:{msg}");
                    }
                }
            }
            catch (Exception ex)
            {
                AppendLog("接收数据异常:" + ex.Message);
            }
        }

        private void btnSend_Click(object sender, EventArgs e)
        {
            if (_stream == null || !_client.Connected)
            {
                MessageBox.Show("没有客户端连接");
                return;
            }

            string msg = txtMsg.Text.Trim();
            if (string.IsNullOrEmpty(msg))
            {
                return;
            }

            byte[] data = Encoding.UTF8.GetBytes(msg);
            _stream.Write(data, 0, data.Length);
            AppendLog($"发送消息:{msg}");
            txtMsg.Clear();
        }

        private void StopServer()
        {
            _isRunning = false;
            try
            {
                _stream?.Close();
                _client?.Close();
                _listener?.Stop();
            }
            catch (Exception ex)
            {
                AppendLog("关闭异常:" + ex.Message);
            }

            btnStart.Text = "启动";
            AppendLog("服务端已停止");
        }

        private void AppendLog(string msg)
        {
            if (lstLog.InvokeRequired)
            {
                lstLog.Invoke(new Action<string>(AppendLog), msg);
            }
            else
            {
                lstLog.Items.Add($"[{DateTime.Now:HH:mm:ss}] {msg}");
            }
        }

        private void ServerForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            StopServer();
        }
    }
}

5. TCP 客户端实现

5.1 界面设计

客户端窗体控件布局:

  • 一个 TextBox(服务端 IP 输入框,命名为 txtIP)
  • 一个 TextBox(端口号输入框,命名为 txtPort)
  • 一个 Button(连接/断开按钮,命名为 btnConnect)
  • 一个 TextBox(消息输入框,命名为 txtMsg)
  • 一个 Button(发送按钮,命名为 btnSend)
  • 一个 ListBox(日志显示区域,命名为 lstLog)

5.2 客户端核心代码

using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace TcpDemo
{
    public partial class ClientForm : Form
    {
        private TcpClient _client;
        private NetworkStream _stream;
        private Thread _receiveThread;
        private bool _isConnected;

        public ClientForm()
        {
            InitializeComponent();
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            if (!_isConnected)
            {
                ConnectServer();
            }
            else
            {
                DisconnectServer();
            }
        }

        private void ConnectServer()
        {
            try
            {
                string ip = txtIP.Text.Trim();
                int port = int.Parse(txtPort.Text.Trim());

                _client = new TcpClient();
                _client.Connect(ip, port);
                _stream = _client.GetStream();
                _isConnected = true;

                btnConnect.Text = "断开连接";
                AppendLog($"已连接服务端:{ip}:{port}");

                _receiveThread = new Thread(ReceiveData);
                _receiveThread.IsBackground = true;
                _receiveThread.Start();
            }
            catch (Exception ex)
            {
                MessageBox.Show("连接失败:" + ex.Message);
            }
        }

        private void ReceiveData()
        {
            byte[] buffer = new byte[1024];
            try
            {
                while (_isConnected && _client.Connected)
                {
                    int length = _stream.Read(buffer, 0, buffer.Length);
                    if (length > 0)
                    {
                        string msg = Encoding.UTF8.GetString(buffer, 0, length);
                        AppendLog($"收到服务端消息:{msg}");
                    }
                }
            }
            catch (Exception ex)
            {
                AppendLog("接收数据异常:" + ex.Message);
            }
        }

        private void btnSend_Click(object sender, EventArgs e)
        {
            if (!_isConnected || _stream == null)
            {
                MessageBox.Show("尚未连接服务端");
                return;
            }

            string msg = txtMsg.Text.Trim();
            if (string.IsNullOrEmpty(msg))
            {
                return;
            }

            byte[] data = Encoding.UTF8.GetBytes(msg);
            _stream.Write(data, 0, data.Length);
            AppendLog($"发送消息:{msg}");
            txtMsg.Clear();
        }

        private void DisconnectServer()
        {
            _isConnected = false;
            try
            {
                _stream?.Close();
                _client?.Close();
            }
            catch (Exception ex)
            {
                AppendLog("断开异常:" + ex.Message);
            }

            btnConnect.Text = "连接服务端";
            AppendLog("已断开连接");
        }

        private void AppendLog(string msg)
        {
            if (lstLog.InvokeRequired)
            {
                lstLog.Invoke(new Action<string>(AppendLog), msg);
            }
            else
            {
                lstLog.Items.Add($"[{DateTime.Now:HH:mm:ss}] {msg}");
            }
        }

        private void ClientForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            DisconnectServer();
        }
    }
}

6. 运行与测试

  1. 在这个场景下,先运行服务端程序,输入端口号(如 8888),点击「启动」。
  2. 结合项目来看,再运行客户端程序,输入服务端 IP(本机测试可填 127.0.0.1)和端口号 8888,点击「连接服务端」。
  3. 在客户端输入消息同时点击「发送」,观察服务端日志窗口是否收到消息。
  4. 在服务端输入消息同时点击「发送」,观察客户端日志窗口是否收到消息。

7. 常用问题与注意事项

7.1 跨线程访问控件

理解这一步时,接收数据的线程属于后台线程,不能直接操作 UI 控件,必须借助 Invoke 或 BeginInvoke 封送到 UI 线程执行,示例中的 AppendLog 方法已做处理。

7.2 粘包与拆包问题

结合项目来看,TCP 是字节流协议,多次发送的数据可能被合同时接收(粘包),也可能一次发送的数据被拆成多次接收(拆包)。对于轻松示例,能够约定以特定字符(如 n)作为消息结束符,或自定义消息头声明数据长度。

7.3 心跳机制

在这个场景下,在真实项目中,客户端可能因网络异常而断开,服务端无法及时感知。建议客户端定时发送心跳包,服务端超时未收到心跳则判定连接失效同时清理资源。

7.4 异常断开处理

客户端强制关闭时,服务端 Read 方法会抛出异常或得到 0,应在此处释放资源同时更新连接状态。

8. 总结

落到代码里,本文借助一个完整的 WinForm 示例,演示了 C# 中 TCP 服务端与客户端的基本开发流程。核心要点包括:

  • 服务端采用 TcpListener 端口,借助 AcceptTcpClient 接受连接。
  • 客户端采用 TcpClient 主动连接服务端。
  • 收发数据统一借助 NetworkStream 进行读写。
  • 后台线程负责接收数据,借助 Invoke 实现跨线程更新 UI。

在这个场景下,在此基础上,读者能够进一步扩展多客户端同时发处理、消息分包协议、心跳保活、断线重连等功能,构建更健壮的网络应用。

实际处理时,总的来说,C# WinForm TCP客户端与服务端适合结合实际项目边做边理解。先抓住核心思路,再逐步补上细节和边界处理,最后效果会更稳定,也更容易复用。

点击查看更多
推荐专题
热门阅读