基于superSocket——AForge的UDP传输摄像案例——服务端
作者:互联网
服务端
业务逻辑:
这里打个比方,有个陌生人(摄像客户端)一直在敲你家的门,但是你没开,等到你爸(Web客户端)给你一个命令开门,你就把门打开,让陌生人进来,并进行核对信息(信息匹配),原来是隔壁老王,给你带了好吃的(UDP数据),你于是就把吃的留下来了。
准备操作:
用superSocket接受客户端发来的UDP数据,superSocket开源框架网址:https://github.com/kerryjiang/SuperSocket。
在NuGet中添加:SuperSocket,SuperSocket.Engine,SuperSocket.ProtoBase
用FFMPEG、VFW进行录入视频。
①.新建UdpTestSession继承AppSession,用于建立会话;
using SuperSocket.SocketBase;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SuperSocket.SocketBase.Protocol;
namespace Receive.udp
{
public class UdpTestSession : AppSession<UdpTestSession, MyUdpRequestInfo>
{
protected override void OnSessionClosed(CloseReason reason)
{
base.OnSessionClosed(reason);
}
protected override void OnSessionStarted()
{
Console.WriteLine("start Session");
base.OnSessionStarted();
}
protected override void HandleUnknownRequest(MyUdpRequestInfo requestInfo)
{
Console.WriteLine("unKnownRequest");
base.HandleUnknownRequest(requestInfo);
}
}
}
②.新建MyUdpRequestInfo,用于封装会话信息与传输的数据信息。
using Receive.udp;
using SuperSocket.ProtoBase;
using SuperSocket.SocketBase.Protocol;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Receive.udp
{
public class MyUdpRequestInfo : UdpRequestInfo, IPackageInfo
{
public MyUdpRequestInfo(string key, string sessionID)
: base(key, sessionID)
{
}
public byte[] Body { get; set; }
}
}
③.新建MyReceiveFilter,用于处理(过滤)传输的数据,封装成MyUdpRequestInfo。
其中这步中,我将客户端分为web客户端与摄像设备客户端,web 发送的数据类型 密钥+设备号+录像时间+结束标志,录像设备发送的数据 密钥+设备号+照片+结束标志。其中密钥与设备号可以设置固定长度,不足补休止符。(见下辅助类)
using System.Linq;
namespace Receive.udp
{
public class MyReceiveFilter : SuperSocket.SocketBase.Protocol.IReceiveFilter<MyUdpRequestInfo>
{
public int LeftBufferSize
{
get { return 0; }
}
public SuperSocket.SocketBase.Protocol.IReceiveFilter<MyUdpRequestInfo> NextReceiveFilter
{
get { return this; }
}
public SuperSocket.SocketBase.Protocol.FilterState State
{
get; private set;
}
/// <summary>
/// 过滤器接受信息 解析byte[]
/// </summary>
/// <param name="readBuffer"></param>
/// <param name="offset"></param>
/// <param name="length"></param>
/// <param name="toBeCopied"></param>
/// <param name="rest"></param>
/// <returns></returns>
public MyUdpRequestInfo Filter(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest)
{
var endFlag = HandleUdpUtils.getEndFlagStr(readBuffer, length);
if (!endFlag.Equals(HandleUdpUtils.IMG_END_FLAG)||!endFlag.Equals(HandleUdpUtils.WEB_END_FLAG)) //判断结束标志
{
int endFlagSize = 2;
string privateKey = HandleUdpUtils.getPrivateKey(readBuffer);
string sesssionId = HandleUdpUtils.getMachineCode(readBuffer); //machineCode 当成sessionId
rest = 0;
byte[] body = readBuffer.Skip(HandleUdpUtils.MACHINE_CODE_LENGTH).Take(length - HandleUdpUtils.MACHINE_CODE_LENGTH - endFlagSize).ToArray();
return new MyUdpRequestInfo(privateKey, sesssionId) { Body = body };
}else
{
rest = 0;
return null;
}
}
public void Reset()
{
}
}
}
④.新建UdpAppServer
集中处理传递的数据,根据不同的客户端请求,进行不一样的操作。web设备的请求则开启摄像,摄像设备则进行视频的录入。
using SuperSocket.SocketBase;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SuperSocket.SocketBase.Config;
using SuperSocket.SocketBase.Protocol;
using SuperSocket.ProtoBase;
using SuperSocket.SocketBase.Logging;
using System.Collections.Concurrent;
using log4net;
using System.IO;
using System.Drawing;
namespace Receive.udp
{
class UdpAppServer : AppServer<UdpTestSession, MyUdpRequestInfo>
{
public static readonly log4net.ILog LOG = log4net.LogManager.GetLogger("VideoSocket");
public static readonly string PATH = "D:\\ReadVersionD\\video";
public static Dictionary<string, string> PRIVATE_KEY = new Dictionary<string, string>(); //deviceCode->key
public static readonly int MIN_IMG_SIZE = 100;
public UdpAppServer()
: base(new DefaultReceiveFilterFactory<MyReceiveFilter, MyUdpRequestInfo>())
{
}
public static ConcurrentDictionary<string, VideoPackageModel> FILE_WRITERS = new ConcurrentDictionary<string, VideoPackageModel>(); //读取文件
protected override void OnSystemMessageReceived(string messageType, object messageData)
{
Console.WriteLine(messageType);
base.OnSystemMessageReceived(messageType, messageData);
}
/// <summary>
/// 过滤器后 进入该方法
/// </summary>
/// <param name="session"></param>
/// <param name="requestInfo"></param>
protected override void ExecuteCommand(UdpTestSession session, MyUdpRequestInfo requestInfo)
{
if (requestInfo.Key.Equals(HandleUdpUtils.WEB_SESSION_KEY))
{
handleWebUdpSocket(requestInfo, session);
return;
}
string privateKey = "";
if (PRIVATE_KEY.TryGetValue(requestInfo.SessionID, out privateKey) && privateKey.Equals(requestInfo.Key))
{
handleDeviceInfo(requestInfo);
session.Send("receive success");
}
else
{
LOG.Error(requestInfo.SessionID + "|不存在该设备");
}
}
#region WEB
/// <summary>
/// 发送socket 到web客户端
/// </summary>
/// <param name="requestInfo"></param>
/// <param name="session"></param>
private void handleWebUdpSocket(MyUdpRequestInfo requestInfo, UdpTestSession session)
{
switch (handleWebCommand(requestInfo))
{
case 1:
session.Send("addOK");
break;
case 0:
session.Send("dataError");
break;
case -1:
session.Send("alreadyExit");
break;
}
}
/// <summary>
/// body的组成 : 5|DateType
/// </summary>
/// <param name="requestInfo"></param>
private int handleWebCommand(MyUdpRequestInfo requestInfo)
{
var startTime = DateTime.Now;
DateTime endTime;
int seconds = HandleUdpUtils.getLasingTime(requestInfo.Body,out startTime,out endTime);
if (seconds == 0)
{
LOG.Error(requestInfo.SessionID + "|获取录像持续时间失败!");
return 0;
}
string privateKey = "";
PRIVATE_KEY.TryGetValue(requestInfo.SessionID,out privateKey);
if (!addVideoPackageModel(requestInfo.SessionID,privateKey,startTime,endTime,seconds))
{
Console.WriteLine(requestInfo.SessionID + "|添加失败!");
LOG.Error(requestInfo.SessionID + "|添加失败!");//TODO 存在更新的操作
return -1;
}
else {
Console.WriteLine(requestInfo.SessionID + "|添加成功!");
LOG.Info(requestInfo.SessionID + "|添加成功!");
return 1;
}
}
/// <summary>
/// 添加VideoPackageModel
/// </summary>
/// <param name="sessionId"></param>
/// <param name="model"></param>
/// <returns></returns>
private bool addVideoPackageModel(string sessionId, string privateKey,DateTime startTime,DateTime endTime ,int seconds)
{
VideoPackageModel oldModel;
if(FILE_WRITERS.TryGetValue(sessionId,out oldModel))
{
if (oldModel.Disposed) //可能没删除
{
FILE_WRITERS.TryRemove(sessionId,out oldModel);
VideoPackageModel model = new VideoPackageModel(privateKey, sessionId, startTime, endTime, seconds, PATH); //避免计时器出错
return FILE_WRITERS.TryAdd(sessionId, model);
}
return false;
}else
{
VideoPackageModel model = new VideoPackageModel(privateKey, sessionId, startTime, endTime, seconds, PATH);
return FILE_WRITERS.TryAdd(sessionId, model);
}
}
#endregion
#region 录像设备
/// <summary>
/// 处理设备发来的视频
/// </summary>
/// <param name="requestInfo"></param>
private void handleDeviceInfo(MyUdpRequestInfo requestInfo)
{
VideoPackageModel videoModel = getVideoPackage(requestInfo.SessionID);
if (videoModel == null || requestInfo.Body.Length< MIN_IMG_SIZE) {
return;
}
MemoryStream stream = null;
stream = new MemoryStream(requestInfo.Body);
Bitmap img = null;
try
{
img = HandleUdpUtils .BytesToBitmap(requestInfo.Body) ;
// img.Save("D:\\JGHPCXReadVersionD\\video\\001\\test.jpg");
videoModel.wirteVideo(img);
}
catch (Exception e)
{
LOG.Error(requestInfo.SessionID+"|"+e.ToString());
Console.WriteLine(requestInfo.SessionID + "|" + e.ToString());
}finally
{
if(stream!=null)
stream.Close();
if(img!=null)
img.Dispose();
}
}
/// <summary>
/// getVideoPackage
/// </summary>
/// <param name="machineCode"></param>
/// <returns></returns>
private VideoPackageModel getVideoPackage(string sessionId)
{
VideoPackageModel video;
if(FILE_WRITERS.TryGetValue(sessionId, out video))
{
LOG.Error(sessionId + "|设备未开启录像");
}
return video;
}
#endregion
}
}
辅助类——VideoPackageModel:用于封装Video数据与操作。
using Accord.Video.FFMPEG;
using Accord.Video.VFW;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Timers;
namespace Receive.udp
{
public class VideoPackageModel: IDisposable
{
//校验码
public string PrivateKey { get; }
//机器码
public string SessionId { get;}
//录制开始时间
public DateTime StartTime { get; }
//录制结束时间
public DateTime EndTime { get; }
//视频路径
public string VideoPath { get; }
//计时器
private System.Timers.Timer Timer = new System.Timers.Timer();
//视频写入(32位的FFMPEG)
// private VideoFileWriter Writer = new VideoFileWriter();
//是否释放资源
public bool Disposed = false;
//avi读取
private AVIWriter aviWriter = new AVIWriter("wmv3");
//VideoFileWriter 所需的时间戳
DateTime _firstFrameTime;
public VideoPackageModel(string privateKey,string sessionId,DateTime startTime,DateTime endTime,int second,string path)
{
SessionId = sessionId;
PrivateKey = privateKey;
StartTime = startTime;
EndTime = endTime;
_firstFrameTime = DateTime.Now;
int width = 848; //录制视频的宽度
int height = 480; //录制视频的高度
int fps = 20;
path = getTimeStamp(path);
aviWriter.Open(path, Convert.ToInt32( width), Convert.ToInt32( height));
// Writer.Open(path, width, height, fps, VideoCodec.Default);
int lastTime = second * 1000;
Console.WriteLine("持续时间"+lastTime);
Timer.Elapsed += new System.Timers.ElapsedEventHandler(endTimeEvent); //到达时间的时候执行事件;
Timer.AutoReset = false; //设置是执行一次(false)还是一直执行(true);
Timer.Interval = lastTime;//设置定时间隔(毫秒为单位)
Timer.Enabled = true;
}
/// <summary>
/// video 存储路径
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
private string getTimeStamp(string path) {
System.DateTime originTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));
long timeStamp = (long)(StartTime - originTime).TotalMilliseconds;
string filePath = path + "\\" + SessionId;
if (!Directory.Exists(filePath))
{
Directory.CreateDirectory(filePath);
}
return filePath + "\\" + timeStamp + ".avi";
}
/// <summary>
/// 结束资源
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void endTimeEvent(object sender, ElapsedEventArgs e)
{
using (this)
{
VideoPackageModel videoModel;
UdpAppServer.FILE_WRITERS.TryRemove(SessionId, out videoModel);
Console.WriteLine("endWriters");
}
}
/// <summary>
/// 写入视频
/// </summary>
/// <param name="img"></param>
public void wirteVideo(Bitmap img)
{
try
{
lock (aviWriter)
{
if (aviWriter != null)
aviWriter.AddFrame(img);
}
// if(Writer != null && Writer.IsOpen)
// {
// lock (Writer)
// {
// if (_firstFrameTime != null)
// {
// Writer.WriteVideoFrame(img, TimeSpan.FromMilliseconds(DateTime.Now.ToUniversalTime().Subtract(
//new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)
//).TotalMilliseconds - _firstFrameTime.ToUniversalTime().Subtract(
//new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)
//).TotalMilliseconds));
// }
// else
// {
// Writer.WriteVideoFrame(img);
// _firstFrameTime = DateTime.Now;
// }
// }
// }
}
catch (Exception e)
{
_firstFrameTime = DateTime.Now;
Console.WriteLine(e.ToString());
}
}
#region Dispose 操作
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!Disposed)
{
if (disposing)
{
if (aviWriter != null)
{
try
{
aviWriter.Close();
aviWriter.Dispose();
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
aviWriter = null;
}
}
if(Timer!=null)
{
Timer.Dispose();
Timer = null;
}
}
//处理非托管
Disposed = true;
}
}
~VideoPackageModel()
{
Dispose(false);
}
#endregion
}
}
辅助类——字节数组操作类。
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
namespace Receive.udp
{
//handle byte
public class HandleUdpUtils
{
public static readonly string IMG_END_FLAG = "##"; //设备传来的结束标志
public static readonly string WEB_END_FLAG = "**"; //WEB传来的结束标志
public static readonly string WEB_SESSION_KEY = "f21c2a0689443179082e02f8f44079";
public static readonly int KEY_LENGTH = 40;
public static readonly int MACHINE_CODE_LENGTH = 80;
public enum TimeTypeEnum { Day,Hour, Minute,Second }
/// <summary>
/// 读取byte[]并转化为图片
/// </summary>
/// <param name="bytes">byte[]</param>
/// <returns>Image</returns>
public static Bitmap BytesToBitmap(byte[] Bytes)
{
MemoryStream stream = null;
try
{
stream = new MemoryStream(Bytes);
return new Bitmap((Image)new Bitmap(stream));
}
catch (ArgumentNullException ex)
{
throw ex;
}
catch (ArgumentException ex)
{
throw ex;
}
finally
{
stream.Close();
}
}
/// <summary>
/// 得到传输来的结束标志
/// </summary>
/// <param name="bytes"></param>
/// <param name="length"></param>
/// <returns></returns>
public static string getEndFlagStr(byte[] bytes, int length)
{
int begin = length - 2;
int size = 2;
string endFlag = Encoding.ASCII.GetString(bytes, begin, size);
return String.IsNullOrEmpty(endFlag)?"":endFlag;
}
/// <summary>
/// 根据标志判断客户端类型
/// </summary>
/// <param name="bytes"></param>
/// <param name="length"></param>
/// <returns></returns>
public static int getClientType(byte[] bytes, int length)
{
var endFlagStr= getEndFlagStr(bytes ,length);
if (endFlagStr.Equals(WEB_END_FLAG)) //web
return 1;
else if (endFlagStr.Equals(IMG_END_FLAG)) //device
return 2;
else
return 0;
}
/// <summary>
/// 长度为40的密钥
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static string getPrivateKey(byte[] bytes)
{
string privateKey = "";
if (bytes.Length <= KEY_LENGTH)
return privateKey;
privateKey = Encoding.ASCII.GetString(bytes, 0, KEY_LENGTH).TrimEnd('\0'); //取掉休止符
return privateKey;
}
/// <summary>
/// 得到机器码
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static string getMachineCode(byte[] bytes)
{
string machineCode = "";
if (bytes.Length <= MACHINE_CODE_LENGTH)
return machineCode;
machineCode = Encoding.ASCII.GetString(bytes, KEY_LENGTH, MACHINE_CODE_LENGTH-KEY_LENGTH).TrimEnd
('\0');
return machineCode;
}
/// <summary>
/// 密钥构造成字节数组 (客户端)
/// </summary>
/// <param name="privateKey"></param>
/// <returns></returns>
public static byte[] InitPrivateKeyBytes(string privateKey)
{
byte[] fixedBytes = new byte[40];
if (String.IsNullOrEmpty(privateKey))
return fixedBytes;
var bytes = Encoding.Default.GetBytes(privateKey);
for (int i = 0; i < bytes.Length; i++)
fixedBytes[i] = bytes[i];
return fixedBytes;
}
/// <summary>
/// web取截止时间
/// </summary>
/// <param name="body"></param>
/// <param name="endTime"></param>
/// <returns></returns>
public static bool StrToTime(byte[] body, out DateTime endTime)
{
string str = Encoding.Default.GetString(body);
endTime = DateTime.Now;
try
{
DateTime dateTime = Convert.ToDateTime(str);
endTime = dateTime;
}
catch (Exception)
{
return false;
}
return true;
}
/// <summary>
/// 得到计时时间
/// </summary>
/// <param name="body"></param>
/// <param name="startTime"></param>
/// <param name="endTime"></param>
/// <returns></returns>
public static int getLasingTime(byte[] body,out DateTime startTime,out DateTime endTime)
{
string str = Encoding.Default.GetString(body);
int index = str.IndexOf("|");
startTime = DateTime.Now;
endTime = DateTime.Now;
if (index == -1)
return 0;
var timeType = str.Substring(index+1); //5|Second
var num = 0;
try
{
num = Convert.ToInt32(str.Substring(0, index));
}
catch (Exception)
{
return 0;
}
switch (timeType)
{
case nameof(TimeTypeEnum.Day) :
num *= 12 * 60 * 60;
break;
case nameof(TimeTypeEnum.Hour):
num *= 60 * 60;
break;
case nameof(TimeTypeEnum.Minute):
num *= 60;
break;
case nameof(TimeTypeEnum.Second):
break;
default:
num = 0;
break;
}
endTime = startTime.AddSeconds(num);
return num;
}
}
}
主函数:
using Receive.udp;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Config;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UdpSocketServer
{
class Program
{
private static IServerConfig SERVER_CONFIG;
private static IRootConfig ROOT_CONFIG;
public static IServerConfig DefaultServerConfig
{
get
{
return new ServerConfig
{
Ip = "127.0.0.1",
LogCommand = true,
MaxConnectionNumber = 1000,
Mode = SocketMode.Udp,
Name = "Udp Test Socket Server",
Port = 6602,
ClearIdleSession = true,
ClearIdleSessionInterval = 1,
IdleSessionTimeOut = 5,
SendingQueueSize = 100,
ReceiveBufferSize = 50000
};
}
}
static void Main(string[] args)
{
initData();
SERVER_CONFIG = DefaultServerConfig;
ROOT_CONFIG = new RootConfig();
var testServer = new UdpAppServer();
testServer.Setup(ROOT_CONFIG, SERVER_CONFIG);
testServer.Start();
while (true)
{
}
}
private static void initData()
{
UdpAppServer.PRIVATE_KEY.Add("webSession", "f21c2a0689443179082e02f8f44079");
UdpAppServer.PRIVATE_KEY.Add("001", "key001");
UdpAppServer.PRIVATE_KEY.Add("002", "key002");
UdpAppServer.PRIVATE_KEY.Add("003", "key003");
}
}
}
下载Demo地址:
https://download.csdn.net/download/a748448660/10559647
————————————————
版权声明:本文为CSDN博主「追风筝的摆渡人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/a748448660/article/details/81175712
https://blog.csdn.net/a748448660/article/details/81175712
标签:UDP,return,superSocket,requestInfo,System,AForge,using,public,string 来源: https://blog.csdn.net/ba_wang_mao/article/details/115611495