需求:一台服务器,三十台客户端,都处于广域网下(服务器位于公网上,客户端用3G无线)
服务器放在机房内,三十台客户端放在闹市区路边的各个点,用于展示。
客户端上展示的内容(内容为PDF格式的报纸,属于电子阅报性质)统一由服务器发送。
服务器放置服务器端程序,客户端放置客户端程序(三十个客户端放置同样的前台程序)。因为客户端设备位于闹市区路边,仅用于展示内容,所以属于无人维护性质。
所以我的设计思路是:
    三十个客户端程序不间断连接服务端,如果服务端没开,则继续尝试连接。当服务端程序运行并传送文件时,客户端连接成功,则接收文件(先判断此文件是否已完整接收过(因为有可能丢包,导致接收部分),如果完整接收过则不再接收)。在测试时,先弄的一台服务端对应一台客户端,出现情况如下:
运行服务端、客户端,服务端发送文件,客户端接收,再发送,再接收,无问题。
A、此时,关闭客户端,重新打开,服务端再发送时,显示远程关闭一个现有连接对话框,关闭此对话框重新发送时,ok。再选择文件,发送,也ok。以后如果再关闭客户端,重新打开,服务端仍然会显示那个啥远程关闭连接啥的。
B、此时,关闭服务器,重新打开,服务端发送文件,会有界面阻塞状态,但文件可以发送成功。一台服务端对应两台客户端时,情况为:
服务端和客户端程序运行后,服务端发送文件,界面卡死,约过几分钟这样,报告内存溢出异常(引发类型为System.OutOfMemoryException的异常),客户端无异常信息,且接收文件完整。
再次发送时,其中一个客户端接收完全,另一个只接收到部分,且报告内存溢出。客户端无异常信息。
请教各位大牛,下附代码:
服务端:using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;namespace FileServer
{ 
    public partial class ServerFrm : Form
    {
        private string fileName;
        private int size =66530000;        string ip = "120.209.97.225";
        int port = 34857;        public ServerFrm()
        {
            InitializeComponent();
            
            ServerFrm.CheckForIllegalCrossThreadCalls = false;            //监听本地端口
            tpclis = new TcpListener(IPAddress.Parse(ip), port);
            tpclis.Start();
        }        //选择文件
        private void button3_Click(object sender, EventArgs e)
        {
            //选择要发送的文件
            DialogResult result = openPath.ShowDialog();
            fileName = openPath.FileName;            this.textBox1.Text = fileName;            if(!string.IsNullOrEmpty(fileName))
            {
                this.button1.Enabled = true;
            }
        }        //发送按钮
        private void button1_Click(object sender, EventArgs e)
        {            //ThreadPool.QueueUserWorkItem(new WaitCallback(onStart));
            onStart(null);
            //onClientAccept(null);
        }        void onStart(Object unused)
        {
            // 服务器的循环, 一直监听, 来一个, 服务一个  
            // 靠的是启动客户端服务线程  
            while (true)
            {
                try
                {
                    TcpClient tcpClient = tpclis.AcceptTcpClient();
                    ThreadPool.QueueUserWorkItem(new WaitCallback(onClientAccept), tcpClient);//将方法排队以便执行
                }
                catch
                {
                    Thread.Sleep(100);
                }
            }
        }        /// <summary>
        /// 接收文件
        /// </summary>
        private TcpListener tpclis=null;
        private void onClientAccept(object acceptedClient)
        {
            TcpClient tc = null;
            NetworkStream ns = null;
            FileStream fsByte = null;
            try
            {
                //获取连接上的客户端对象
                //TcpClient tc = tpclis.AcceptTcpClient();
                tc = acceptedClient as TcpClient;
                //创建网络流
                ns = tc.GetStream();
                //创建一个文件流,并将打开的文件以字节流形式读入内存
                fsByte = new FileStream(fileName, FileMode.Open, FileAccess.Read,FileShare.Read);                //第一次发送:文件信息(文件名、文件长度),并返回此信息字节数组
                byte[] a = InsertFileSign(ns, fsByte);                int sCount = 0, curLen = 0;
                double c = 0.0, f = (double)fsByte.Length;                while (sCount < ((int)fsByte.Length) && ns.CanWrite)
                {
                    byte[] byts = new Byte[size];
                    //获取读取时的实际长度
                    curLen = fsByte.Read(byts, 0, byts.Length);
                   
                    //第二次发送:文件本身内容
                    ns.Write(byts, 0, curLen);                    //计算每次实际长度的总和
                    sCount = sCount + curLen;
                    c = sCount;                    //进度条:读取时实际长度/文件总长度
                    progressBar1.Value = Convert.ToInt32((c / f) * 100);
                    this.Refresh();
                }
                progressBar1.Value = 100;
                this.Refresh();
                //MessageBox.Show("文件发送成功!");
                this.listBox1.Items.Add("文件发送成功!");
                progressBar1.Value = 0;
            }
            catch (Exception ex)
            {
                MessageBox.Show("发送文件时异常:" + ex.Message);
            }
            finally
            { //关闭打开的流
                fsByte.Flush();
                fsByte.Close();
                ns.Flush();
                ns.Close();
                tc.Close();                this.Refresh();
            }
        }        /// <summary>
        /// 向传输的文件写入文件结构信息标记
        /// </summary>
        /// <param name="ns"></param>
        /// <param name="fsByte"></param>
        /// <returns></returns>
        private byte[] InsertFileSign(NetworkStream ns, FileStream fsByte)
        {
            int index = fileName.LastIndexOf('\\') + 1;
            string tempName = fileName.Substring(index, fileName.Length - index);////文件名
            //自定义编码方式#MName#文件名.扩展名#MLen#文件长度#End#"
            string sign1 = "#MName#" + tempName + "#MLen#" + fsByte.Length.ToString() + "#End#";
            byte[] a = StringTOByts(sign1);
            ns.Write(a, 0, a.Length);
            return a;
        }
        //将字符串转换为字节数组
        public byte[] StringTOByts(string str)
        {   //当字符向字节转换是2个字节表示一个字符
            byte[] byts = new Byte[1000];
            char[] chs = str.ToCharArray();
            for (int index = 0; index < chs.Length; index++)
            {   //取出字符的低8位并将二进制转换位十进制存入字节数组
                byts[index * 2] = (byte)(chs[index] & 0xFF);
                //用右移8位取出高8位并将二进制转换位十进制存入字节数组
                byts[index * 2 + 1] = (byte)((chs[index] >> 8) & 0xFF);
            }
            //剩余空间用0填充
            for (int i = (str.Length * 2); i < 1000; i++)
            {
                byts[i] = 0;
            }
            return byts;
        }        private void button2_Click(object sender, EventArgs e)
        {
            this.Close();
        }    }
}

