前言
我最近有个需求要写Tcp服务端,我发现Tcp服务端的回调函数比较麻烦,简化Tcp的服务,我打算自己封装一个简单的Tcp服务端。
相关文章
C# TCP应用编程三 异步TCP应用编程
C# Tcpclient Tcplistener 服务器接收多个客户端消息通讯
关于C#Socket断开重连问题
前言
我最近有个Tcp服务端的项目,发现TcpListener 服务端官方写起来很麻烦。而且没有回调函数。现在做个简单的服务端封装
设计
代码
public class TcpServeService
{
public string Ip { get; set; }
public int Port { get; set; }
public TcpListener Server { get; set; }
public List<TcpClient> Clients { get; set; }
/// <summary>
/// 客户端添加回调函数,如果要重写通讯逻辑需要覆盖
/// </summary>
public Action<TcpClient> AddClient_CallBack { get; set; }
/// <summary>
/// 客户端自动断开
/// </summary>
public Action<TcpClient> RemoveClient_CallBack { get; set; }
/// <summary>
/// 检测是否断开Tcp服务
/// </summary>
public int CheckConnectTime { get; set; } = 1 * 1000;
public Action<string> ShowMsg { get; set; }
/// <summary>
/// 默认自动回复Tcp服务端
/// </summary>
/// <param name="ip"></param>
/// <param name="port"></param>
public TcpServeService(string ip, int port)
{
Clients = new List<TcpClient>();
ShowMsg = (msg) => Console.WriteLine(msg);
AddClient_CallBack = (client) => AutoSendBack(client);
this.Ip = ip;
this.Port = port;
Server = new TcpListener(IPAddress.Parse(ip), port);
CheckConnectLoop();
}
/// <summary>
/// Tcp添加Client回调
/// </summary>
/// <param name="ar"></param>
private void DoAcceptTcpclient(IAsyncResult ar)
{
// Get the listener that handles the client request.
TcpListener listener = (TcpListener)ar.AsyncState;
// End the operation and display the received data on
// the console.
TcpClient client = listener.EndAcceptTcpClient(ar);
Clients.Add(client);
// Process the connection here. (Add the client to a
// server table, read data, etc.)
ShowMsg($"Tcp客户端连接成功!,当前连接数{Clients.Count},Id[{client.Client.RemoteEndPoint.ToString()}]");
AddClient_CallBack(client);
//开启线程用来不断接收来自客户端的数据
Server.BeginAcceptTcpClient(new AsyncCallback(DoAcceptTcpclient), Server);
}
/// <summary>
/// 移除Tcp客户端
/// </summary>
/// <param name="client"></param>
public void RemoveClient(TcpClient client)
{
NetworkStream stream = client.GetStream();
ShowMsg($"Tcp客户端连接断开!,当前连接数{Clients.Count},Id[{client.Client.RemoteEndPoint.ToString()}]");
stream.Close();
client.Close();
Clients.Remove(client);
}
/// <summary>
/// 启动Tcp服务
/// </summary>
public void Start()
{
Server.Start();
Server.BeginAcceptTcpClient(new AsyncCallback(DoAcceptTcpclient), Server);
ShowMsg($"Tcp服务端启动成功!IP[{Ip}],Port[{Port}]");
}
/// <summary>
/// 监测Tcp 客户端服务是否断开
/// </summary>
/// <returns></returns>
private async Task CheckConnectLoop()
{
while (true)
{
//Console.WriteLine("检测设备连接状况");
try
{
var removeList = new List<TcpClient>();
foreach (var item in Clients)
{
var isConnect = IsConnect(item);
if (!isConnect)
{
//Console.WriteLine($"设备已断开");
if (RemoveClient_CallBack != null)
{
RemoveClient_CallBack(item);
}
removeList.Add(item);
}
}
foreach (var item in removeList)
{
Clients.Remove(item);
}
await Task.Delay(CheckConnectTime);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
/// <summary>
/// 返回数据
/// </summary>
/// <param name="Str"></param>
/// <param name="Bytes"></param>
public record TcpData(string Str, byte[] Bytes);
/// <summary>
/// 同步阻塞读取数据
/// </summary>
/// <param name="client"></param>
/// <returns></returns>
public static TcpData ReadMsg(TcpClient client)
{
NetworkStream networkStream = client.GetStream();
var resBytes = new byte[client.ReceiveBufferSize];
var num = networkStream.Read(resBytes, 0, resBytes.Length);
resBytes = resBytes.Take(num).ToArray();
var resStr = UnicodeEncoding.ASCII.GetString(resBytes);
if (!IsConnect(client))
{
throw new Exception($"{client.Client.RemoteEndPoint?.ToString()}Tcp连接已断开");
}
return new TcpData(resStr, resBytes);
}
/// <summary>
/// 发送Ascll数据
/// </summary>
/// <param name="tcpClient"></param>
/// <param name="msg"></param>
public static void SendMsg(TcpClient tcpClient, string msg)
{
byte[] arrSendMsg = Encoding.UTF8.GetBytes(msg);
SendMsg(tcpClient, arrSendMsg);
}
/// <summary>
/// Tcp客户端连接是否断开
/// </summary>
/// <param name="tcpClient"></param>
/// <returns></returns>
public static bool IsConnect(TcpClient tcpClient)
{
if (tcpClient.Client.Poll(1, SelectMode.SelectRead) && tcpClient.Available == 0)
{
return false;
}
else { return true; }
}
/// <summary>
/// 发送Bytes[]数据
/// </summary>
/// <param name="tcpClient"></param>
/// <param name="msg"></param>
public static void SendMsg(TcpClient tcpClient, byte[] msg)
{
NetworkStream networkStream = tcpClient.GetStream();
networkStream.Write(msg, 0, msg.Length);
}
/// <summary>
/// 发送并返回数据
/// </summary>
/// <param name="tcpClient"></param>
/// <param name="msg"></param>
/// <returns></returns>
public static TcpData SendAndReceive(TcpClient tcpClient, string msg)
{
SendMsg(tcpClient, msg);
return ReadMsg(tcpClient);
}
public static TcpData SendAndReceive(TcpClient tcpClient, byte[] msg)
{
SendMsg(tcpClient, msg);
return ReadMsg(tcpClient);
}
/// <summary>
/// 默认自动回复,异常捕捉
/// </summary>
/// <param name="tcpClient"></param>
/// <param name="timeOut">超时时间</param>
/// <returns></returns>
public async Task AutoSendBack(TcpClient tcpClient, int timeOut = 10 * 1000)
{
//超时时间
tcpClient.ReceiveTimeout = timeOut;
tcpClient.SendTimeout = timeOut;
while (true)
{
try
{
if (!Clients.Contains(tcpClient))
{
throw new Exception("Tcp客户端已被移除!");
}
var receive = ReadMsg(tcpClient);
ShowMsg($"TcpClient[{tcpClient.Client.RemoteEndPoint?.ToString()}]:收到数据{receive.Str}");
SendMsg(tcpClient, receive.Str);
}
catch (Exception ex)
{
RemoveClient(tcpClient);
ShowMsg("发送失败");
ShowMsg(ex.Message);
}
}
}
}
简单使用
//对tcpServeService进行了默认配置,默认自动回复,自动维护Client集合
TcpServeService tcpServeService = new TcpServeService("192.168.100.21", 10003);
//如果想要自定义回复,需要覆盖AddClient_CallBack函数,使用异步任务处理连接
//tcpServeService.AddClient_CallBack = ((client) => {
// Task.Run(() =>
// {
// //你的客户端连接异步任务
// });
//});
//如果想要打印在Winfrom/WPF的界面,覆盖此回调
//tcpServeService.ShowMsg = (msg) =>
//{
// //你的消息打印函数
//};
//tcpServeService.Start();
tcpServeService.Start();
运行结果
心跳包Tcp服务器
在现实使用中,串口设备的有效连接距离一般不超过5m,太长了就考虑转网络了。串口转网络也是十分成熟的技术。为了区别每个串口设备的,需要一个类似于身份证编码的东西。一般不用IP地址,因为Ip地址可能会出现冲突,一般使用的是一个8位的心跳包作为区别
当每次连上设备的时候,都会自动发送一个心跳包。
心跳包客户端实体类
public class TcpHeartCilent
{
/// <summary>
/// 心跳包
/// </summary>
public byte[] HeartNum { get; set; }
public string HeartStr
{
get => BitConverter.ToString(HeartNum).Replace("-", " ");
}
public string ClientIp { get => TcpClient.Client.RemoteEndPoint.ToString(); }
/// <summary>
/// Client对象
/// </summary>
public TcpClient TcpClient { get; set; }
}
心跳包服务端
public class TcpHeartService
{
/// <summary>
/// 服务端
/// </summary>
public TcpServeService TcpServeService { get; set; }
/// <summary>
/// 设备添加回调
/// </summary>
public Action<TcpHeartCilent> AddTcpHeartClient_CallBack { get; set; }
/// <summary>
/// 设备断开回调
/// </summary>
public Action<TcpHeartCilent> RemoveTcpHeartClient_CallBack { get; set; }
public List<TcpHeartCilent> HeartCilents { get; set; } = new List<TcpHeartCilent>();
public TcpHeartService(string ip, int port)
{
TcpServeService = new TcpServeService(ip, port);
TcpServeService.AddClient_CallBack = (client) =>
{
try
{
var res = TcpServeService.ReadMsg(client);
var list = from item in HeartCilents
where item.TcpClient.Equals(client)
select item;
if (list.Count() == 0)
{
Console.WriteLine($"心跳包[{BitConverter.ToString(res.Bytes).Replace("-", " ")}].新设备,按照心跳包添加设备信息");
var newItem = new TcpHeartCilent()
{
TcpClient = client,
HeartNum = res.Bytes
};
HeartCilents.Add(newItem);
if (AddTcpHeartClient_CallBack != null)
{
AddTcpHeartClient_CallBack(newItem);
}
Console.WriteLine("当前设备列表");
for (var i = 0; i < HeartCilents.Count; i++)
{
var item = HeartCilents[i];
Console.WriteLine($"设备心跳包[{item.HeartStr}],设备Ip地址[{item.TcpClient.Client.RemoteEndPoint.ToString()}]");
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
};
TcpServeService.RemoveClient_CallBack = (client) =>
{
var item = HeartCilents.FirstOrDefault(t => t.TcpClient.Equals(client));
if (item != null)
{
Console.WriteLine($"设备[{item.ClientIp}] 已断开!");
HeartCilents.Remove(item);
if (RemoveTcpHeartClient_CallBack != null)
{
RemoveTcpHeartClient_CallBack(item);
}
}
};
}
public void Start()
{
TcpServeService.Start();
}
}
测试结果
static void Main(string[] args)
{
TcpHeartService tcpHeartService = new TcpHeartService("192.168.100.21", 41966);
tcpHeartService.Start();
Console.WriteLine("Hello, World!");
Console.ReadKey();
}
- 可以实现心跳包入库,自动断开
文章来源:https://www.toymoban.com/news/detail-766310.html
必须要确保连接第一个数据包是心跳包,不然会出逻辑问题。文章来源地址https://www.toymoban.com/news/detail-766310.html
到了这里,关于C# Tcplistener,Tcp服务端简易封装的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!