Search code examples
c#serializationserial-portbinary-serialization

C# read binary not as it was written


I write byte arrays to binary file and each array starts with number 313 or 331, but when I read it I have arrays which start with number 28164. What have went wrong? Code below.

Question history


I receive data from SerialPort. DataReceived raised on secondary thread, so all my data processing runs on that thread. I check the data before serialization and during serialization and everything is ok, no problems there, but when I start read this file (when there is no more writing) I read not that data, that I've written (you can read question history bellow for more information). I've find out that if every serialization call I would write in different files that in no one will be errors and that's why I suppose that reason for error is accessing File/BinaryWriter from different threads. I've tried to use locks, but it didn't help. What I should do?

lock try:

private static object locker = new object();

public void Serialize() {
    lock(locker) {
        // some logic here
    }
}

I have ConcurrentQueue<byte[]> as Buffer. When I receive an array of bytes from SerialPort I check CRC and if it's okay, I add call next function (short description further):

public void PassData(byte[] data) {
    const int recordLen = 18;
    const int unusedBytesLen = 6; // 2 for command, 2 for index, and 2 for CRC
    const int addressLen = 2;
    const int nonRecordDataLen = unusedBytesLen + addressLen;

    int recordDataLen = data.Length - nonRecordDataLen;
    byte recordsCount = (byte)(recordDataLen / recordLen);

    if(recordDataLen % recordLen != 0) {
        return;
    }

    if(recordDataLen == 0) {
        return;
    }

    int serializeLen = data.Length - unusedBytesLen + 1;  // plus one for record count
    totalCount += serializeLen;
    byte[] temp = new byte[serializeLen];

    Array.Copy(data, 0, temp, 0, addressLen);    // copy address
    temp[addressLen] = recordsCount;             // record count after address
    Array.Copy(data, 6, temp, 3, recordDataLen); // copy records

    Buffer.Enqueue(temp);

    int addr = BitConverter.ToUInt16(data, 0);
    if(addr!=331 && addr != 313) {
        throw new Exception("Incorrect data");
    }
}

So, as you can see I don't add an array to buffer if

  1. I've got an incorrect record (if data.Length-6 is not divisible by 18)
  2. If I've got no records (if data.Length==6).

The array consists of the address, records count, and records (group of 18 bytes).

And if the ushort from the first two bytes is not one of my addresses I throw Exception.

After some conditions, I begin to serialize.

public void Serialize() {
    if(Buffer.Count == 0)
        return;

    using(var stream = File.Open(fileName, FileMode.OpenOrCreate)) {
        using(var writer = new BinaryWriter(stream)) {
            while(Buffer.Count > 0) {
                byte[] bOut;
                bool success = Buffer.TryDequeue(out bOut);

                if(bOut[2] == 0) {
                    // will be never thrown cause of the second reason
                    throw new Exception("Incorrect data");
                }
                if(bOut.Length != bOut[2] * 18 + 3) {
                    // will be never thrown cause of the first reason
                    throw new Exception("Incorrect data");
                }

                if(success) {
                    writer.Write(bOut);
                }
                else {
                    return;
                }
            }
        }
    }
}

But when I try to read this file (after it was written) I have some incorrect records. The first several are okay, but then I receive an address like 12751 (which is impossible cause I throw an exception if I recieve not my address). Why does this happen? Read function below:

using(var stream = File.Open(fileName, FileMode.Open)) {
    using(var reader = new BinaryReader(stream)) {
        int length = (int)reader.BaseStream.Length;
        while(reader.BaseStream.Position != length) {
            uint readerAddr = reader.ReadUInt16();
            byte count = reader.ReadByte();
            for(int i = 0; i < count; i++) {
                uint markAddr = reader.ReadUInt16();
                byte cmp0 = reader.ReadByte();
                byte cmp1 = reader.ReadByte();
                byte cmp2 = reader.ReadByte();
                byte cmp3 = reader.ReadByte();
                byte cmp4 = reader.ReadByte();
                byte cmp5 = reader.ReadByte();
                byte cmp6 = reader.ReadByte();
                byte cmp7 = reader.ReadByte();
                byte cmp8 = reader.ReadByte();
                char fec = reader.ReadChar();
                uint tim = reader.ReadUInt32();
                byte freq = reader.ReadByte();
                byte rssi = reader.ReadByte();
                sb.Append($"{readerAddr} {markAddr} {cmp0} {cmp1} {cmp2} {cmp3} {cmp4} {cmp5} {cmp6} {cmp7} {cmp8} {fec} {tim} {freq} {rssi}\n");
            }
            Console.WriteLine(sb.ToString());
            sb.Length = 0;
            Console.ReadLine();
        }
    }
}

P.S. I'm awfully sorry if my question is cumbersome and unclear, I have been fighting with this bug all day long. Please, leave comments and I'll try to give some clarifications.

EDIT / Clarification:

Why do I use ConcurrentQueue<byte>? This is my attempt to fix this bug.

I receive data from SerialPort in the next way: I have a SerialPort.DataReceived event handler that enables a Timer for 15ms. If the event is triggered in this time interval I restart Timer (just timer.Stop(); timer.Start();). On Timer.Elapsed event handler I check CRC and pass the data if everything is okay.

So, I've thought the reason for the errors is that SerialPort.DataReceived raised on a secondary thread and so I have some problems with concurrency.

On my first try, I used List<byte> as Buffer which is not thread-safe.

UPD: I've tried to write each group of data in separate file and problem gone. So maybe there is some problems with calling Serialize() from different Threads?.. I've tried to lock work with file, but it didn't help.


Solution

  • You (I) should use FileMode.Append instead of FileMode.OpenOrCreate. If you use the last one BinaryWriter will start to rewrite file instead of adding new data to the end. Lock is redundant (in my case) cause I have significant time delay between two serialization iterations.