原文链接:http://blog.joycode.com/svs/archives/2009/12/25/115829.joy
坚持每天都有Post真的是一件很困难的事情,除非是那种宅男或是患有强烈的电脑依赖症。连续写东西真的很累。
今天聊聊StreamInsight中的Adapter,这是一个很大的话题,但是很重要,因为数据的输入和输出都要靠它了。开发Adapter不是很困难的事情,但是需要先了解一下Adapter的工作机制。Adapter是基于状态机的模式进行工作,而我们的代码主要是实现在不同状态间切换及如何从数据源中获取信息并构造事件数据。
当Adpater创建后通过Query的启动来调用Aapter的Start方法,之后AdapterState进入Running状态,这个时候我们需要通过代码实现对事件的创建及Enqueue操作,Enqueue会返回一个结果,表示队列的状态,如果状态为进入Suppended状态,当可用时StreamInsight会调用Resume()方法重新是Adapter进入Running状态。这个图上给出的是InputAdapter和OutputAdapter的状态图。
Adapter的停止将分为2个状态Stopping和Stopped,我们在处理和生成事件的时候要注意AdapterState,如果是Stopping需要对缓冲区的数据进行处理。
Adapter的创建需要通过工厂模式进行实例化。首先我们要实现IInputAdapterFactory\ITypeInputAdapterFactory或对应的OutputAdapterFactory,这个是成对出现的。这两种接口分别用于创建弱类型和强类型的AdapterFactory,我个人认为弱类型会灵活一些,强类型的性能稍好一些,并可以编译检查,具体使用需要结合需求。下面是我的一个AdpaterFactory的代码。其中的我只处理对于Point类型Event的处理,并返回NetworkPointInput这个Adapter。
public struct NetworkInputConfig { //网络监听地址 public string InterfaceAddress { get; set; } //CTI public int CtiFrequency { get; set; } //输入数据字段名称及顺序 public List<string> InputFieldOrders { get; set; } } public class NetworkInputFactory : IInputAdapterFactory<NetworkInputConfig> { public InputAdapterBase Create(NetworkInputConfig configInfo, EventShape eventShape, CepEventType cepEventType) { InputAdapterBase adapter = default(InputAdapterBase); if (eventShape == EventShape.Point) { adapter = new NetworkPointInput(configInfo, cepEventType); } else { throw new ArgumentException("The adapter cannot instance adapter with event shape {0}", eventShape.ToString()); } return adapter; } public void Dispose() { } }
针对Adapter我们需要继承PointInputAdapter类,并重载Resume()和Start()方法。当然我们还需要写构造函数接受AdapterFactory中传入的config和cepEventType。
public NetworkPointInput(NetworkInputConfig configInfo, CepEventType cepEventType) { _bindtimeEventType = cepEventType; _ctiFrequency = configInfo.CtiFrequency; foreach (PcapDevice device in Pcap.GetAllDevices()) { foreach (PcapAddress address in device.Addresses) { if (address.Addr.ipAddress.ToString() == configInfo.InterfaceAddress) { _device = device; break; } } if (_device != null) break; } if (_device == null) throw new ArgumentException("Interface address is not found"); _inputOrdinalToCepOrdinal = new Dictionary<int, int>(); //配置文件字段数量与CepEventType字段数量不一致 if (configInfo.InputFieldOrders != null && configInfo.InputFieldOrders.Count != cepEventType.Fields.Count) { throw new ArgumentException( string.Format(CultureInfo.InvariantCulture, "The configuration element InputFieldOrders should have {0} elements, but it only has {1} elements", cepEventType.Fields.Count, configInfo.InputFieldOrders.Count)); } CepEventTypeField engineField; for (int i = 0; i < cepEventType.Fields.Count; i++) { if (configInfo.InputFieldOrders != null) { if (!cepEventType.Fields.TryGetValue(configInfo.InputFieldOrders[i], out engineField)) { throw new ArgumentException( 0; string.Format(CultureInfo.InvariantCulture, "Event type {0} doesn’t have an input field named ‘{1}’", cepEventType.ShortName, configInfo.InputFieldOrders[i])); } _inputOrdinalToCepOrdinal.Add(i, engineField.Ordinal); } else { // Use default mapping _inputOrdinalToCepOrdinal.Add(i, i); } } if (!_device.Opened) _device.Open(true, 1000); _device.SetFilter("ip and tcp"); }
我的程序引用了SharpPcap类库并安装Winpcap来监听网络数据。
构造函数还是很简单基本上是从配置文件中创建需要使用的一些内部对象。
public override void Resume() { ProduceEvents(); } public override void Start() { ProduceEvents(); }
主要是这段ProduceEvents()
private void ProduceEvents() { PointEvent currEvent = default(PointEvent); EnqueueOperationResult result = EnqueueOperationResult.Full; Packet packet = null; while (true) { //判断设备状态 if (!_device.Opened) { _device.Open(true, 1000); _device.SetFilter("ip and tcp"); } //循环获取网络数据包 while ((packet = _device.GetNextPacket()) != null) { #region Adapter Stopping if (AdapterState.Stopping == AdapterState) { if (_pendingEvent != null) { currEvent = _pendingEvent; _pendingEvent = null; } PrepareToStop(currEvent); Stopped(); return; } if (_pendingEvent != null) { currEvent = _pendingEvent; _pendingEvent = null; } else if (_pendingCtiTime != null) { } else { if (packet is TCPPacket) { currEvent = CreateEventFromPacket(packet); _pendingEvent = null; } } if (_pendingCtiTime != null) { result = EnqueueCtiEvent(_pendingCtiTime.Value); if (EnqueueOperationResult.Full == result) { PrepareToResume(_pendingCtiTime.Value); Ready(); return; } else { _pendingCtiTime = null; } } else { DateTimeOffset currEventTime = currEvent.StartTime; result = Enqueue(ref currEvent); if (EnqueueOperationResult.Full == result) { PrepareToResume(currEvent); Ready(); return; } else { _eventsEnqueued++; _pendingEvent = null; if (Zero == (_eventsEnqueued % _ctiFrequency)) { DateTimeOffset currCtiTime = currEventTime + TimeSpan.FromTicks(NumberOfTicks); result = EnqueueCtiEvent(currEventTime); if (EnqueueOperationResult.Full == result) { PrepareToResume(currEventTime); Ready(); return; } else { _pendingCtiTime = null; } } } } } } } private PointEvent CreateEventFromPacket(Packet packet) { PointEvent evt = CreateInsertEvent(); evt.StartTime = DateTime.Now ; TCPPacket tcp = (TCPPacket)packet; string content = string.Empty; if (tcp.TCPData.Length > 0) { content = System.Text.ASCIIEncoding.ASCII.GetString(tcp.TCPData); if (content.Length > 256) content = content.Substring(0, 256); } string[] data = new string[] { //Len packet.PcapHeader.CaptureLength.ToString(), tcp.SourceAddress.ToString(), tcp.DestinationAddress.ToString(), tcp.SourcePort.ToString(), tcp.DestinationPort.ToString(), content }; //populate the payload fields for (int ordinal = 0; ordinal < _bindtimeEventType.FieldsByOrdinal.Count; ordinal++) { try { int cepOrdinal = _inputOrdinalToCepOrdinal[ordinal]; CepEventTypeField evtField = _bindtimeEventType.FieldsByOrdinal[cepOrdinal]; object value = Convert.ChangeType(data[ordinal], evtField.Type.ClrType); evt.SetField(cepOrdinal, value); } catch (AdapterException e) { Console.WriteLine(e.Message + "\n" + e.StackTrace); } } return evt; } private void PrepareToResume(DateTimeOffset currCtiTime) { _pendingCtiTime = currCtiTime; } private void PrepareToStop(PointEvent currEvent) { if (null != currEvent) { ReleaseEvent(ref currEvent); } if (_device.Opened) _device.Close(); } private void PrepareToResume(PointEvent currEvent) { _pendingEvent = currEvent; }
这些都是最核心的部分了基本上所有的Adapter都差不多,不同的是Event如何创建,这里面我有一个方法CreateEventFromPacket()来创建Event。但是需要注意的是Event Payload中的字段最多是32个这个无法改,每个字段默认最长为512字节,目前我还不知道如何更改payload默认长度限制。
@Starbucks_Guangzhou
评论:这段输入适配器的代码应该是模仿 CsvInputAdapter编写的,当然解释有点少,这里推荐大家阅读前面介绍的SQL CRD MSDN博客中的《 StreamInsight手札系列》文章。