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