做一个传输图片的的scoket,遇到了各种各样的问题(附录中给出)

 

后来用一个比较稳定的框架后得到解决

网络框架服务端:

 

这里记录该网络框架的用法:


整个传输图片的流程:

打开客户端时  客户端中用新线程打开netinputwork方法监听服务端的信号。

在客户端发送图片时,用startsend方法创建文件流打开文件流,方法内调用了ProcSendMsg方法把图片大小和要在服务端要存的路径,图片保存的名字这三个信息发送给服务端,并用10信号说明是一张图片的首次发送。


向服务端发射10信号,服务端接到10信号后创建相应的文件夹及文件流,并返回11信号


客户端监听到11信号后

先判断当前图片位置加上4096位移是不是已经超过图片大小,如果是的话就说明这是一张图片的最后一次发送,把信号设为30,并发送最后的字节.服务端接到30信号后就知道这已经是一张图片的末尾了,处理完后对客户端发送21信号。客户端收到21信号后,把线程的堵塞打开,则开始传一下张图片 


如果不是最后一张图片的末尾 则继续发送 用20信号,服务端接受到20信号只做写入文件流的操作


服务端中的接收类:

FileServer.cs代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SANetworkEngine;
using System.IO;
using System.Runtime.InteropServices;

namespace FileTransServer
{
    public class FileServer
    {

        private ServerHost m_host;

        public void Start()
        {
            m_host = new ServerHost(true, CallbackThreadType.IOThread,
               new FileService(), 2048, 15000, 180000, 2000);

            
            m_host.AddListener(new System.Net.IPEndPoint(System.Net.IPAddress.Any,8500), 100, 3);

            try
            {
                m_host.Start();
                Console.WriteLine(DateTime.Now.ToString() + ">服务启动成功\r\n");
               
            }
            catch (Exception ex)
            {
                Console.WriteLine(DateTime.Now.ToString() + ">服务启动失败:" + ex.Message + "\r\n");
              
            }
        }
    }

    

    // 服务器处理逻辑
    public class FileService : BaseService
    {
        // private string save = AppDomain.CurrentDomain.BaseDirectory;
       private string save = @"D:\TEST";

        class FileClientInfo
        {
            public string FileName;
            public int FileSize;
            public int FileOffset;
            public string FileDir;
            public FileStream FS;
        }

        private Dictionary<int, FileClientInfo> fileList = new Dictionary<int, FileClientInfo>();


        public override void OnConnected(ConnectionEventArgs e)
        {
            Console.WriteLine("有客户上线:"+e.Connection.HashId);

            fileList.Add(e.Connection.HashId, new FileClientInfo());
            base.OnConnected(e);


        }

        public override void OnDisconnected(ConnectionEventArgs e)
        {
            base.OnDisconnected(e);
        }

