(原创)TCPIP的二次封装,可同时多客户端收发数据

来源:互联网 发布:知乎for mac 编辑:程序博客网 时间:2024/06/07 15:50


TCPIP多客户端收发数据

研究这个搞了好久,性能绝对没得说,我测试的同时接入200多个客户端内存大约增加90多MB,还是不错的,哈哈!废话不多说进入正文。


这里只讲解服务端,客户端很简单没啥可研究的,说白了懂了服务端,客户端自然明白了。

首先引用命名空间

<span style="font-size:14px;">using System.Net.Sockets;using System.Net;</span><span style="font-size:14px;"></span>

设置一个属性Port用于设置TCP的服务端口,代码如下:

<span style="font-size:14px;">        TcpListener objListerer;        #region 侦听的端口号        /// <summary>        /// 侦听的端口号        /// </summary>        public int Port        {            set            {                objListerer = new TcpListener(IPAddress.Any, value);            }        }</span>

然后再设置一个方法Start(),方法实现了自动侦听客户端的功能和自动更新登陆信息的功能

<span style="font-size:14px;">        /// <summary>        /// 启动多线程侦听        /// </summary>        public void Start()        {            objListerer.Start();            Thread thread = new Thread(WaitNewConnection);            thread.IsBackground = true;            thread.Start(objListerer);            System.Timers.Timer objTimer = new System.Timers.Timer();            objTimer.Interval = 1000;            objTimer.Elapsed += objTimer_Elapsed;            objTimer.Start();        }</span>

