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 创建项目
- 打开 visual studio,选择「创建新项目」。
- 搜索并选择「windows 窗体应用」模板。
- 项目名称可命名为
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. 运行与测试
- 先运行服务端程序,输入端口号(如
8888),点击「启动监听」。 - 再运行客户端程序,输入服务端 ip(本机测试可填
127.0.0.1)和端口号8888,点击「连接服务端」。 - 在客户端输入消息并点击「发送」,观察服务端日志窗口是否收到消息。
- 在服务端输入消息并点击「发送」,观察客户端日志窗口是否收到消息。
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服务端与客户端的文章就介绍到这了,更多相关c# winform tcp客户端与服务端内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论