2010-07-19 16 views
6

Tôi đang xem lại thiết kế phân tích cú pháp giao thức truyền thông cho một luồng byte (dữ liệu nối tiếp, nhận được 1 byte tại một thời điểm).Thiết kế phân tích giao thức truyền thông nhị phân cho dữ liệu nối tiếp

Cấu trúc gói tin (không thể thay đổi) là:

|| Start Delimiter (1 byte) | Message ID (1 byte) | Length (1 byte) | Payload (n bytes) | Checksum (1 byte) || 

Trong quá khứ tôi đã thực hiện hệ thống như vậy trong một cách tiếp cận nhà máy thủ tục. Khi mỗi byte dữ liệu đến, máy trạng thái được định hướng để xem nơi/nếu dữ liệu đến phù hợp với một gói hợp lệ một byte tại một thời điểm, và một khi toàn bộ gói đã được lắp ráp, một câu lệnh chuyển đổi dựa trên ID thông báo thực hiện xử lý thích hợp cho tin nhắn. Trong một số triển khai, vòng lặp xử lý máy/trình phân tích cú pháp/trạng thái nằm trong chuỗi riêng của nó để không gây gánh nặng cho dữ liệu nối tiếp đã nhận xử lý sự kiện và được kích hoạt bởi một chuỗi ký tự chỉ rõ đã được đọc.

Tôi tự hỏi nếu có một giải pháp thanh lịch hơn cho vấn đề phổ biến này, khai thác một số tính năng ngôn ngữ hiện đại hơn của thiết kế C# và OO. Bất kỳ mẫu thiết kế nào có thể giải quyết vấn đề này? Sự kiện theo định hướng so với kết hợp với cuộc thăm dò ý kiến?

Tôi muốn nghe ý tưởng của bạn. Cảm ơn.

Prembo.

Trả lời

4

Trước hết, tôi sẽ tách trình phân tích cú pháp gói khỏi trình đọc luồng dữ liệu (để tôi có thể viết các bài kiểm tra mà không cần xử lý luồng). Sau đó xem xét một lớp cơ sở cung cấp một phương thức để đọc trong một gói và một để viết một gói.

Bên cạnh đó tôi sẽ xây dựng một từ điển (một lần duy nhất sau đó tái sử dụng nó cho các cuộc gọi trong tương lai) như sau:

class Program { 
    static void Main(string[] args) { 
     var assembly = Assembly.GetExecutingAssembly(); 
     IDictionary<byte, Func<Message>> messages = assembly 
      .GetTypes() 
      .Where(t => typeof(Message).IsAssignableFrom(t) && !t.IsAbstract) 
      .Select(t => new { 
       Keys = t.GetCustomAttributes(typeof(AcceptsAttribute), true) 
         .Cast<AcceptsAttribute>().Select(attr => attr.MessageId), 
       Value = (Func<Message>)Expression.Lambda(
         Expression.Convert(Expression.New(t), typeof(Message))) 
         .Compile() 
      }) 
      .SelectMany(o => o.Keys.Select(key => new { Key = key, o.Value })) 
      .ToDictionary(o => o.Key, v => v.Value); 
      //will give you a runtime error when created if more 
      //than one class accepts the same message id, <= useful test case? 
     var m = messages[5](); // consider a TryGetValue here instead 
     m.Accept(new Packet()); 
     Console.ReadKey(); 
    } 
} 

[Accepts(5)] 
public class FooMessage : Message { 
    public override void Accept(Packet packet) { 
     Console.WriteLine("here"); 
    } 
} 

//turned off for the moment by not accepting any message ids 
public class BarMessage : Message { 
    public override void Accept(Packet packet) { 
     Console.WriteLine("here2"); 
    } 
} 

public class Packet {} 

public class AcceptsAttribute : Attribute { 
    public AcceptsAttribute(byte messageId) { MessageId = messageId; } 

    public byte MessageId { get; private set; } 
} 

public abstract class Message { 
    public abstract void Accept(Packet packet); 
    public virtual Packet Create() { return new Packet(); } 
} 

Edit: Một số giải thích về những gì đang xảy ra ở đây:

Trước tiên:

[Accepts(5)] 

Đường này là thuộc tính C# (được xác định bởi AcceptsAttribute)) Cho biết lớp FooMessage chấp nhận id của thông điệp của 5.

Thứ hai:

Có từ điển đang được xây dựng trong thời gian chạy qua phản ánh. Bạn chỉ cần làm điều này một lần (tôi sẽ đặt nó vào một lớp singleton mà bạn có thể đặt một trường hợp thử nghiệm trên nó có thể được chạy để đảm bảo rằng từ điển xây dựng chính xác).

