【问题标题】:Identify an incoming file through NetworkStream C#通过 NetworkStream C# 识别传入文件
【发布时间】:2015-05-24 16:31:30
【问题描述】:

我正在使用 TcpClient 和 TcpListener 类制作客户端/服务器聊天应用程序。它应该能够在客户端和服务器之间传输文本消息和文件。我可以通过为每个单独的客户端创建一个线程,然后创建一个辅助线程来接收传入消息,从而使主线程保留用于发送消息,从而处理文本消息。现在,如果我能够识别传入的消息是文件而不是文本消息,那么我知道如何使用 NetworkStream 和 FileStream 来处理它。但我无法这样做。处理传入文件的代码是here。另外请告诉我使用 NetworkStream for FTP 是否有任何限制。

【问题讨论】:

  • 听起来你应该将消息类型放入协议中,基本上......每个消息都有一个标头,指定消息长度(以字节为单位 - 不是文本消息的字符)和类型。跨度>
  • 我不知道如何指定消息类型。 NetworkStream.Read 和 NetworkStream.Write 是否有任何过载可以做到这一点?其实我是网络新手,请帮我弄清楚。 @JonSkeet
  • 不,您必须设计协议以包含此信息。这不是特定于网络流或 .NET - 这是设计数据格式的问题。
  • 好的,我明白了。就像我必须附加一些标记来告诉接收器方法它是一个文件。对吗?
  • 好吧,我会 prepend 它而不是附加它 - 并且无论哪种方式都有一个标记,理想情况下说明消息有多长,这样你就知道在下一个标记。

标签: c# ftp tcpclient tcplistener networkstream


【解决方案1】:

答案:构建您自己的协议。

通过构建自己的良好通信协议,您可以控制所有数据/消息流。

例如;
1-用户想向服务器发送文件
2-客户端发送命令通知服务器它将发送一个文件.like ;
@File@filename;filesize;
3-服务器向客户端发送就绪消息@FileAccepted@ 4-服务器开始监听缓冲包,并在收到时将它们写入流。
5-当客户端收到{@FileAccepted@} 命令时开始向服务器发送包。确保它们的缓冲区大小相同。
6-当所有字节完成时,客户端在不同的缓冲区中发送@FileEnd@(我使用 256 用于命令,1024 用于文件传输)
7-当服务器接收到 256 字节命令时,查看其 @FileEnd@ 命令是否为真,刷新文件流并关闭连接。

我建议你使用异步

像这样监听服务器上的连接

server.BeginAcceptTcpClient(ServerAcceptEnd,server);

当存在连接时

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();
    }
 }

收到第一个命令时;

    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();
        }
    }

文件接收方法;

    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();
            }
        }
    }

这部分是服务器端。而对于客户端;

发送文件时,先向服务器发送文件信息,然后等待服务器响应。

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
}

如果连接成功,则发送文件命令并等待响应;

    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);

            }
        }
    }

收到响应时查看是否成功接受;

    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);
                }
            }
        }
    }

发送文件分为三个步骤;
1-读取一个块开始
2-然后将块发送到服务器。如果完成发送@FileEnd@命令并跳过步骤3 3-读取下一个文件块
4-如果文件未完成则返回第2步

第 1 步:

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

第 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);

                }
            }
        }
    }

第三步:

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

在上面的示例中,有两个方法称为 toString() 和 toByte()。我用它们将字符串转换为字节,将字节转换为字符串。这是它们;

    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;
    }

示例上面的代码并不完美,需要大量的错误处理和流程控制。我写这些代码是为了给你一个想法和一个快速的开始。

希望它的任何部分对任何人都有帮助^_^

【讨论】:

    猜你喜欢
    • 2012-03-26
    • 1970-01-01
    • 2023-03-07
    • 2018-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-09
    相关资源
    最近更新 更多