摘要
基本UDP收发通信基础库的实现
C#/Udp-1.收发通信基础库的实现 | Loong Eggloongegg.github.io前言
1.系列主题
本系列文章,旨在实现使用Json进行配置的Udp收发器 An Udp Sender & Receiver using json file to configure
2.目标框架(target framework)
- .net framework 4.5 to support using in windows with .net framework >= 4.5
- .net standard 2.0 to support using in linux with .net core >= 2.0
3.项目依赖(Dependency)
- LoongEgg.LoongLog an OpenSource cross-platform & cross-framework log tool, you can get it from nuget or my github
- Newtonsoft.Json
4.项目地址(Source code)
- github
5.发行版获取
- Search in nuget: LoongEgg.UdpCore
UdpReceivedEventArgs.cs
/// <summary>
/// Udp接收事件
/// </summary>
public delegate void UdpReceivedEvent(object sender, UdpReceivedEventArgs args);
/// <summary>
/// Udp接收事件参数
/// </summary>
public class UdpReceivedEventArgs: EventArgs
{
/// <summary>
/// 接收到的缓存信息
/// </summary>
public byte[] Buffer { get; set; }
/// <summary>
/// 默认构造器
/// </summary>
/// <param name="buffer"></param>
public UdpReceivedEventArgs(byte[] buffer)
{
Buffer = buffer;
}
}
UdpReceiver.cs
1. Events
/// <summary>
/// 接收到新消息事件
/// </summary>
public event UdpReceivedEvent MessageRecieved;
2. Fields
/// <summary>
/// 默认配置文件
/// </summary>
private static readonly string DefaultConfigFile = "config.udpreceiver.json";
/// <summary>
/// 使用说明
/// </summary>
private static readonly string Usage =
Environment.NewLine + " Usage: -p port [-g groupaddress] [-t tag]" +
Environment.NewLine + " -p port number listen to" +
Environment.NewLine + " -g group address(224.0.0.0, 239.255.255.255)" +
Environment.NewLine + " -t tag" +
Environment.NewLine + " Info: config options will be saved as config.udpreceiver.json";
3. Properties
/// <summary>
/// 端口号
/// </summary>
public int Port { get; set; }
/// <summary>
/// 组地址
/// </summary>
public string GroupAddress { get; set; }
/// <summary>
/// 接收器标识
/// </summary>
public string Tag { get; set; }
4. Constructors
/// <summary>
/// 默认构造函数
/// </summary>
public UdpReceiver() { }
/// <summary>
/// 创建一个新的接收器
/// </summary>
/// <param name="port">端口号</param>
/// <param name="groupAddress">组地址</param>
public UdpReceiver(int port, string groupAddress = null)
{
GroupAddress = groupAddress;
Port = port;
}
5. Public Methods
/// <summary>
/// 默认控制台程序实现
/// </summary>
/// <param name="useDefaultConfig">default=true, 使用默认的配置文件</param>
public static UdpReceiver DefaultConsole(bool useDefaultConfig = true)
{
string hostName = Dns.GetHostName();
IPAddress[] IPs = Dns.GetHostAddresses(hostName);
Logger.Info($"HostName: {hostName}, Local IP(s):");
if (IPs.Any())
{
IPs.ToList().ForEach(ip =>
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
Logger.Info($" {ip.ToString()}");
});
}
Logger.Info($"Try reading default UDP receiver config:{DefaultConfigFile}");
UdpReceiver receiver = null;
if (useDefaultConfig && File.Exists(DefaultConfigFile))
{
using (StreamReader reader = File.OpenText(DefaultConfigFile))
{
JsonSerializer serializer = JsonSerializer.Create();
receiver = serializer.Deserialize(reader, typeof(UdpReceiver)) as UdpReceiver;
if (receiver != null)
{
Logger.Info(receiver.ToString());
Logger.Info("Reading default config OK.");
}
}
}
else
{
Logger.Warn("Reading default UDP receiver config falied!");
Logger.Info(Usage);
bool unconfig = true;
do
{
Logger.Info("Input udp config options:");
string command = Console.ReadLine();
string[] args = command.Split(' ');
int port;
string group;
string tag;
ParseCommandOptions(args, out port, out group, out tag);
if (port != 0)
{
receiver = new UdpReceiver { Port = port, GroupAddress = group, Tag = tag };
Logger.Info("Udp initial as: " + receiver.ToString() + "?");
Logger.Info($"Enter Y/y to confirm, and save as [{DefaultConfigFile}]. OR any other keys to reinput.");
unconfig = !(Console.ReadLine().ToLower() == "y");
}
else
{
Logger.Warn("option missed: -p [port number] ");
}
} while (unconfig);
string json = JsonConvert.SerializeObject(receiver, Formatting.Indented);
using (StreamWriter writer = File.CreateText(DefaultConfigFile))
{
writer.Write(json);
writer.Flush();
writer.Close();
}
}
return receiver;
}
/// <summary>
/// 接收器工作
/// </summary>
/// <returns></returns>
public async Task ReceiveAsync()
{
using (var client = new UdpClient(Port))
{
if (GroupAddress != null)
{
Logger.Debug($"Join Multicast Group = {GroupAddress}");
client.JoinMulticastGroup(IPAddress.Parse(GroupAddress));
}
Logger.Debug("Start Listening...Sending in [stop] to stop listening");
bool completed;
do
{
UdpReceiveResult result = await client.ReceiveAsync();
byte[] datagram = result.Buffer;
MessageRecieved?.Invoke(this, new UdpReceivedEventArgs(datagram));
string received = Encoding.UTF8.GetString(datagram);
Logger.Info($"Received (from {result.RemoteEndPoint.Address}) < {received}");
completed = (received.ToLower() == "stop");
} while (!completed);
if (GroupAddress != null)
{
client.DropMulticastGroup(IPAddress.Parse(GroupAddress));
}
Logger.Warn("Listening stop command received.");
Logger.Warn("Udp is stopping...");
}
}
/// <summary>
/// 显示Tag、Port、GroupAddress等详细信息
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"{nameof(Port)}={Port}, {nameof(GroupAddress)}={GroupAddress ?? "null"}, {nameof(Tag)}={(Tag ?? "null")}";
}
6. Private Methods
/// <summary>
/// 将数组转义到确切的UDP配置定义
/// </summary>
/// <param name="args">命令参数</param>
/// <param name="port">端口号</param>
/// <param name="group">组地址</param>
/// <param name="tag">识别标签</param>
private static void ParseCommandOptions(string[] args, out int port, out string group, out string tag)
{
UdpHelper.TryParseCommandParam(args, "-p", out port);
UdpHelper.TryParseCommandParam(args, "-g", out group);
UdpHelper.TryParseCommandParam(args, "-t", out tag);
}
UdpSender.cs
1.Fields
/// <summary>
/// IP端口
/// </summary>
private IPEndPoint EndPoint;
/// <summary>
/// Udp端口
/// </summary>
private UdpClient UdpClient;
private const string DefaultConfigFile = "config.udpsender.json";
2.Properties
/// <summary>
/// 端口号
/// </summary>
public int Port { get; set; } = 5566;
/// <summary>
/// 组播
/// </summary>
public bool IsBroadCast { get; set; }
/// <summary>
/// 组地址
/// </summary>
public string GroupAddress { get; set; }
/// <summary>
/// 主机名字
/// </summary>
public string HostName { get; set; }
/// <summary>
/// 小端在前?
/// </summary>
public bool LittleEndian { get; set; }
/// <summary>
/// IpV6模式?
/// </summary>
public bool IsIpV6 { get; set; }
/// <summary>
/// 标识
/// </summary>
public string Tag { get; set; }
3.Constructors
/// <summary>
/// Udp发送器的构造器, 创建后不要忘记调用<see cref="Init()"/>进行初始化
/// </summary>
/// <param name="port">端口号</param>
/// <param name="isBroadcast">组播?</param>
/// <param name="groupAddress">组地址</param>
/// <param name="isIpV6">IpV6模式?</param>
[Obsolete("直接使用方法CreatFromConfig()更香,使用默认构造器,初始化时指定属性")]
public UdpSender(
int port,
bool isBroadcast = true,
string groupAddress = null,
bool isIpV6 = false)
{
Port = port;
IsBroadCast = isBroadcast;
GroupAddress = groupAddress;
IsIpV6 = IsIpV6;
}
/// <summary>
/// 默认构造器, 创建后不要忘记调用<see cref="Init()"/>进行初始化
/// </summary>
public UdpSender() { }
3.Public Methods
/// <summary>
/// 从指定的配置文件创建一个发送器
/// </summary>
/// <param name="path">配置文件路径</param>
/// <returns></returns>
public static UdpSender CreatFromConfig(string path = null)
{
if (path == null)
{
if (File.Exists(DefaultConfigFile))
{
using (StreamReader reader = File.OpenText(DefaultConfigFile))
{
JsonSerializer serializer = JsonSerializer.Create();
if (serializer.Deserialize(reader, typeof(UdpSender)) is UdpSender sender)
{
Logger.Info($"Reading default [{DefaultConfigFile}] config OK.");
return sender;
}
else
{
string message = $"UdpSender creat from default config file [{DefaultConfigFile}] failed";
Logger.Warn(message);
throw new ArgumentException(message);
}
}
}
else
{
Logger.Warn($"Reading default config file [{DefaultConfigFile}] failed.");
Logger.Warn("UdpSender creat with default property");
return new UdpSender();
}
}
else
{
using (StreamReader reader = File.OpenText(path))
{
JsonSerializer serializer = JsonSerializer.Create();
if (serializer.Deserialize(reader, typeof(UdpSender)) is UdpSender sender)
{
Logger.Info("Reading default config OK.");
return sender;
}
else
{
string message = $"UdpSender creat from specifit config file [{path}] failed";
Logger.Warn(message);
throw new ArgumentException(message);
}
}
}
}
/// <summary>
/// 发送信息
/// </summary>
/// <param name="message">待发送的信息</param>
/// <returns></returns>
public async void SendAsync(string message)
{
if (EndPoint == null || UdpClient == null)
throw new InvalidOperationException("Init() before first sending");
try
{
Logger.Info($"Send > {message}");
byte[] datagram = Encoding.UTF8.GetBytes(message);
await UdpClient.SendAsync(datagram, datagram.Length, EndPoint);
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 初始化端口
/// </summary>
public void Init()
{
try
{
EndPoint = GetIPEndPoint(Port, IsBroadCast, HostName, GroupAddress, IsIpV6).Result;
UdpClient = new UdpClient
{
EnableBroadcast = IsBroadCast
};
if (GroupAddress != null && GroupAddress.ToLower() != "null")
{
UdpClient.JoinMulticastGroup(IPAddress.Parse(GroupAddress));
}
Logger.Info("Udp sender initialized");
Logger.Info(this.ToString() );
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 将配置属性转为字符串
/// </summary>
/// <returns></returns>
public override string ToString() =>
Environment.NewLine + $"UdpSender: "
+ Environment.NewLine + $" {nameof(Port)}={Port}"
+ Environment.NewLine + $" {nameof(IsBroadCast)}={IsBroadCast}"
+ Environment.NewLine + $" {nameof(HostName)}={HostName}"
+ Environment.NewLine + $" {nameof(GroupAddress)}={GroupAddress}"
+ Environment.NewLine + $" {nameof(IsIpV6)}={IsIpV6}";
/*--------------------------------- Private Methods -------------------------------*/
/// <summary>
/// 获取指定的IP端口
/// </summary>
/// <param name="port">端口号</param>
/// <param name="isBroadcast">组播?</param>
/// <param name="hostName">主机名称</param>
/// <param name="groupAddress">组地址</param>
/// <param name="isIpV6">IpV6模式?</param>
/// <returns></returns>
private static async Task<IPEndPoint> GetIPEndPoint(
int port,
bool isBroadcast,
string hostName,
string groupAddress,
bool isIpV6)
{
IPEndPoint endpoint;
try
{
if (isBroadcast)
{
endpoint = new IPEndPoint(IPAddress.Broadcast, port);
Logger.Info($"{nameof(isBroadcast)}={isBroadcast}, {nameof(port)}={port} ");
}
else if (hostName != null)
{
IPHostEntry hostEntry = await Dns.GetHostEntryAsync(hostName);
IPAddress address;
if (isIpV6)
{
address = hostEntry.AddressList.Where(
a => a.AddressFamily == AddressFamily.InterNetworkV6
).FirstOrDefault();
}
else
{
address = hostEntry.AddressList.Where(
a => a.AddressFamily == AddressFamily.InterNetwork
).FirstOrDefault();
}
endpoint = new IPEndPoint(address, port);
Logger.Info($"{nameof(hostName)}={hostName}, {nameof(address)}={address}, {nameof(isIpV6)}={isIpV6}");
}
else if (groupAddress != null)
{
endpoint = new IPEndPoint(IPAddress.Parse(groupAddress), port);
Logger.Info($"{nameof(groupAddress)}={groupAddress}, {nameof(port)}={port} ");
}
else
{
throw new InvalidOperationException($"{nameof(hostName)}, {nameof(isBroadcast)}, or {nameof(groupAddress)} must be set");
}
}
catch (Exception ex)
{
throw ex;
}
return endpoint;
}
~