Thứ ba:

var m = messages[5](); 

Dòng này được biểu thức lambda biên soạn sau đây ra khỏi từ điển và thực hiện nó:

()=>(Message)new FooMessage(); 

(Dàn diễn viên là cần thiết trong .NET 3.5 nhưng không phải trong 4,0 do với những thay đổi về biến đổi trong cách làm việc của delagates, trong 4.0 đối tượng thuộc loại Func<FooMessage> có thể được gán cho một đối tượng thuộc loại Func<Message>.)

biểu thức lambda này được xây dựng bởi các dòng phân Giá trị trong quá trình tạo từ điển:

Value = (Func<Message>)Expression.Lambda(Expression.Convert(Expression.New(t), typeof(Message))).Compile() 

(Dàn diễn viên ở đây là cần thiết để đúc biểu thức lambda biên soạn để Func<Message>.)

tôi đã làm điều đó theo cách này bởi vì tôi xảy ra đã có sẵn loại cho tôi tại thời điểm đó. Bạn cũng có thể sử dụng:

Value =()=>(Message)Activator.CreateInstance(t) 

Nhưng tôi tin rằng sẽ chậm hơn (và các diễn viên ở đây là cần thiết để thay đổi Func<object> vào Func<Message>).

Thứ tư:

.SelectMany(o => o.Keys.Select(key => new { Key = key, o.Value })) 

này được thực hiện bởi vì tôi cảm thấy rằng bạn có thể có giá trị trong việc đặt càng AcceptsAttribute hơn một lần trên một lớp (để chấp nhận nhiều hơn một id của thông điệp mỗi lớp). Điều này cũng có tác dụng phụ tốt đẹp của việc bỏ qua các lớp thông báo không có thuộc tính id thư (nếu không phương thức Where sẽ cần phải có độ phức tạp để xác định nếu thuộc tính có mặt).

+0

Xin chào Bill, Cảm ơn câu trả lời đó. Tôi đang cố gắng để có được đầu của tôi xung quanh nó! Làm thế nào để ... [Chấp nhận (5)] ... ký hiệu có nghĩa là gì? Từ điển có được phản ánh theo thời gian chạy không? – Prembo

+0

Cảm ơn lời giải thích chi tiết đó. Tôi đã học được rất nhiều! Đó là một giải pháp rất thanh lịch và có khả năng mở rộng. Xuất sắc. I – Prembo

1

Những gì tôi thường làm là định nghĩa một lớp thông báo cơ sở trừu tượng và lấy được các thông điệp được niêm phong từ lớp đó. Sau đó, có một đối tượng phân tích cú pháp thư có chứa máy trạng thái để giải thích các byte và xây dựng một đối tượng thông báo thích hợp. Đối tượng phân tích cú pháp thông báo chỉ có một phương thức (để chuyển nó vào các byte đến) và tùy chọn một sự kiện (được gọi khi một thông báo đầy đủ đã đến).

Sau đó, bạn có hai lựa chọn để xử lý các thông điệp thực tế:

  • Xác định một phương pháp trừu tượng trên lớp thông báo cơ sở, trọng nó trong mỗi người trong số các lớp thông điệp có nguồn gốc. Yêu cầu trình phân tích cú pháp thông báo gọi phương thức này sau khi thông báo đã đến hoàn toàn.
  • Tùy chọn thứ hai ít hướng đối tượng hơn, nhưng có thể dễ dàng hơn để làm việc với: chỉ để lại các lớp thông báo dưới dạng dữ liệu. Khi thông báo hoàn tất, gửi nó thông qua một sự kiện lấy lớp thông điệp cơ sở trừu tượng làm tham số của nó. Thay vì câu lệnh chuyển đổi, trình xử lý thường là as - chuyển chúng thành các loại có nguồn gốc.

Cả hai tùy chọn này đều hữu ích trong các trường hợp khác nhau.

+0

Cảm ơn lời đề nghị của bạn Stephen. Đó là một cách tiếp cận thực hiện rất dễ dàng. – Prembo

2

Tôi hơi muộn một chút với bữa tiệc nhưng tôi đã viết một khuôn khổ mà tôi nghĩ có thể làm được điều này. Nếu không biết nhiều hơn về giao thức của bạn, thật khó để tôi viết mô hình đối tượng nhưng tôi nghĩ nó sẽ không quá khó. Hãy xem binaryserializer.com.

+0

Cảm ơn bạn đã chia sẻ - Tôi sẽ xem xét. – Prembo

+0

Không sao, hãy cho tôi biết nếu nó giúp hoặc nếu tôi cần sửa cái gì đó :) – Jeff