定时器翻转方法,主要用来更新登陆信息,踢掉超时的链接:

        void objTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)        {            Connectioner[] objcon = objconn.ToArray();            objconn.CopyTo(objcon, 0);            Parallel.ForEach(objcon, (item, loop) =>                {                    item.登陆时间++;                    if (item.登陆时间 > outTime)                    {                        var t = Task.Factory.StartNew(() => Abort(item)); //终止所有相关任务                        var t1 = Task.Factory.StartNew(() => objconn.Remove(item));                        var t2 = Task.Factory.StartNew(() => loop.Stop()); return;                    }                });            ConntionInfo.BeginInvoke(objconn, SendDataOK, "OK");        }

outTime用来设置超时自动题掉的阈值

        private int outTime = 100;        /// <summary>        /// 超时时间设置,默认为100S        /// </summary>        public int OutTime        {            get { return outTime; }            set { outTime = value; }        }

等待新的客户端接入的方法,接入后加入到线程列表中,线程列表有定时器控制,也可手动控制:

        private void WaitNewConnection(object tcpListener)        {            TcpListener Listener = tcpListener as TcpListener;            try            {                while (true)                {                    TcpClient client = new TcpClient();                    objListerer.Start();                    client.Client = objListerer.AcceptSocket();                    NetworkStream ns = client.GetStream();                    Thread thead = new Thread(ReceiveData);                    thead.IsBackground = true;                    List<object> Info = new List<object>();                    Info.Add(ns);                    Info.Add(client);                    thead.Start(Info);                    if (!isContain(objconn, client.Client.ToString(), client.Client))//如果不存在则添加一个新的                    {                        objconn.Add(new Connectioner() { Key = client.Client.RemoteEndPoint.ToString(), netStream = ns, thread = thead, client = client, 登陆时间 = 0 });                    }                }            }            catch (Exception)//出错不报            {                           }        }

判断登陆对象是否存在的方法

<span style="font-size:18px;">        /// <summary>        /// 判断是否包含该链接        /// </summary>        /// <param name="objCOn"></param>        /// <param name="key"></param>        /// <param name="socket"></param>        /// <returns></returns>        private bool isContain(List<Connectioner> objCOn, string key, Socket socket)        {            int number = 0;            foreach (var item in objCOn)            {                if (item.Key == socket.RemoteEndPoint.ToString())                {                    number++;                }            }            if (number > 0)            {                return true;            }            else            {                return false;            }        }</span>


由于在软件中增加了自动识别客户端的功能所以增加了如下属性

此属性用来设置正则表达式,

       public IDFilterClass[] IDFilter        {            get;            set;        }

正则表达式验证登陆信息类

    /// <summary>    /// 正则获取ID表达式类    /// </summary>    public class IDFilterClass    {        /// <summary>        /// 正则匹配表达式        /// </summary>        public string 标准正则 { get; set; }        /// <summary>        /// 正则要获取的信息        /// </summary>        public string 获取正则 { get; set; }        /// <summary>        /// 截取的位置        /// </summary>        public int index { get; set; }        /// <summary>        /// 要截取的长度        /// </summary>        public int length { get; set; }    }


连接信息类,应该放在前面吧,哈哈

   public class Connectioner    {        public string Key { get; set; }        internal Thread thread { get; set; }        internal NetworkStream netStream { get; set; }        internal TcpClient client { get; set; }        public int 登陆时间 { get; set; }        public string ID { get; set; }    }

登陆信息验证方法

        private string Filter(string data)        {            if (IDFilter.Count()>0)            {                try                {                    for (int i = 0; i < IDFilter.Length; i++)                    {                        if (Regex.Match(data, IDFilter[i].标准正则).Success)                        {                            string regex = Regex.Match(Regex.Match(data, IDFilter[i].标准正则).Value.ToString(), IDFilter[i].获取正则).Value;                            if (regex != string.Empty)                            {                                return regex.Substring(IDFilter[i].index, IDFilter[i].length);                            }                        }                    }                }                catch (Exception err)                {                                        throw new Exception(err.Message);                }            }            return string.Empty;        }

接收数据方法,此方法会自动通知调用方

       private void ReceiveData(object Stream)        {            TcpClient client;            client = new TcpClient();            NetworkStream ns = null;            List<object> objList = Stream as List<object>;            Parallel.ForEach(objList, item =>                {                    if (item is TcpClient)                    {                        client = (TcpClient)item;                    }                    if (item is NetworkStream)                    {                        ns = (NetworkStream)item;                    }                });            try            {                while (true)                {                    byte[] bytes;                    bytes = new byte[2048];                    int length = ns.Read(bytes, 0, bytes.Count());                    if (length==0)                    {                        Thread.Sleep(200);                        continue;                    }                    byte[] NewBytes = new byte[length];                    Array.Copy(bytes, NewBytes, length);                    if (length > 0)                    {                        Parallel.ForEach(objconn, item =>                            {                                if (item.Key == client.Client.RemoteEndPoint.ToString())                                {                                    if (Filter(Encoding.ASCII.GetString(NewBytes)) != string.Empty && Filter(Encoding.ASCII.GetString(NewBytes)) != null)                                    {                                        item.ID = Convert.ToInt32(高低字节对调(Filter(Encoding.ASCII.GetString(NewBytes))), 16).ToString().PadLeft(10, '0');                                        NewConnterID.Invoke(item.ID);                                    }                                }                            });                        MessageByte.BeginInvoke(NewBytes, Encoding.ASCII.GetString(NewBytes), client.Client.RemoteEndPoint.ToString(), ReceiveOK, "OK");                        RefreshTime(client.Client.RemoteEndPoint.ToString());                    }                }            }            catch (Exception )            {                            }        }

如果收到新数据自动更新超时时间默认设置为0


    private void RefreshTime(string key)        {            Parallel.ForEach(objconn, item =>                {                    if (item.Key == key)                    {                        item.登陆时间 = 0;                    }                });        }

下面的代码用于终止线程

     /// <summary>        /// 终止某个线程        /// </summary>        /// <param name="key"></param>        public void ThreadStop(string key)        {            int num = objconn.Count;                for (int i = 0; i < num; i++)                {                    if (objconn[i].Key == key)                    {                        Abort(objconn[i]);                        objconn.Remove(objconn[i]);                        num--;                    }                }        }        /// <summary>        /// 终止所有线程        /// </summary>        public void Stop()        {            if (objconn.Count!=0)            {                int num = objconn.Count;                for (int i = 0; i < num; i++)                {                    Abort(objconn[i]);                    objconn.Remove(objconn[i]);                    num--;                }                objListerer.Stop();            }                   }


发送消息方法,支持Byte,或者HexByte(十六进制字符串形式的Byte)

       public void SendData(string key, byte[] dataByte = null, string dataHex = null)        {            if (dataByte == null && dataHex == null)            {                return;            }            foreach (var item in objconn)            {                if (item.Key == key)                {                    if (dataByte != null)                    {                        try                        {                            item.netStream.BeginWrite(dataByte, 0, dataByte.Length, SendDataOK, "OK");                        }                        catch (Exception)                        {                            return;                        }                    }                    else                    {                        dataByte = HexToHexByte(dataHex).ToArray();                        try                        {                            item.netStream.BeginWrite(dataByte, 0, dataByte.Length, SendDataOK, "OK");                        }                        catch (Exception)                        {                            return;                        }                    }                }            }        }

这个方法是别人写的,比较二逼,不过功能还是可以得

  private List<byte> HexToHexByte(string convertString) //         {            //【格式转换】将文本框的string转换为byte[]类型(ASCII)!            byte[] bytes_input = Encoding.Default.GetBytes(convertString);            uint bytes_input_len = (uint)Encoding.Default.GetByteCount(convertString);   //字符个数            //【过滤无效字符】仅提取符合0~9,A~F,a~f的ASCII值            byte[] bytes_text_fiter = new byte[65536];            uint filter_len = 0; for (uint i = 0; i < bytes_input_len; i++)            {                if ((bytes_input[i] >= 48 && bytes_input[i] <= 57) || (bytes_input[i] >= 65 && bytes_input[i] <= 70) || (bytes_input[i] >= 97 && bytes_input[i] <= 102))                { bytes_text_fiter[filter_len++] = bytes_input[i]; }            }            //【ASCII 转16进制】            for (uint i = 0; i < filter_len + 1; i++)            {                if (bytes_text_fiter[i] == '0') { bytes_text_fiter[i] = 0x00; }                else if (bytes_text_fiter[i] == '1') { bytes_text_fiter[i] = 0x01; }                else if (bytes_text_fiter[i] == '2') { bytes_text_fiter[i] = 0x02; }                else if (bytes_text_fiter[i] == '3') { bytes_text_fiter[i] = 0x03; }                else if (bytes_text_fiter[i] == '4') { bytes_text_fiter[i] = 0x04; }                else if (bytes_text_fiter[i] == '5') { bytes_text_fiter[i] = 0x05; }                else if (bytes_text_fiter[i] == '6') { bytes_text_fiter[i] = 0x06; }                else if (bytes_text_fiter[i] == '7') { bytes_text_fiter[i] = 0x07; }                else if (bytes_text_fiter[i] == '8') { bytes_text_fiter[i] = 0x08; }                else if (bytes_text_fiter[i] == '9') { bytes_text_fiter[i] = 0x09; }                else if (bytes_text_fiter[i] == 'a' || bytes_text_fiter[i] == 'A') { bytes_text_fiter[i] = 0x0A; }                else if (bytes_text_fiter[i] == 'b' || bytes_text_fiter[i] == 'B') { bytes_text_fiter[i] = 0x0B; }                else if (bytes_text_fiter[i] == 'c' || bytes_text_fiter[i] == 'C') { bytes_text_fiter[i] = 0x0C; }                else if (bytes_text_fiter[i] == 'd' || bytes_text_fiter[i] == 'D') { bytes_text_fiter[i] = 0x0D; }                else if (bytes_text_fiter[i] == 'e' || bytes_text_fiter[i] == 'E') { bytes_text_fiter[i] = 0x0E; }                else if (bytes_text_fiter[i] == 'f' || bytes_text_fiter[i] == 'F') { bytes_text_fiter[i] = 0x0F; }            }            //最终有效的16进制格式数据            byte[] bytes_hex = new byte[65536];            uint bytes_hex_len = 0;            //合并前后两个16进制字节存储在byte[]中            for (int i = 0; i < (filter_len + 1) / 2; i++)            {                int sw_tmp = 0;                sw_tmp = bytes_text_fiter[i * 2];                sw_tmp <<= 4;                bytes_text_fiter[i * 2] = (byte)sw_tmp;                bytes_hex[i] = (byte)(bytes_text_fiter[i * 2] | bytes_text_fiter[i * 2 + 1]);                bytes_hex_len++;            }            //最后一个字节是否需要右移4位            if (filter_len % 2 == 1)        //查看合并字节前有效字节个数是否是奇数            {                uint sw_tmp = 0;                sw_tmp = bytes_hex[bytes_hex_len - 1];  //将合并后的最有一个字节右移4位                sw_tmp >>= 4;                bytes_hex[bytes_hex_len - 1] = (byte)sw_tmp;            }            List<byte> list_res = new List<byte>();            for (uint i = 0; i < (bytes_hex_len); i++)            {                list_res.Add(bytes_hex[i]);            }            return list_res;        }

添加事件

  public event NewID NewConnterID;       /// <summary>        /// 刷新登陆信息        /// </summary>       public event Refresg ConntionInfo;       /// <summary>        /// 收到新的消息        /// </summary>       public event NewMessage MessageByte;        TcpListener objListerer;

<span style="font-size:18px;color:#ff0000;">注意下面的代码要写在类的外面</span>
   public delegate void NewMessage(byte[] msgByte,string msgASCII,string key);    /// <summary>    /// 有的新的集中器ID    /// </summary>    /// <param name="RemoteEndPoint"></param>    public delegate void NewID(string ID);    /// <summary>    /// 有客户端关闭    /// </summary>    /// <param name="RemoteEndPoint"></param>    public delegate void SocketCLose(string RemoteEndPoint);    /// <summary>    /// 更新登陆列表    /// </summary>    /// <param name="objList"></param>    public delegate void Refresg(List<Connectioner> objList);    /// <summary>    /// TCPIP服务端    /// </summary>

封装后的方法列表
登陆信息

惊恐500多行的代码,没法讲太细。。。(-。-;)

效果图



使用方法

objStream.Port=this.objStringIP.Port;                        List<IDFilterClass> objCLass = new List<IDFilterClass>();                        objCLass.Add(new IDFilterClass { 标准正则 = "[D][E][V][:][0-9a-fA-F]{8}", 获取正则 = "[D][E][V][:][0-9a-fA-F]{8}", index = 4, length = 8 });                        objCLass.Add(new IDFilterClass { 标准正则 = "[[][E][N][N][P][I][N][G][]][[][L][S][D][0-9a-fA-F]{8}[]]", 获取正则 = "[L][S][D][0-9a-fA-F]{8}", index = 3, length = 8 });                        objStream.IDFilter = objCLass.ToArray();                        objStream.Start();

觉得麻烦或者看不懂还是下DLL吧!这个实在

DLL下载地址:http://download.csdn.net/detail/hotmee/9575341




0 0
原创粉丝点击