使用装饰器模式的密码保护流
在 System.IO.Stream 基础上构建一个受密码保护的流
引言
本文解释了如何使用装饰器模式在System.IO.Stream
之上构建一个受密码保护的流。然后,该流可以像普通流一样使用,只需稍作修改即可。
背景
这项工作只是我阅读'Judith Bishop'的《C# 3.0 设计模式》一书时所做练习的一部分。 这是“装饰器模式”一节的练习。 为了练习,我选择让它变得复杂一点,并且更具功能性,而不是仅仅是一个简单的练习。
Using the Code
代码有四个逻辑部分
PasswordProtectedStream
类,它扩展了System.IO.Stream
并覆盖了stream
类的几个必需属性。 最重要的点是我如何重写Stream
的read
和write
方法。- '
DataEnvelop
' 类,它被标记为 'Serializable
' 并且它是数据的实际占位符。 使用 'BinaryFormatter
' 将此类序列化为类文件。 为了使代码更简单,我将数据保存为纯文本,否则对于更真实的情况,可以使用加密,或者可以使用相同的“装饰器模式”技术将另一个流扩展为“EncryptedStream
”。 - 一个委托 '
ReadCompleteHandler
' 和一个对应的事件 'On_ReadComplete
'。在验证密码后,此事件将数据传递给客户端应用程序。我无法直接从read
方法中获取数据,因为要读取的数据长度大于实际数据长度(由于序列化)。因此使用了这种技术。 但是,如果有人能帮助我删除委托并将数据直接放入缓冲区中,方法是重置要读取的数据的位置和长度,我将不胜感激。 - 一个类 '
ReadCompleteEventArgs
',它是事件 'On_ReadComplete
' 的一个参数,并将读取状态和数据传递给客户端应用程序。
整个代码按以下流程工作
- 您使用两个参数创建
PasswordProtectedStream
的实例:一个基本流和一个密码。 - 您将此流放入
StreamReader
或StreamWriter
中。 - 如果将其放入
StreamWriter
,它将调用PasswordProtectedStream
的重写“Write
”方法。此方法将创建一个类 'DataEnvelop
' 的可序列化对象的实例,并将密码与数据一起放入该对象中,并将其写入实际流中。 - 如果将其放入
StreamReader
中,则会调用PasswordProtectedStream
的重写“Read”方法。它的工作方式与流的通常读取方式略有不同。它将读取所有字节(包括序列化对象DataEnvelop
的标头),检查密码,如果正确,则会将数据传递给“On_ReadComplete”事件,以便由客户端应用程序处理。这是使用“ReadCompleteEventArgs
”类完成的。否则,如果密码错误,则不会将任何数据传递给事件,并且仅报告失败状态,以便可以在客户端应用程序中捕获。状态由一个名为 'Status
' 的枚举标记
缺点:由于这是第一个版本,因此代码在单次读取和单次写入上运行良好。 但是,它很可能在多次读取和多次写入时出现问题。 如果有人可以提出一种我不需要使用委托和事件并且可以直接在流的 Read
方法中重置缓冲区的方式,则可以解决此问题。 我尝试过,但由于一些查找和定位问题以及缓冲区长度为 1024 字节,它一直在循环。 欢迎所有建议。
整个代码如下
//extends the stream class
public class PasswordProtectedStream : Stream
{
string password; //password for the stream
Stream str; //the base stream
bool eventRaised = false; //this is a kind of locking variable
//so that if read operations are called multiple times,
//the event (the description to be followed) doesn't
//get invoked multiple times
public delegate void ReadCompleteHandler
(object s, ReadCompleteEventArgs e); //delegate for event
public event ReadCompleteHandler On_ReadComplete; // event that passes data
//and read status to the client application
public PasswordProtectedStream(Stream str,
string password) //set the base parameters
: base()
{
this.str = str;
this.password = password;
}
#region "Overridden Methods of Stream"
public override void Write(byte[] buffer,
int offset, int count) //override the write method
{
byte[] data = new byte[count];
for (int i = offset, j = 0; j < count; i++, j++)
data[j] = buffer[i]; //construct the actual data buffer
DataEnvelop env = new DataEnvelop(data, password); //create an instance of
//our own custom serialized class (to be followed later)
BinaryFormatter f = new BinaryFormatter();
f.Serialize(str, env); //serialize the object
}
public override int Read(byte[] buffer, int offset, int count) //override the
//read method
{
int r = str.Read(buffer, offset, count); //read all bytes from base stream
byte[] newData = new byte[str.Length]; //construct buffer to hold
//actual data and not the default buffer otherwise the object
//won't be de-serialized properly due to padded empty bytes
for (int i = 0; i < str.Length; i++) //in respect to the actual
//length of the base stream
newData[i] = buffer[i]; //copy all non-empty bytes
MemoryStream mstr = new MemoryStream(newData); //construct memory stream
//for de-serialization
BinaryFormatter f = new BinaryFormatter();
DataEnvelop env = (DataEnvelop)f.Deserialize(mstr);
if (env.password == password) //if password is matched
{
if (On_ReadComplete != null && !eventRaised) //if event is not empty
// and it's not been invoked earlier
{
On_ReadComplete(this, new ReadCompleteEventArgs
(ReadCompleteEventArgs.Status.SUCCESS,
env.data)); //bind successful read event
eventRaised = true; //mark it so that the event
//is not invoked again on multiple reads
}
}
else //if wrong password
{
if (On_ReadComplete != null && !eventRaised) //if event is not empty
//and it's not been invoked earlier
{
On_ReadComplete(this, new ReadCompleteEventArgs
(ReadCompleteEventArgs.Status.FAILURE, null)); //bind
//un-successful read event
eventRaised = true; //mark it so that the event is not invoked
//again on multiple reads
}
}
return r; //return actual number of bytes read and not the bytes
//of the actual data otherwise it will loop.
//This is the only reason why
//I had to pass the data to the event and couldn't
//directly process here.
//If anyone can suggest a way, I would be grateful.
}
public override void Close()
{
str.Close();
str.Dispose();
}
#endregion
#region "Overridden Properties of Stream"
public override void SetLength(long value)
{
str.SetLength(value);
}
public override long Seek(long offset, SeekOrigin origin)
{
return str.Seek(offset, origin);
}
public override long Position
{
get
{
return str.Position;
}
set
{
str.Position = value;
}
}
public override long Length
{
get { return str.Length; }
}
public override bool CanWrite
{
get { return str.CanWrite; }
}
public override void Flush()
{
str.Flush();
}
public override bool CanSeek
{
get { return str.CanSeek; }
}
public override bool CanRead
{
get { return str.CanRead; }
}
#endregion
[Serializable]
class DataEnvelop //private DataEnvelop Class that is serialized to the base stream
// with the password and the data bytes, it can be enhanced to encrypt the password
{
public byte[] data { get; set; }
public string password { get; set; }
public DataEnvelop(byte[] data, string password)
{
this.data = data;
this.password = password;
}
}
public class ReadCompleteEventArgs : EventArgs //arguments passed to the event
//that passes data to the client
{
byte[] data; //actually data bytes
Status status; //status of the current output,
//whether password is correct or not
public enum Status { SUCCESS, FAILURE } //if password is correct,
//set as CORRECT else FAILURE
public ReadCompleteEventArgs(Status status, byte[] data)
{
this.data = data;
this.status = status;
}
public byte[] Data { get { return data; } }
public Status ReadStatus { get { return status; } }
}
}
//This is the client code. You can place two buttons named
//button1 and button2 and place the following code in the forms code section
private void button1_Click(object sender, EventArgs e)
{
PasswordProtectedStream st = new PasswordProtectedStream
(new FileStream("C:/pwdsample.txt",
FileMode.Create), "12345"); //create instance of our stream
StreamWriter w = new StreamWriter(st);
w.Write("Hye this is test"); //write some data
w.Flush();
w.Close();
st.Close();
st.Dispose();
MessageBox.Show("Data Written Successfully");
}
private void button2_Click(object sender, EventArgs e)
{
PasswordProtectedStream st = new PasswordProtectedStream(new FileStream
("C:/pwdsample.txt", FileMode.Open), "12345"); //create instance
//of our stream,
//try changing a password here
st.On_ReadComplete += new PasswordProtectedStream.ReadCompleteHandler
(st_On_ReadComplete); //hook the read complete event
StreamReader w = new StreamReader(st);
w.ReadToEnd(); //read but don't display the data here,
//else you won't get anything useful.
//Readers are welcome if they can provide an implementation
//which enables us to read the data in usual manner,
//without the use of event
w.Close();
st.Close();
st.Dispose();
}
void st_On_ReadComplete
(object s, PasswordProtectedStream.ReadCompleteEventArgs e)
{
if (e.ReadStatus == PasswordProtectedStream.ReadCompleteEventArgs.
Status.FAILURE) //if wrong password
MessageBox.Show("Wrong Password");
else
MessageBox.Show("Data Read back: " +
Encoding.ASCII.GetString(e.Data)); //display data
}
关注点
- 设计模式
- .NET 中的 I/O 流
历史
- 这是此示例应用程序的第一个版本。