        /// <summary>
        /// 服务器的逻辑处理
        /// 第1阶段设置的服务器 只包含主要的功能
        /// 1:验证登陆 2:接受发送的预约成功的消息并记录
        /// 下来,准备的得到信息就是钱
        /// 2:阶段添加更多的可能的控制功能
        /// </summary>
        /// <param name="e"></param>
        int count = 0;
        public override void OnReceived(MessageEventArgs e)
        {
            // 0xff, 0xaa, 0xff 
            if (e.Buffer.Length == 3 && e.Buffer[0] == 0xff && e.Buffer[1] == 0xaa && e.Buffer[2] == 0xff) // 心跳包不压缩
            {
                base.OnReceived(e);
                return; // 
            }

            try
            {
                byte[] info = CommonLib.Util.SecTool.SecDecode(e.Buffer);
                byte[] copyBuf=new byte[info.Length-1];
                int cmd = info[0];
                List<byte> rtData = new List<byte>();
               
                switch (cmd)
                {
                    case 10: // 获得文件信息

                        string data= Encoding.UTF8.GetString(info, 1, info.Length - 1);
                        string[] datasp = data.Split(new char[] { ',' });

                        fileList[e.Connection.HashId].FileName =datasp[0];
                        fileList[e.Connection.HashId].FileSize = int.Parse(datasp[1]);
                        fileList[e.Connection.HashId].FileDir = datasp[2];
                        fileList[e.Connection.HashId].FileOffset = 0;
                        string dir=Path.Combine(save,fileList[e.Connection.HashId].FileDir);

                        if(!Directory.Exists(dir))
                        {
                            try{
                                Directory.CreateDirectory(dir);
                            }catch{}
                        }

                        fileList[e.Connection.HashId].FS = new FileStream(Path.Combine(dir,
                            fileList[e.Connection.HashId].FileName), FileMode.Create);

                        rtData.Add((byte)11);
                        e.Connection.BeginSend(CommonLib.Util.SecTool.SecCode(rtData.ToArray()));
                        break;
                    case 20:
                        try
                        {
                            Buffer.BlockCopy(info, 1, copyBuf, 0, copyBuf.Length);
                            fileList[e.Connection.HashId].FS.Write(copyBuf,
                                0
                                , copyBuf.Length);

                            //fileList[e.Connection.HashId].FileOffset += copyBuf.Length;
                        }
                        catch 
                        
                        {
                            Console.Write("写入错误");
                        }

                       rtData.Add((byte)11);
                        e.Connection.BeginSend(CommonLib.Util.SecTool.SecCode(rtData.ToArray()));

                        break;
                    case 30:
                        try
                        {
                            Buffer.BlockCopy(info, 1, copyBuf, 0, copyBuf.Length);
                            fileList[e.Connection.HashId].FS.Write(copyBuf,
                                0
                                , copyBuf.Length);

                            //fileList[e.Connection.HashId].FileOffset += copyBuf.Length;
                        }
                        catch
                        {
                            Console.Write("写入错误");
                        }

                          rtData.Add((byte)21);
                        e.Connection.BeginSend(CommonLib.Util.SecTool.SecCode(rtData.ToArray()));

                        fileList[e.Connection.HashId].FS.Close();

                        count++;

                        Console.Write("传完一张\t" + DateTime.Now+"\t"+count+"\n");
                        break;

                }

            }
            catch
            {
                Console.Write("接受错误");
            }

            base.OnReceived(e);
        }

        public override void OnException(ExceptionEventArgs e)
        {
            if (e.Exception != null)
            {

            }
            base.OnException(e);
        }

        public override void OnSend(MessageEventArgs e)
        {
            base.OnSend(e);
        }
    }


}

 

 

 

 

根据服务端的需求的操作  只需要修改 服务端的逻辑处理中的 代码就行了  这里实现的 是 接收客户端发送的信息    把图片写入 相应的 文件夹。

 

 

 

 

客户端要引入服务端中的两个project-------CommonLib和SANetworkEngine

然后新建一个发送类


客户端中的发送类:

using System.Threading;
using Import_Advtisement;
using System.IO;
using System.Collections.Generic;
using System.Text;



