简体   繁体   中英

XmlSerializer Won't Deserialize over NetworkStream

I'm looking to implement a simple Client/Server setup that can transfer a serialized EmailRequest object (using XmlSerializer ) from the client to the server, to be used to send an email.

Server

class Program
{
    private static void Main()
    {
        try
        {
            TcpListener listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 8888);

            listener.Start();

            while (true)
            {
                Console.WriteLine("Waiting for request..");

                TcpClient client = listener.AcceptTcpClient();

                NetworkStream stream = client.GetStream();

                XmlSerializer serializer = new XmlSerializer(typeof(EmailRequest));

                EmailRequest request = (EmailRequest)serializer.Deserialize(stream); // This is where the problem is //

                bool success = SendGridUtility.SendEmail(request.Recipients, request.Subject,
                    request.BodyType == EmailRequest.Type.Plain ? request.PlainText : request.HTMLText,
                    request.BodyType).Result;

                byte[] response = Encoding.ASCII.GetBytes(success ? "Success" : "Failure");
                Console.WriteLine("Email Successfully Sent!");

                stream.Write(response, 0, response.Length);
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Something went wrong.. :( :\n" + e.Message + "\n\n");
        }
        Console.ReadLine();
    }
}

Client

class Program
{
    static void Main(string[] args)
    {
        try
        {
            TcpClient client = new TcpClient("127.0.0.1", 8888);

            NetworkStream stream = client.GetStream();

            XmlSerializer serializer = new XmlSerializer(typeof(EmailRequest));

            EmailRequest request = new EmailRequest
            {
                BodyType = EmailRequest.Type.Plain,
                HTMLText = "not used",
                PlainText = "Email Body",
                Recipients = new List<string> {"johnsmith@example.com"},
                Subject = "Email Subject"
            };

            serializer.Serialize(stream,request);

            Byte[] data = new Byte[256];

            Int32 bytes = stream.Read(data, 0, data.Length);
            string responseData = Encoding.ASCII.GetString(data, 0, bytes);
            Console.WriteLine("Received: {0}", responseData);
            Console.ReadLine();

        }
        catch (Exception e)
        {
            Console.WriteLine(e.StackTrace + "\n\n");
            Console.WriteLine(e.Message);
            Console.ReadLine();
        }
    }
}

EmailRequest Model

[Serializable]
public class EmailRequest
{
    public enum Type
    {
        Plain,
        HTML
    }
    public List<string> Recipients { get; set; }
    public string Subject { get; set; }
    public Type BodyType { get; set; }
    public string PlainText { get; set; }
    public string HTMLText { get; set; }
}

When the program reaches the Deserialize method, the application doesn't hang, but waits, as though it's expecting user-input, and I have no idea why. I've got no experience with TCP/XmlSerialization/Streams apart from what I've done today. Any help or suggestions as to how I could improve the program would, as always, be much appreciated. Thanks.

The problem here is that the XmlSerializer has no way to know that there is no more data to deserialize, because the client has failed to shutdown the socket.

After the call to serializer.Serialize(stream,request); , you will need to add this line of code:

client.Client.Shutdown(SocketShutdown.Send);

This will indicate the end-of-stream from the client side, so that the server knows that the transmission of data has completed. This allows the XmlSerializer to know there's nothing more to try to deserialize and that it can return the new object.

In addition, the server never calls Shutdown() either. You can get away with it with the code you posted, because your client doesn't check for the end-of-stream when receiving.

While that will probably work nearly all of the time, it's not quite correct. Since you don't check the length of the receive value, you have no way to know that the client has received all of the data that the server has sent in response.

Instead, you should first fix the server to call Shutdown() , by adding these statements after you write the response to the stream:

client.Client.Shutdown(SocketShutdown.Both);
client.Close();

Then, you also need to fix the client side…

A simpler and more reliable way for the client to deal with processing the response would be to replace everything after the call to Shutdown() with this:

using (StreamReader reader = new StreamReader(stream))
{
    string line;

    while ((line = reader.ReadLine()) != null)
    {
        Console.WriteLine("Received: " + line);
    }
}

This allows you to not worry about the underlying stream I/O directly, instead delegating that to the StreamReader class, which can process the data in a more convenient way (ie it automatically buffers the data and correct detects the end of the stream).

Finally, after receiving the remaining data (per above), you should also close the client socket:

client.Close();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM