Search code examples
c#ftptcpclienttcplistenernetworkstream

Identify an incoming file through NetworkStream C#


I am making a client/server chat application using TcpClient and TcpListener classes. It should be able to transfer text messages and files between client and server. I am able to handle text messages by making a thread for each separate client and then making a secondary thread for receiving incoming message making primary thread reserved for sending messages. Now if I would be able to identify that incoming message is a file and not a text message then I know how to handle it using NetworkStream and FileStream. But I am unable to do so. Code to handle incoming file is here. Also please tell me if there are any limitations using NetworkStream for FTP.


Solution

  • Answer: Build your own protocol.

    By building your own good communication protocol you can control all data/message flow.

    For example;
    1-User wants to send a file to server
    2-Client sends a command to inform the server that it will send a file.Like ;
    @File@filename;filesize;
    3-Server sends a ready message back to client @FileAccepted@ 4-Server begins to listen buffer packages and when it receives writes them to an stream.
    5-When client receives {@FileAccepted@} command begins to send packages to server. Be sure their buffer sizes are same.
    6-When all bytes complete client sends @FileEnd@ in diffrent buffer (i use 256 for commands and 1024 for file transfer)
    7-When server receives 256 byte command looks if its the @FileEnd@ command and is true flushes file stream and closes connection.

    I recomment you use Async

    Listen for connections on server like this

    server.BeginAcceptTcpClient(ServerAcceptEnd,server);
    

    And when a connection is present

    public void ServerAcceptEnd(IAsyncResult ar)
    {
        if(!ar.IsCompleted)
        {
            //Something went wrong
            AcceptServer();
            return;
        }
        try
        {
            var cli = servertc.EndAcceptTcpClient(ar);
            if(cli.Connected)
            {
                //Get the first Command   
                cli.GetStream().BeginRead(serverredbuffer,0,serverredbuffer.Length,ServerFirstReadEnd,cli);
            }
            else
            {
                //Connection was not successfull log and wait
                AcceptServer();
            }
        }
        catch(Exceiption ex)
        {
            //An error occur log and wait for new connections
            AcceptServer();
        }
     }
    

    When first command received ;

        public void ServerFirstReadEnd(IAsyncResult ar)
        {
        if(!ar.IsCompleted)
        {
            //Something went wrong
            AcceptServer();
            return;
        }
            try
            {
                TcpClient cli = (TcpClient)ar.AsyncState;
                int read = cli.GetStream().EndRead(ar);
                string req = toString(serverredbuffer);  
                if(req.StartsWith("@File@"))
                {
                    //File Received
                    string filename = req.Replace("@File@","");
                    string[] spp = filename.Split(';');
                    filename = spp[0];
                    serverreceivetotalbytes = Convert.ToInt64(spp[1]);
                    cli.GetStream().Write(toByte("@FileAccepted@",256),0,256);
                    cli.GetStream().BeginRead(serverreceivebuffer,0,1024,ServerReadFileCyle,cli)   
                }
                else
                {
                    //Message Received
                }
            }
            catch(Exception ex)
            {
                //An error occur log and wait for new connections
                AcceptServer();
            }
        }
    

    File receive method ;

        public void ServerReadFileCyle(IAsyncResult ar)
        {
            TcpClient cli = (TcpClient)ar.AsyncState;
            if(ar.IsCompleted)
            {
                int read = cli.GetStream().EndRead(ar);
                if(read == 256)
                {
                    try
                    {
                        string res = toString(serverreceivebuffer);
                        if(res == "@FileEnd@")
                            read = 0;
                    }
                    catch
                    {
                    }
                }
                if(read > 0)
                {
                    serverfile.Write(serverreceivebuffer,0,read);
                    cli.GetStream().BeginRead(serverreceivebuffer,0,1024,ServerReadFileCyle,cli);
                }
                else
                {
                    serverfile.Flush();
                    serverfile.Dispose();
                    AcceptServer();
                }
            }
        }
    

    This part was server side.And for client side;

    When sending a file first send a information to server for file and then wait for a response from server.

    try
    {
        System.Windows.Forms.OpenFileDialog ofd = new System.Windows.Forms.OpenFileDialog();
        ofd.Multiselect = false;
        ofd.FileName="";
        if(ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
        {
            filesendpath = ofd.FileName;
            senderfilestream = System.IO.File.Open(filesendpath,System.IO.FileMode.Open);
            sendertotalbytes = senderfilestream.Length;
            filesendcommand = "@File@" + System.IO.Path.GetFileName(filesendpath) + ";" + senderfilestream.Length;
            senderfilestream.Position = 0;
            sendertc.BeginConnect(ip.Address,55502,FileConnect,null);
        }
        else
        {
            //No file selected
        }
    
    }
    catch(Exception ex)
    {
        //Error connecting log the error
    }
    

    If connection is successfull , then send the file command and wait for response ;

        public void FileConnect(IAsyncResult ar)
        {
            if(ar.IsCompleted)
            {
                sender.EndConnect(ar);
                if(sender.Connected)
                {
                    sender.GetStream().Write(toByte(filesendcommand,256),0,256);
                    sender.GetStream().BeginRead(ComputerNameBuffer,0,256,FileSendCyleStarter,null);
    
                }
            }
        }
    

    When response received look if it is successfull an accepted;

        public void FileSendCyleStarter(IAsyncResult ar)
        {
            if(ar.IsCompleted)
            {
                if(sender.Connected)
                {
                    string kabul = toString(ComputerNameBuffer);
                    if(kabul == "@FileAccepted@")
                    {
                        senderfilestream.BeginRead(filesendbuffer,0,1024,FileSendCyle,null);
                    }
                }
            }
        }
    

    Sending a file has three steps;
    1-Read a chunk for a start
    2-Then send the chunk to server.if its completed send @FileEnd@ command and skip step 3 3-Read next chunk of file
    4-Return step 2 if file isnt completed

    Step 1 :

    senderfilestream.BeginRead(filesendbuffer,0,1024,FileSendCyle,null);
    

    Step 2-4 :

        public void FileSendCyle(IAsyncResult ar)
        {
            if(ar.IsCompleted)
            {
                if(sendertc.Connected)
                {
                    int  read = senderfilestream.EndRead(ar);
                    if(read > 0)
                    {
                        sendertc.GetStream().BeginWrite(filesendbuffer,0,read,FileSendCyle2,null);
    
                    }
                    else
                    {
    
                        sendertc.GetStream().Write(toByte("@FileEnd@",256),0,256);
    
                    }
                }
            }
        }
    

    Step 3 :

        public void FileSendCyle2(IAsyncResult ar)
        {
            if(ar.IsCompleted)
            {
                if(sendertc.Connected)
                {
                   sendertc.GetStream().EndWrite(ar);
                   senderfilestream.BeginRead(filesendbuffer,0,1024,FileSendCyle,null);
                }
            }
        }
    

    In abowe example there are two methods called toString() and toByte().I used them for converting strings to bytes and bytes to strings.Here are them ;

        public string toString(byte[] buffer)
        {
            return Encoding.UTF8.GetString(buffer).Replace("\0","");
        }
        public byte[] toByte(string str,int bufferlenght)
        {
            byte[] buffer = new byte[256];
            Encoding.UTF8.GetBytes(str,0,str.Length,buffer,0);
            return buffer;
        }
    

    The code abowe example isn't perfect and need lots of error handling and flow controls.I write theese to give you an idea and a jump start.

    Hope any part of it helps anybody ^_^