public class FileClient: SANetworkEngine.BaseDisposable
    {
        static FileClient m_inst;
        private string m_connectIP;

        public string ConnectIP
        {
            get { return m_connectIP; }
            set { m_connectIP = value; }
        }
        private int m_connectPort;

        public int ConnectPort
        {
            get { return m_connectPort; }
            set { m_connectPort = value; }
        }

        private SANetworkEngine.SimpleClient m_net;

        public SANetworkEngine.SimpleClient Net
        {
            get { return m_net; }
            set { m_net = value; }
        }

        Thread m_netInputWork;
        bool m_netInputFlag;

        private Form1 m_uiHandler;

        public Form1 UiHandler
        {
            get { return m_uiHandler; }
            set { m_uiHandler = value; }
        }


        private FileClient()
        {
            m_netInputWork = new Thread(new ThreadStart(NetInputWork));
            m_netInputWork.IsBackground = true;
        }

        public static FileClient Instance
        {
            get
            {
                if (m_inst == null)
                {
                    lock (typeof(FileClient))
                    {
                        if (m_inst == null)
                        {
                            m_inst = new FileClient();
                        }
                    }
                }

                return m_inst;
            }
        }
        private void NetInputWork()
        {

            while (m_netInputFlag)
            {
                // 目前只有一个回应消息处理
                byte[] info = m_net.Read();
                if (info != null)
                {
                    try
                    {
                        // 读一个字节的指令
                        // 解码
                        info = CommonLib.Util.SecTool.SecDecode(info);
                        int cmd = info[0];
                        int tran_size=4096;
                        byte cmdd = 20;
                     
                        switch (cmd)
                        {
                           
                            case 11:

                                if (file_offer + 4096 >= file_size)
                                {
                                   tran_size = file_size - file_offer;

                                    cmdd = 30;



                                }
                                byte[] buffer = new byte[tran_size];
                                file_stream.Read(buffer, 0,tran_size);

                                file_offer += tran_size;

                                ProcSendMsg1(cmdd,buffer);
                           

                                break;
                            case 21:
                               
                                file_stream.Close();
                                reset_event.Set();
                                break;
                        }
                    }
                    catch
                    {
                        int a = 4;

                    }
                }
                else if (m_net.LastError != null)
                {
                    int a = 4;
                }
                else
                {
                    int a = 4;
                }
                Thread.Sleep(10);
            }
        }

        private void ProcSendMsg1(byte cmd, byte[] data_pic)
        {
            List<byte> data = new List<byte>();
            data.Add(cmd);
            data.AddRange(data_pic);
            byte[] msg = data.ToArray();
            msg = CommonLib.Util.SecTool.SecCode(msg);

            m_net.Write(msg); // 发送网络信息
        }


        /// <summary>
        /// 启动网络获取器
        /// </summary>
        public void StartNetGet()
        {
            m_netInputFlag = true;
            m_netInputWork.Start();
        }

        public void ResetClient()
        {
            Free(true);
            m_netInputWork = new Thread(new ThreadStart(NetInputWork));
            m_netInputWork.IsBackground = true;
        }

        protected override void Free(bool canAccessFinalizable)
        {
            m_net.Disconnect();
            m_net.Dispose();   // 释放网络资源
            m_netInputFlag = false;
            //try
            //{
                // m_netInputWork.Abort();
            //}
            //catch { }
            base.Free(canAccessFinalizable);
        }

        int file_offer;
        int file_size;
        FileStream file_stream;
        public  void StartSend(string strFileName, string strDictory, string strSaveFileName)
        {
            //创建一个文件对象  

            file_offer = 0;
           
            FileInfo EzoneFile = new FileInfo(strFileName);
            //打开文件流  
          file_stream = EzoneFile.OpenRead();

            file_size = (int)file_stream.Length;

            ProcSendMsg(10, strSaveFileName+","+file_size+","+strDictory);

            reset_event.WaitOne();

        }

        private AutoResetEvent reset_event=new AutoResetEvent(false); //false为激活状态
       



        // 就采用一个字节作为指令有0-255 种
        private void ProcSendMsg(byte cmd, string info)
        {
            List<byte> data = new List<byte>();
            data.Add(cmd);
            data.AddRange(Encoding.UTF8.GetBytes(info));
            byte[] msg = data.ToArray();
            msg = CommonLib.Util.SecTool.SecCode(msg);

            m_net.Write(msg); // 发送网络信息
        }

        //public void SendCodeBitmap(string codemark, byte[] imgData)
        //{
        //    List<byte> data = new List<byte>();
        //    data.Add((byte)80);
        //    byte[] mark = Encoding.UTF8.GetBytes(codemark);
        //    short marklen = (short)mark.Length;
        //    data.AddRange(BitConverter.GetBytes(marklen));
        //    data.AddRange(mark);
        //    data.AddRange(imgData);
        //    byte[] msg = data.ToArray();
        //    msg = CommonLib.Util.SecTool.SecCode(msg);
        //    m_net.Write(msg); // 发送网络信息

        //}
       
         登陆信息发送
        //public void SendLoginInfo(string id, string logname,string proxysn)
        //{
        //    ProcSendMsg((byte)10, id + "," + logname +  "," + proxysn);
        //}

        //public void SendAcctInfo(string pid, string pwd, string proxysn)
        //{
        //    ProcSendMsg((byte)20, pid + "," + pwd + "," + proxysn);
        //}

        //public void SendSuccessInfo(string pid, string proxysn)
        //{
        //    ProcSendMsg((byte)30, pid + "," + proxysn);
        //}

    }

 

 客户端要修改操作时,主要是修改NetInputWork() ,Startsend() 还有ProSendMsg()  ProSendMsg1()这几个函数。

 

