Search code examples
c#tcpnetwork-programmingtcpclient

C# Program filling NetworkStream with 0's for an entire 1023 bytes, with first byte correct


Currently I am writing a C# program meant to satisfy a Computer Networks class project, where the program can be written in any language (I have chosen C#) and must perform some sort of networked multimedia process. It can be peer to peer or client-server based.

I have chosen to make my application Peer to Peer. Clients use UDP to broadcast their connect/disconnect to other clients on the local network, and then use TCP to send and receive .WAV files between each other.

Upon a connection request, the client will accept the TCP connection and begin reading data from the sending client.

After the data has all been read, the client may then play the .WAV file.

I have currently been trying to fix a bug in my program that required a .WAV file be sent twice instead of once, for the receiver to get the file and play it.

This has caused me to run into a problem I ran into before: Wave header is corrupt.

Before, I ran into this issue because on playing of the .WAV files, I was not setting the stream position to 0. This fixed it the first time.

However, the cause is now much more interesting because I am trying to fix a different bug.

The cause of it this time is that the data in the .WAV file stream is split. At the beginning, the very first byte of data is correct (verified by using a hexeditor to look at the bytes in the .WAV file I'm trying to send/receive) and then the stream is filled with 1023 bytes of 0's. After that, the rest of the .WAV file's data is available with no splits.

(Another send/recv trial revealed that the first byte was again correct, but the next 1023 bytes were out of sync and misplaced. The next 1023 bytes were not 0x00, but instead were from another piece of the file that was misplaced.)

I have no idea what's causing this and have taken to Wireshark to try and figure out what the issue is. I took a wireshark capture on the receiving end and found that the very first packet sent has only 1 byte of data: 0x52.

The next packet had 1460 bytes. There was no split of 0x00 after those 1460 bytes in the NetworkStream.

Provided below is my program's code. It is also available on Github at https://github.com/cparks1/ECE369ProjB-MultimediaGUI

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Net.Sockets; // For network programming
using System.Net; // For network programming
using System.Media; // For playing .WAV files
using System.IO; // For playing .WAV files (SoundPlayer in System.Media accepts either a filepath or an IO stream.)

namespace ProjectBMultimediaGUI
{

public partial class Form1 : Form
{
    private delegate void ObjectDelegate(string msg, IPEndPoint sender);
    private delegate void LabelChanger(string msg);

    const int PUBLISH_PORT_NUMBER = 8030; // Port number used for publish (UDP communications)
    const int TCP_PORT_NUMBER = 8031; // Port number used for the rest of communications (TCP communications)
    IPAddress me = GetLocalIP(); // me is the IPAddress that your machine currently owns on the local network

    const string CLIENT_ANNOUNCE = "[ECE 369] Multimedia client publish"; // UDP datagram to be sent when the client is announcing itself
    const string CLIENT_DISCONNECT = "[ECE 369] Multimedia client disconnect"; // UDP datagram to be sent when the client is announcing that it is disconnecting

    UdpClient pub = new UdpClient(PUBLISH_PORT_NUMBER, AddressFamily.InterNetwork); // Creates a new UDP client capable of communicating on a network on port defined by const, via IPv4 addressing
    IPEndPoint UDP_BROADCAST = new IPEndPoint(IPAddress.Broadcast, PUBLISH_PORT_NUMBER); // Broadcast address and port

    TcpListener tlisten = new TcpListener(IPAddress.Any,TCP_PORT_NUMBER); // Sets up a listener that looks for TCP connections
    TcpClient tcprecvr;

    Stream wavstream = new MemoryStream(); // Initializes a memory stream that will hold .wav file data when being written to. Will be reinitialized in packet receiving functions

    Timer tmr = new Timer(); // Timer used to announce client on the local network every 250 ms

    SoundPlayer splayer;

    bool isClosing = false; // Used to determine if program is closing

    byte[] readbuf = new byte[1024];
    long wavpos = 0;

    public Form1()
    {
        InitializeComponent();
        #region Timer Setup
        tmr.Interval = 250;
        tmr.Start();
        tmr.Tick += new EventHandler(tmr_Tick);
        #endregion
    }

    private void tmr_Tick(object sender, EventArgs e)
    {
        if (!isClosing)
            Announce_Client_Connect(); // Announce the client is connected every 250 ms
    }

    private void playpauseBUT_MouseClick(object sender, MouseEventArgs e)
    {
        Button sbut = (sender as Button);
        splayer = new SoundPlayer(wavstream);
        if (sbut.ImageIndex == 0) // If the PLAY button was clicked
        { // Initiate play functions
            if (wavstream.CanRead && wavstream.Length > 0)
            {
                splayer.Stream.Position = 0;
                splayer.Play();
            }
            sbut.ImageIndex = 1; // Change the button to show PAUSE
        }
        else // If the PAUSE button was clicked
        {
            splayer.Stop();
            sbut.ImageIndex = 0; // Change the button to show PLAY
        }
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        butIML.ImageSize = new Size(playpauseBUT.Size.Width-1,playpauseBUT.Size.Height-1); // This ensures the play and pause buttons are always the same size as the button they are encased in
        stopBUT.Size = playpauseBUT.Size; // Ensures the stop button is the same size as the play/pause button.
        stopBUT.Location = new Point(stopBUT.Location.X, playpauseBUT.Location.Y);

        pub.AllowNatTraversal(false); // Disables the ability for the program to communicate with the outside world, for security purposes
        try { pub.BeginReceive(new AsyncCallback(RecvPub), null); }
        catch(Exception err) { MessageBox.Show("An error occurred!\n"+err.ToString()); Application.Exit(); }

        tlisten.AllowNatTraversal(false);
        BeginListening(); // Begins listening for attempts to connect to the client via TCP
        Announce_Client_Connect();
    }

    private void BeginListening()
    {
        tlisten.Start();
        tlisten.BeginAcceptTcpClient(new AsyncCallback(RecvTcp), null);
    }

    private void stopBUT_MouseClick(object sender, MouseEventArgs e)
    {
        splayer.Stop();
        playpauseBUT.ImageIndex = 0; // Change the play/pause button to show PLAY
    }

    private void mainMS_Paint(object sender, PaintEventArgs e)
    {
        e.Graphics.DrawLine(new Pen(Color.Black), mainMS.Left, mainMS.Bottom-1, mainMS.Right, mainMS.Bottom-1); // Draws a border on the bottom of the main menu strip.
        e.Graphics.DrawLine(new Pen(Color.Black), mainMS.Left, mainMS.Top, mainMS.Right, mainMS.Top); // Draws a border on the top of the menu strip.
    }

    private void RecvPub(IAsyncResult res) // Function used to handle received UDP messages
    {
        IPEndPoint recv = new IPEndPoint(IPAddress.Any, PUBLISH_PORT_NUMBER);
        byte[] message = null;
        string dmessage;
        if (!isClosing)
            message = pub.EndReceive(res, ref recv);

        if(message != null) // If a message was received
        {
            ObjectDelegate del = new ObjectDelegate(HandleUDPDatagram);
            dmessage = Encoding.ASCII.GetString(message);
            del.Invoke(dmessage, recv);
            HandleUDPDatagram(dmessage, recv);
        }

        if (!isClosing)
            pub.BeginReceive(new AsyncCallback(RecvPub), null);
    }

    private void RecvTcp(IAsyncResult res)
    {
        LabelChanger lblchgr = new LabelChanger(dataavailable); // Used for cross thread operations on dataavailLBL

        wavstream = new MemoryStream(); // Clear the wav stream, we don't want two wav files in one stream, it would cause errors.

        lblchgr.Invoke("Data unavailable."); // No data available yet.

        //tcprecvr = tlisten.AcceptTcpClient(); // Accept the incoming TCP connection.
        tcprecvr = tlisten.EndAcceptTcpClient(res); // Create a new TCP connection with the requester
        NetworkStream stream = tcprecvr.GetStream(); // Get the TCP network stream
        if (stream.CanRead)
        {
            while (!stream.DataAvailable) ;
            stream.BeginRead(readbuf, 0, readbuf.Length, new AsyncCallback(RecvTCPData), null);
        }
        else
        {
            tcprecvr.Close();
            MessageBox.Show("An error has occurred. Unable to read incoming TCP stream.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
        }
    }

    private void RecvTCPData(IAsyncResult res)
    {
        LabelChanger lblchgr = new LabelChanger(dataavailable); // Used for cross thread operations on dataavailLBL

        wavpos = 0; // Reset the WAV stream position

        NetworkStream stream = tcprecvr.GetStream(); // Get the TCP data stream
        int nbytes = stream.EndRead(res); // Get the number of bytes read
        if (nbytes == 0) // Finished reading
        {
            tcprecvr.Close(); // Close the TCP connection
            lblchgr.Invoke("Data available!"); // Inform the user there is a .WAV file to be played
            BeginListening(); // Begin listening to connection requests again
            //return;
        }
        else // Not finished reading, data in buffer
        {
            wavstream.Write(readbuf, 0, readbuf.Length); // Write WAV data to wav stream
            stream.BeginRead(readbuf, 0, readbuf.Length, new AsyncCallback(RecvTCPData), null); // begin read again
        }
    }

    private void dataavailable(string msg)
    {
        if(InvokeRequired)
        {
            LabelChanger method = new LabelChanger(dataavailable);
            Invoke(method, msg);
            return;
        }
        dataavailLBL.Text = "Data available!";
    }

    private void HandleUDPDatagram(string msg, IPEndPoint sender) // Used for handling UDP messages
    {
        if (!sender.Address.Equals(me)) // Verifies the UDP datagram received isn't from your own machine.
        { //This is done because some UDP datagrams are sent to the broadcast address, which means we receive what we've sent. We obviously don't want packets from ourselves so we block them.
            if (InvokeRequired && !isClosing) // Used for handling thread magic. Please don't ask me to explain this.
            {
                ObjectDelegate method = new ObjectDelegate(HandleUDPDatagram);
                Invoke(method, msg, sender);
                return;
            }

            switch (msg)
            {
                case CLIENT_ANNOUNCE: // If we've received a client connection message
                    if (!hostsLB.Items.Contains(sender.Address)) // If the client is not already in the list box
                        hostsLB.Items.Add(sender.Address); // Add the client to the listbox of clients
                    break;
                case CLIENT_DISCONNECT: // If we've received a client disconnection message
                    if (hostsLB.Items.Contains(sender.Address)) // If the client is in the listbox
                        hostsLB.Items.Remove(sender.Address); // Remove the client from the listbox of clients
                    break;
            }
        }
    }

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        isClosing = true;
        Announce_Client_Disconnect(); // Declare your disconnection to the other clients on the network
    }

    private void Announce_Client_Disconnect()
    {
        byte[] dgram = Encoding.ASCII.GetBytes(CLIENT_DISCONNECT); // Encode the client disconnection message into an array of bytes
        pub.Send(dgram, dgram.Length, UDP_BROADCAST); // Send the disconnection message off to all clients on the network via the broadcast address
    }

    private void Announce_Client_Connect()
    {
        byte[] dgram = Encoding.ASCII.GetBytes(CLIENT_ANNOUNCE); // Encode the client connection message into an array of bytes
        pub.Send(dgram, dgram.Length, UDP_BROADCAST); // Send the connection message off to all clients on the network via the broadcast address
    }

    private void OpenBrowse()
    { // This function allows the user to browse for a file and then sets the file path textbox with the path of the file.
        OpenFileDialog fopen = new OpenFileDialog();
        fopen.CheckFileExists = true; fopen.CheckPathExists = true; fopen.Filter = "WAV Files|*.wav";
        fopen.ShowDialog();
        filepathTB.Text = fopen.FileName;
    }

    private void sendwavBUT_MouseClick(object sender, MouseEventArgs e)
    {
        if (hostsLB.Items.Count > 0 && hostsLB.SelectedItem != null)
        {
            TcpClient tcpsender = new TcpClient((hostsLB.SelectedItem as IPAddress).ToString(),TCP_PORT_NUMBER); // Connect to the client

            sendwavBUT.Enabled = false; filesendPB.UseWaitCursor = true;
            try
            {
                //byte[] buf = new byte[1024]; // !! TRIAL !!
                FileStream fs = new FileStream(filepathTB.Text, FileMode.Open);
                int dat = 0;
                while(fs.CanRead && fs.Position!=fs.Length)
                {
                    dat = fs.ReadByte();
                    if (dat != -1)
                    {
                        tcpsender.GetStream().WriteByte((byte)dat);
                        filesendPB.Value = (int)((fs.Position / fs.Length) * 100);
                    }
                }
                fs.Close(); // Release any resources used by the file stream.
            }
            catch(Exception err) { MessageBox.Show(err.ToString(),"Error",MessageBoxButtons.OK,MessageBoxIcon.Error); }

            MessageBox.Show("File send complete.");
            tcpsender.Close(); // Release any resources used by the TCP stream and close the TCP connection.
            sendwavBUT.Enabled = true; filesendPB.UseWaitCursor = false; // Re-enable the send button.
        }
        else if (hostsLB.Items.Count <= 0)
            MessageBox.Show("There are no clients to send this file to!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        else
            MessageBox.Show("You must select a client!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }

    static IPAddress GetLocalIP() // Has the machine report its local network IP address
    { // This code snippet has been taken from StackOverflow and adapted to return an IPAddress rather than a string
        IPHostEntry host;
        host = Dns.GetHostEntry(Dns.GetHostName());
        foreach (IPAddress ip in host.AddressList)
            if (ip.AddressFamily.ToString() == "InterNetwork")
                return ip;

        return null;
    }

    private void closeToolStripMenuItem_Click(object sender, EventArgs e)
    { Application.Exit(); }
    private void openToolStripMenuItem_Click(object sender, EventArgs e)
    { OpenBrowse(); }
    private void browseBUT_MouseClick(object sender, MouseEventArgs e)
    { OpenBrowse(); }
}
}

Provided also is a picture of the GUI: Picture of the Multimedia Application I'm working on.

Thank you for any input you may have, this issue has been driving me nuts! :)


Solution

  • Just looking at the stream reads/writes, it looks like you have a bug on this line:

    wavstream.Write(readbuf, 0, readbuf.Length); // Write WAV data to wav stream
    

    It should be:

    wavstream.Write(readbuf, 0, nbytes); // Write WAV data to wav stream