解决方案 »

  1.   


    客户端:
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    using System.Net.Sockets;
    using System.IO;
    using System.Net;
    using System.Threading;
    namespace FileServer
    {
        public partial class ClientFrm : Form
        {
            //客户端保存服务器发送的文件目录
            string paths = "D://";        private int size =66530000;        string ip = "120.209.97.225";
            int port = 34857;        public ClientFrm()
            {
                InitializeComponent();
                
                //创建接收文件线程
                Thread thread = new Thread(new ThreadStart(FileReceive));
                Control.CheckForIllegalCrossThreadCalls = false;
                thread.IsBackground = true;
                thread.Start();
            }
            //接收文件
            private void btnSendFile_Click(object sender, EventArgs e)
            {
                
            }
            /// <summary>
            /// 负责向服务器发送文件
            /// </summary>
            private void FileReceive()
            {
                while (true)
                { 
                    try
                    {
                        //tctClient对象可指定主机名和端口
                        TcpClient tc = new TcpClient(ip, port);
                        //创建网络流
                        NetworkStream ns = tc.GetStream();                    //接收文件,长度、名称
                        int MLen;
                        string Mname;                    GetFileSignInfo(ns, out MLen, out Mname);
                        //检查路径如果不存在就创建它
                        bool fag = Directory.Exists(paths);
                        if (!fag)
                            Directory.CreateDirectory(paths);                    //获取已经接受过的文件名
                        string tempName = ReadReceivedFile();
                        if (Mname != tempName)
                        {                          //创建文件
                            FileStream fsWriteFile = new FileStream(paths + @"\" + Mname, FileMode.OpenOrCreate);
                            int sCount = 0, curLen = 0;
                            double c = 0.0, f = MLen;
                            while (sCount < MLen && ns.CanRead)
                            {
                                byte[] file = new Byte[size];
                                curLen = ns.Read(file, 0, file.Length);
                                fsWriteFile.Write(file, 0, curLen);
                                sCount = sCount + curLen;
                                c = sCount;
                                progressBar1.Value = Convert.ToInt32((c / f) * 100);
                            }
                            progressBar1.Value = 100;
                            fsWriteFile.Flush();
                            fsWriteFile.Close();
                            ns.Flush();
                            ns.Close(); 
                            
                            this.textBox1.Text = "文件已收到!";                        //记录接收文件
                            WriteReceivedFile(Mname);                        progressBar1.Value = 0;
                        }
                    }
                    catch (Exception ex)
                    {
                        this.textBox1.Text = ex.Message;
                        continue;
                    }
                    finally { }
                }
            }
            string path = "D:\\ReceivedFile.txt";
            private string ReadReceivedFile()
            {
                FileStream _fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Read);
                StreamReader _streamReader = new StreamReader(_fileStream);            string receivedname = _streamReader.ReadLine();            _streamReader.Close();
                _fileStream.Close();            return receivedname;
            }
            private void WriteReceivedFile(string content)
            {
                FileStream _fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
                StreamWriter _streamWriter = new StreamWriter(_fileStream);
                _streamWriter.WriteLine(content);
                _streamWriter.Close();
                _fileStream.Close();
            }
            /// <summary>
            /// 获得文件标记信息
            /// </summary>
            /// <param name="ns"></param>
            /// <param name="bytes"></param>
            /// <param name="MLen"></param>
            /// <param name="Mname"></param>
            private void GetFileSignInfo(NetworkStream ns, out int MLen, out string Mname)
            {
                byte[] a = new Byte[1000];
                ns.Read(a, 0, 1000);
                string str = BytsTOString(a, 1000);
                //解析规则"#98#MName#光良 - 童话.mp3#MLen#244582#End#";
                int ln1 = str.IndexOf("#MName#", 0);
                int ln2 = str.IndexOf("#MLen#", ln1 + 6);
                int ln3 = str.IndexOf("#End#", ln2 + 5);
                //获得文件参数
                MLen = int.Parse(str.Substring(ln2 + 6, ln3 - ln2 - 6));//获得歌曲长度
                Mname = str.Substring(ln1 + 7, ln2 - ln1 - 7);//获得歌曲名及扩展名
            } 
            
            //将字节型数据转换成字符串
            public string BytsTOString(byte[] byts, int len)
            {
                string str = ""; char ch;
                for (int index = 0; index < len / 2; index++)
                {
                    ch = (char)(byts[index * 2] + (byts[index * 2 + 1] << 8));
                    str += ch;
                }            return str;
            }      
      
            private void btnCance_Click(object sender, EventArgs e)
            {
                this.Close();
            }
        }
    }
      

  2.   

    一台服务端对应两台客户端时的异常信息为服务端的onClientAccept(object acceptedClient)方法的while循环处导致。
      

  3.   

     //发送按钮
            private void button1_Click(object sender, EventArgs e)
            {            //ThreadPool.QueueUserWorkItem(new WaitCallback(onStart));
                onStart(null);//一对多时调用
                //onClientAccept(null);//一对一时调用
            }
      

  4.   

    你还是要看一下socket方面的东西,你不可能把一个文件用stream一次推送到客户端,要分解成数据包。里面小细节还是很多。
      

  5.   

    并不是一次性吧。服务端先发送文件名以及文件大小,后来的while循环也是根据文件大小进行动态判断是否需要分次发送,如果大的话,它会进行几次循环才能发送完。
      

  6.   

    这个场景   web 更合适些吧。为毛要传pdf,再展示。