在需要发送的地方调用发送类中的方法:

FileClient.Instance.StartSend(directoryName + "\\" + dt.Rows[i]["月"].ToString() + "." + dt.Rows[i]["日"].ToString() + "\\" + dt.Rows[i]["媒体"].ToString() + "\\" + sValue, Convert.ToDateTime(dt.Rows[i]["发布日期"].ToString()).ToString("yyyy-MM"), fileName + sValue.Substring(sValue.LastIndexOf(".")));

 

 

 在主窗体Form的登录函数添加连接:

 Form1_Load(object sender, EventArgs e) 函数的相应位置添加:

using System.Net;
using System.Net.Sockets;




FileClient.Instance.UiHandler = this;
                FileClient.Instance.Net = new SANetworkEngine.SimpleClient(true, SANetworkEngine.CallbackThreadType.IOThread,

                 new IPEndPoint(IPAddress.Parse("192.168.0.79"), 8500), 2048, 3, 3000, true, 15000);
                         //  new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8500), 2048, 3, 3000, true, 15000);
                FileClient.Instance.Net.OnHeart += new EventHandler<SANetworkEngine.HeartEvent>(Net_OnHeart);
                FileClient.Instance.Net.OnException += new EventHandler<SANetworkEngine.ClientExceptionEvent>(Net_OnException);
                FileClient.Instance.Net.OnDisconnect += new EventHandler(Net_OnDisconnect);


                FileClient.Instance.Net.ConnectServer();
                if (FileClient.Instance.Net.Connected)
                {

                    FileClient.Instance.StartNetGet();


                }
                else
                {

                    MessageBox.Show("无法连接");
                }

 

 

 并且增加以下函数:

void Net_OnDisconnect(object sender, EventArgs e)
        {
            FileClient.Instance.ResetClient();
            FileClient.Instance.Net = new SANetworkEngine.SimpleClient(true, SANetworkEngine.CallbackThreadType.IOThread,

                         new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8500), 2048, 3, 3000, true, 15000);

            FileClient.Instance.Net.OnHeart += new EventHandler<SANetworkEngine.HeartEvent>(Net_OnHeart);
            FileClient.Instance.Net.OnException += new EventHandler<SANetworkEngine.ClientExceptionEvent>(Net_OnException);
            FileClient.Instance.Net.OnDisconnect += new EventHandler(Net_OnDisconnect);


            FileClient.Instance.Net.ConnectServer();
            if (FileClient.Instance.Net.Connected)
            {

                FileClient.Instance.StartNetGet();


            }
            else
            {


            }


        }

        void Net_OnException(object sender, SANetworkEngine.ClientExceptionEvent e)
        {
            throw new NotImplementedException();
        }

        private byte[] heartdata = new byte[] { 0xff, 0xaa, 0xff };
        void Net_OnHeart(object sender, SANetworkEngine.HeartEvent e)
        {
            e.HeartData = heartdata;
        }

 

附录:

现象:服务端打开但是过了一会就关闭了一个连接,或者远程主机关闭了一个连接

原因1:
接收的
监听的队列不够

 

解决方法:
1.增加监听的队列
2.把监听放到新线程中

 

原因2:

服务端在接受数据时,客户端突然关闭或者 因为网络原因连接关闭

解决方法:
接收数据应该放在try catch中,使其不影响到下面的传输 ,不应作为一个异常处理

解决方法3:
用阻塞单线程,一个传完了再运行另一个(框架中的方法)

 

 

 

 


现象:接受完后有些文件或者图片不全


 


原因:


Arithmetic operation resulted in an overflo   scoket发生算术溢出或者算术异常


 soket缓冲区只有8k字节,当每次发送的字节太多而网速又不够时 就会发生上面这两种错误


 



解决方法有两种


1.将发送后的线程等待时间 thread.sleep设得长一点.



2.比较安全的方法:不要一次把整个文件发送,而是分段发送,而且每段的字节不超过8字节 也就是 8x1024 byte (一般设置为2x1024byte或者 4x1024byte)