Reputation:
I'm trying to set up an interface to send serialized objects between a client and a server using a TCP connection.
I have the following class and extension methods for reading and writing these objects from/to the stream:
public class NetworkTransmittedUpdate
{
/// <summary>
/// The type of update that is being sent
/// </summary>
public UpdateType UpdateType { get; init; }
/// <summary>
/// The type that UpdateObject should be deserialized as. Should match one of the types in SerializedTypes
/// </summary>
public string UpdateObjectType { get; init; } = string.Empty;
/// <summary>
/// Any information that accompanies the update. Each update type has an associated UpdateObject type, which is
/// usually but not always the same for both frontend and backend. UpdateObject may be null if only the event
/// happening must be conveyed.
/// </summary>
public object? UpdateObject { get; init; }
}
public static class StreamExtensions
{
/// <summary>
/// Attempts to read a serialized object from the stream, convert it, and return it
/// </summary>
/// <param name="stream">The stream to read from</param>
/// <typeparam name="T">The type of object to deserialize</typeparam>
/// <returns>An instance of the object, or null if it could not be created</returns>
public static T? ReadObject<T>(this Stream stream) where T : class
{
// Get length of incoming object
var sizeBuffer = new byte[sizeof(int)];
var bytesRead = 0;
Debug.Assert(stream.CanRead);
while (bytesRead < sizeBuffer.Length)
bytesRead += stream.Read(sizeBuffer, bytesRead, sizeBuffer.Length - bytesRead);
// Create a buffer for serialized data
var serializationLength = BitConverter.ToInt32(sizeBuffer);
var serializedDataBuffer = new byte[serializationLength];
// Get data from the stream
bytesRead = 0;
while (bytesRead < serializationLength)
bytesRead += stream.Read(serializedDataBuffer, bytesRead, serializationLength - bytesRead);
// Deserialize data into an object
var json = Encoding.UTF8.GetString(serializedDataBuffer);
// Deserialize the wrapped type correctly using reflection
var deserializedObject = JsonSerializer.Deserialize<T>(json);
if (deserializedObject is NetworkTransmittedUpdate {UpdateObject: { }} dynamicUpdateWrapper)
{
// In this block, we know we are wrapping data. The deserializer doesn't choose the write type by default, so we need to create a new
var innerData = dynamicUpdateWrapper.UpdateObject!.ToString();
var innerDataType = Type.GetType(dynamicUpdateWrapper.UpdateObjectType);
return new NetworkTransmittedUpdate
{
UpdateType = dynamicUpdateWrapper.UpdateType,
UpdateObjectType = dynamicUpdateWrapper.UpdateObjectType,
UpdateObject = JsonSerializer.Deserialize(innerData ?? string.Empty, innerDataType!)
} as T;
}
return deserializedObject;
}
/// <summary>
/// Serializes and writes an object to a stream
/// </summary>
/// <param name="stream">The stream to write UTF-8 data to</param>
/// <param name="value">An object to serialize and write</param>
/// <typeparam name="T">A serializable type</typeparam>
public static void WriteObject<T>(this Stream stream, T value)
{
var json = JsonSerializer.Serialize(value);
var bytes = Encoding.UTF8.GetBytes(json);
// Always send a number of bytes ahead, so the other side knows how much to read
stream.Write(BitConverter.GetBytes(bytes.Length));
// Send serialized data
stream.Write(bytes);
stream.Flush();
}
}
For some reason, the below test gets stuck while reading the length of object being sent.
[Fact]
public void TestChatMessageNetworkPackets()
{
// Setup
var listener = new TcpListener(IPAddress.Any, 12321);
listener.Start();
using var client = new TcpClient("localhost", 12321);
using var server = listener.AcceptTcpClient();
using var clientStream = client.GetStream();
using var serverStream = server.GetStream();
// Send and receive chat message
clientStream.WriteObject(new NetworkTransmittedUpdate()
{
UpdateObject = new string("Test message"),
UpdateType = UpdateType.ChatMessage,
UpdateObjectType = typeof(string).FullName!
});
var update = clientStream.ReadObject<NetworkTransmittedUpdate>();
}
Upvotes: 0
Views: 401
Reputation: 13517
Change this:
var update = clientStream.ReadObject<NetworkTransmittedUpdate>();
To this:
var update = serverStream.ReadObject<NetworkTransmittedUpdate>();
You want to write on the Client stream, then read on the Server stream since that's on the other side of the fence.
Upvotes: 1