zoukankan      html  css  js  c++  java
  • C# 异步Socket

    C# 异步Socket (BeginXXXX)服务器代码

    前言:

    1、最近维护公司的一个旧项目,是Socket通讯的,主要用于接收IPC(客户端)发送上来的抓拍图像,期间要保持通讯,监测数据包并进行处理。但是看之前那人写的代码个人觉得并不是很适合自己,于是就重写了,不过项目暂时弃置了,为了以后能够方便使用,也方便更多像我一样还是渣渣程序员的人,记录一些心得。我还是坚信那句话,分享才能够进步得更快

    2、其实在做之前我对这个东西了解也很少,毕竟以我的认识,在国内C#更多地是用来开发网站,于是也在网上看了很多前辈贴的代码,我尝试过直接复制粘贴,发现很多都用不了最后自己写了才发现原来是这么一回事。所以在这里也奉劝各位不要做伸手党,通过自己的理解才能够将别人的知识转变成为自己的资本

    开始

    1、Socket 通信分为同步和异步两种模式,客户端一般用同步,异步多用于服务端。至于两者的区别,很多前辈已经有博客阐述过了,我这里就贴其中一个链接帮助大家理解:http://www.cnblogs.com/BLoodMaster/archive/2010/07/02/1769774.html。万一这个链接失效,相信大家也会搜索到其他资源的

    2、做这种Socket通信,我觉得关键点在于【数据处理】而不是建立链接,但往往很多不了解人会遇到各种困难,比如Socket链接关闭,丢包,粘包之类的。通用做法就是

      2.1:使用缓冲区,将接收到的字节数组全部储存起来,再去分析数据,获取有效部分。

      2.2:服务器跟客户端需要约定好报文的格式,一般来说至少要有【数据头标识】、【数据实体长度】、【数据尾标识】,这样才能更好地对数据进行处理,否则你很难界定到底哪一段数据才是你想要的。

      2.3:数据分析一定要少用字符串或者字符拆解,因为这样很有可能获取到的长度是错误的(里面有很多空字节,或者编码解码问题导致字符串长度不一致),所以一定要用字节数组来处理,通过约定好的编码解码格式,查找标识所在位置进行数据拆解。【比如:客户端跟服务器连接需要核对身份信息,因此对一字符串进行的特殊加密再编码发送,服务器接收到数据之后如果先编译成了字符串再获取密码转成byte[],长度跟之前记录的很可能就会不一样】。不过对于获取报文头信息这一类通过字符串来拆解更为方便

    3、在建立连接时通常发生的问题就是没有循环接收客户端消息导致socket被释放,或者只接受到客户端了几条消息之后就停了,我也遇到过这个问题,所以建立连接的关键在于BeginAccept和BeginReceive两个方法的递归循环调用

    4、接下来就是贴代码了,这里只有服务器的,客户端的我还没有做以后补全了会发上来,入口函数是公开的,主要方法全部在这里,至于辅助函数(比如获取IP地址,检查端口是否被占用、获取发送消息大家可以自己去找一下吧,毕竟这些已经有很多前辈开源了)

      4.1:由于本人只是一渣渣野生程序猿,如果大家有什么不同的看法请提出来,交流也是一种很好的进步方式

      4.2:如果大家觉得有用,还是希望能够推荐一下,让更多新人看到,让大家能够彼此学习,也不枉费我写博客的力气,谢谢

      4.3:我更希望有大神来告诉我不足的地方,因为我知道这些代码写得并不好~

    复制代码
    public void Create_Server_Socket()
            {
                Thread th_server = null;
                string result = string.Empty;
                try
                {
                    socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    IPAddress ip = Pubilc.Helper.Computer.GetIPv4();
                    IPEndPoint point = new IPEndPoint(ip, this._port);
                    socketServer.Bind(point);
                    socketServer.Listen(this._listen);
                    th_server = new Thread(() =>
                    {
                        if (this._isRunning)
                            socketServer.BeginAccept(AcceptCallBack, socketServer);
                        else
                            Close_Server_Socket();
                    });
                    th_server.IsBackground = true;
                    th_server.Start();
                }
                catch (Exception ex)
                {
                    if (th_server.IsAlive)
                        th_server.Abort();
                    ShowLog(ex.Message);
                };
            }
            public void Close_Server_Socket()
            {
                if (socketServer != null)
                {
                    socketServer.Close();
                }
            }
    
            void AcceptCallBack(IAsyncResult ar)
            {
                Socket socketServer = (Socket)ar.AsyncState;
                try
                {
                    Socket socketAccept = socketServer.EndAccept(ar);
                    DynamicBuffer state = new DynamicBuffer();
                    state.workSocket = socketAccept;
                    socketAccept.BeginReceive(state.Buffers, 0, state.Buffers.Length, SocketFlags.None, ReceiveCallBack, state);
                }
                catch(Exception ex)
                {
                    LogHelper.WriteLog(typeof(SocketHelper_Server), ex);
                    ShowLog("AcceptCallBack" + ex.Message);
                }
                finally
                {
                    if (this._isRunning)
                        socketServer.BeginAccept(AcceptCallBack, socketServer);
                }
    
            }
            void ReceiveCallBack(IAsyncResult ar)
            {
                string sendMsg = string.Empty;
                DynamicBuffer state = (DynamicBuffer)ar.AsyncState;
                Socket socketAccept = state.workSocket;
                try
                {
                    int len = socketAccept.EndReceive(ar);
                    if (len > 0)
                    {
                        state.WritBuffer();
                    }
                    else
                    {
                        sendMsg=doMsg.CheckConnection(socketAccept.RemoteEndPoint.ToString());
                        if (!string.IsNullOrEmpty(sendMsg))
                        {
                            byte[] buffer = Encoding.Default.GetBytes(sendMsg);
                            state.sb.AddRange(buffer);
                        }
                        else
                        {
                            socketAccept.Shutdown(SocketShutdown.Both);
                            socketAccept.Close();
                        }
                    }
                }
                catch (Exception ex)
                {
                    LogHelper.WriteLog(typeof(SocketHelper_Server), ex);
                    ShowLog("ReceiveCallBack" + ex.Message);
                }
                finally
                {
                    if (this._isRunning)
                    {
                        try
                        {
                            Thread th_send = null;
                            th_send = new Thread(() =>
                            {
                                Send(ref state);
                                th_send.Abort();
                            });
                            th_send.IsBackground = true;
                            th_send.Start();
                            socketAccept.BeginReceive(state.Buffers, 0, state.Buffers.Length, SocketFlags.None, ReceiveCallBack, state);
                        }
                        catch (Exception ex)
                        {
                            showLog(ex.Message);
                            LogHelper.WriteLog(typeof(IpcServerDo), ex);
                        }
                    }
                }
            }
            void Send(ref DynamicBuffer state)
            {
                byte[] buffer = null;
                string sendMsg = string.Empty;
                Socket socketAccept = state.workSocket;
                Dictionary<string, byte[]> data;
                state.GetAllData(out data);
                if (data.Count > 0)
                {
                    sendMsg = doMsg.AcceptFromIpc(socketAccept.RemoteEndPoint.ToString(), ref data);
                    if (!string.IsNullOrEmpty(sendMsg))
                    {
                        buffer = Encoding.Default.GetBytes(sendMsg.ToCharArray());
                        socketAccept.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, SendCallBack, socketAccept);
                    }
                    Send(ref state);
                }
            }
            void SendCallBack(IAsyncResult ar)
            {
                Socket socketAccept = (Socket)ar.AsyncState;
                try
                {
                    int sendBytes = socketAccept.EndSend(ar);
                }
                catch(Exception ex)
                {
                    LogHelper.WriteLog(typeof(SocketHelper_Server), ex);
                }
            }
    复制代码

    4、下面是State对象类,其实这个就是相当于缓冲区,将东西放到这里来保存,包括socket对象,其实有用的就是上面的声明部分和构造函数,至于下面的写入方法和数据拆解,缓冲区清理的方法我都觉得有待提高,因为用这种方法我在接收一张500KB左右的图片竟然要10秒才有返回结果(整个逻辑流程完成并发送数据的时间),这是不可以接受的,所以下面的数据处理方法,大家见仁见智吧,根据自己的需求去找到自己适合的方法,这里只是提供思路

    复制代码
    /// <summary>
        /// 循环队列
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class DynamicBuffer
        {
            /// <summary>
            /// 缓冲区大小
            /// </summary>
            public int bufferSize { get; set; }
           /// <summary>
           /// 通讯对象
           /// </summary>
            public Socket workSocket { get; set; }
            /// <summary>
            /// 缓冲区
            /// </summary>
            public byte[] Buffers { get; set; }
            /// <summary>
            /// 储存区
            /// </summary>
            public List<byte> sb;
            public DynamicBuffer()
            {
                this.bufferSize = 1024 *64;
                this.sb = new List<byte>();
                this.Buffers = new byte[bufferSize];
            }
            /// <summary>
            /// 将缓冲区数据放入储存区
            /// </summary>
            public void WritBuffer()
            {
                int endPosition = 0;
                byte[] buffer;
                int len = 0;
                for (endPosition = this.Buffers.Length - 1; endPosition >= 0; endPosition--)
                {
                    //由于发现数据中有很多的空字节,此处从最后开始执行反向删除
                    if (this.Buffers[endPosition] != (byte)'')
                        break;
                }
                len = endPosition + 1;//因为是取长度,而不是位置,因此此处+1
                buffer = new byte[len];
                Array.Copy(this.Buffers, 0, buffer, 0, len);
                this.sb.AddRange(buffer);
                this.Buffers = new byte[bufferSize];
            }
    
            //返回指定的长度的byet[]数据,并会清理对应的储存区
            public void GetAllData(out Dictionary<string, byte[]> data)
            {
                int DataLength = 0;
                DataLength = GetAllAcceptMsg(out data);
                if (DataLength > 0)
                {
                   ClearList(DataLength);
                }
            }
            //清理储存区已经发送的内容以及空包数据,以便下一次接收使用
            private void ClearList(int DataLength)
            {
                this.sb.RemoveRange(0, DataLength);
            }
            /// <summary>
            /// 判断储存区是否有接受完整的包
            /// 并将完整的包存放,拆解
            /// </summary>
            /// <returns>单次有效数据的长度</returns>
            private int GetAllAcceptMsg(out Dictionary<string, byte[]> data)
            {
                int DataLength=0;
                int contentLength = 0;
                int newlinePosition = 0;
                string title = string.Empty;
                string arr_msg = string.Empty;
                byte[] buffer = this.sb.ToArray();
                data = new Dictionary<string, byte[]>();
                try
                {
                    newlinePosition = StaticHelp.Search(buffer, Encoding.Default.GetBytes("
    
    
    "), false);//查找换行的位置
                    if (newlinePosition > 0)
                    {
                        arr_msg = Encoding.UTF8.GetString(buffer);
                        contentLength = Convert.ToInt32(CommonHelper.GetStrByRegex(arr_msg, "Content-Length: ", "
    "));
                        int dataLen = buffer.Length - newlinePosition;
                        if (dataLen >= contentLength)
                        {
                            title = arr_msg.Split(new string[] { "
    
    
    " }, StringSplitOptions.None)[0] + "
    ";
                            byte[] postData = new byte[contentLength];
                            if (contentLength > 0)
                                Buffer.BlockCopy(buffer, newlinePosition, postData, 0, contentLength);
                            data.Add(title, postData);
                            DataLength = newlinePosition + contentLength;
                        }
                    }
                    else
                    {
                        DataLength = 0;
                    }
                }
                catch (Exception ex)
                {
                    LogHelper.WriteLog(typeof(IpcServerDo), ex);
                }
                return DataLength;
            }
        }        
    复制代码

     

     

     

  • 相关阅读:
    找回Android studio的帮助文档
    adb shell 命令详解
    Android 获取Activity当前view
    下载网络文件HttpURLConnection.getContentLength()大小为 0
    Android设置屏幕旋转后保存数据
    解决TextView drawableRight左侧图片大小不可控的问题
    Android全屏(包含3种隐藏顶部状态栏及标题栏和一种隐藏Android 4.0平板底部状态栏的方法)
    人生苦短,我用Python(目录)
    爬虫学习目录
    Django-jet自定义菜单
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/5722034.html
Copyright © 2011-2022 走看看