再次特别感谢张子阳老师的文章,是我深感益处。 在前一篇文章中可以看到,尽管消息分成了三条单独发送,但是服务端却将后两条合并成了一条。对于这些情况,我们可以这样处理:就好像HTTP协议一样,在实际的请求和应答内容之前包含了HTTP头,其中是一些与请求相关的信息。我们也可以订立自己的协议,来解决这个问题,比如说,对于上面的情况,我们就可以定义这样一个协议: [length=XXX]:其中xxx是实际发送的字符串长度(注意不是字节数组buffer的长度),那么对于上面的请求,则我们发送的数据为:“[length=25]Welcome to TraceFact.Net!”。而服务端接收字符串之后,首先读取这个“元数据”的内容,然后再根据“元数据”内容来读取实际的数据,它可能有下面这样两种情况: NOTE:我觉得这里借用“元数据”这个术语还算比较恰当,因为“元数据”就是用来描述数据的数据。 “[“”]”中括号是完整的,可以读取到length的字节数。然后根据这个数值与后面的字符串长度相比,如果相等,则说明发来了一条完整信息;如果多了,那么说明接收的字节数多了,取出合适的长度,并将剩余的进行缓存;如果少了,说明接收的不够,那么将收到的进行一个缓存,等待下次请求,然后将两条合并。 “[”“]”中括号本身就不完整,此时读不到length的值,因为中括号里的内容被截断了,那么将读到的数据进行缓存,等待读取下次发送来的数据,然后将两次合并之后再按上面的方式进行处理。 转载请说明出处。
所以在此先拟定一个协议。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Text.RegularExpressions; namespace SeverClass.RequestHanderSpace { public class RequestHander { private string temp = string.Empty; public string[] GetActualString(string input) { return GetActualString(input, null); } private string[] GetActualString(string input, List<string> OutputList) { if(OutputList==null) OutputList = new List<string>(); if(!string.IsNullOrEmpty(temp)) input = temp +input; string output = ""; string pattern = @"(?<=^[length=)(d+)(?=])"; int length; if (Regex.IsMatch(input, pattern)) { Match m = Regex.Match(input, pattern); // get the length of string input; length = Convert.ToInt32(m.Groups[0].Value); //获取需要截取的位置 int startIndex = input.IndexOf(']') + 1; //获取这个位置之后的字符串 output = input.Substring(startIndex); if (length == output.Length) { //如果output的长度与消息字符串的长度相等,说明刚好是一条消息; OutputList.Add(output); temp = ""; } else if (output.Length < length) { //说明截取之后的长度小于应有的长度,说明没有发完整,应将整条信息包括元数据全部缓存,与下一条数据一并处理。 temp = input; } else if (output.Length > length) { //说明截取之后的长度大于应有的长度,说明消息发完整了,但是有多余的数据,多余的数据可能是截断信息,也可能是多条完整信息。 output = output.Substring(0, length); OutputList.Add(output); temp = ""; input = input.Substring(startIndex + length);//截取剩下的字符串 GetActualString(input, OutputList);//递归调用 } } else { temp = input;//说明[]本身就不完整 } return OutputList.ToArray(); } } }对得到的字符串进行解析,在此用到了正则表达式,
string pattern = @"(?<=^[length=)(d+)(?=])";关于正则表达式,请自行学习。
服务器代码。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net; using System.Net.Sockets; using SeverClass.RequestHanderSpace; namespace SeverClass { public class RomoteClient { private TcpClient client; private NetworkStream streamToclient; private const int bufferSize = 8192; private byte[] buffer; private RequestHander hander; public RomoteClient(TcpClient client) { this.client = client; //the info of client connected Console.WriteLine("client connected{0} <-- {1}", client.Client.LocalEndPoint, client.Client.RemoteEndPoint); //获得流 streamToclient = client.GetStream(); buffer = new byte[bufferSize]; hander = new RequestHander(); //在构造函数中准备读取 AsyncCallback callback = new AsyncCallback(ReadComplete); streamToclient.BeginRead(buffer, 0, bufferSize, callback, null); } //在读取完时进行回调 private void ReadComplete(IAsyncResult ar) { int bytesRead = 0; try { lock (streamToclient) { bytesRead = streamToclient.EndRead(ar); Console.WriteLine("读取到{0}字节", bytesRead); } if (bytesRead == 0) { throw new Exception("读取到0字节"); } string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead); Array.Clear(buffer, 0, buffer.Length);//清空缓存,避免脏读 string[] msgArray = hander.GetActualString(msg);//获取实际的字符串 //遍历得到的字符串; foreach (string str in msgArray) { Console.WriteLine("Recived: {0}", str); Console.WriteLine(); string back = str.ToUpper(); back = string.Format("[length={0}]{1}",back.Length,back); //将得到的字符串转换为大写重新发送给客户端 byte[] send = Encoding.Unicode.GetBytes(back); lock (streamToclient) { streamToclient.Write(send, 0, send.Length); streamToclient.Flush(); } Console.WriteLine("Send: {0}", back); Console.WriteLine(); } //再次回调,无限循环,直到异常退出 lock (streamToclient) { AsyncCallback callback = new AsyncCallback(ReadComplete); streamToclient.BeginRead(buffer, 0, bufferSize, callback, null); } } catch(Exception ex) { if (streamToclient != null) { streamToclient.Dispose(); } client.Close(); Console.WriteLine(ex.Message); } } } class Program { static void Main(string[] args) { Console.WriteLine("Severe is running!"); IPAddress ip = new IPAddress(new byte[4] { 127, 0, 0,1 }); TcpListener listener = new TcpListener(ip, 8501); listener.Start(); Console.WriteLine("Severe is listenning"); while (true) { TcpClient client = listener.AcceptTcpClient(); RomoteClient remote = new RomoteClient(client); } ConsoleKey key; while (Console.ReadKey().Key != ConsoleKey.Q) { continue; } } } }
这是客户端代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net; using System.Net.Sockets; using SeverClass.RequestHanderSpace; using System.Threading; namespace ClientClass { public class SeverClient { private const int bufferSize = 8192; private byte[] buffer; private TcpClient client; private NetworkStream streamToSever; private string msg = "welcome to tracefact .net"; RequestHander hander = new RequestHander(); public SeverClient() { try { client = new TcpClient(); client.Connect("127.0.0.1", 8501); } catch (Exception ex) { Console.WriteLine(ex.Message); return; } buffer = new byte[bufferSize]; Console.WriteLine("连接成功! {0}-->{1}", client.Client.LocalEndPoint, client.Client.RemoteEndPoint); streamToSever = client.GetStream(); Thread readThread = new Thread(read); readThread.Start(); } //send message to Sever public void sendMessage() { msg = string.Format("[length={0}]{1}", msg.Length, msg); for (int i = 0; i < 5; i++) { byte[] temp = Encoding.Unicode.GetBytes(msg); try { lock (streamToSever) { streamToSever.Write(temp, 0, temp.Length); streamToSever.Flush(); } Console.WriteLine("Send: {0}", msg); Console.WriteLine(); } catch (Exception ex) { Console.WriteLine(ex.Message); break; } } } public void read() { lock (streamToSever) { AsyncCallback callback = new AsyncCallback(ReadComplete); streamToSever.BeginRead(buffer, 0, bufferSize, callback, null); } } void ReadComplete(IAsyncResult ar) { int bytesRead = 0; try { lock (streamToSever) { bytesRead = streamToSever.EndRead(ar); } if (bytesRead == 0) { throw new Exception("读取到0字节"); } Console.WriteLine("读取了{0}字节", bytesRead); string msg = Encoding.Unicode.GetString(buffer,0,bytesRead); Console.WriteLine(msg); string[] strArray = hander.GetActualString(msg); foreach (string str in strArray) { Console.WriteLine("Recived: {0}", str); Console.WriteLine(); } Array.Clear(buffer, 0, buffer.Length);//清空缓存 避免脏读 lock (streamToSever) { AsyncCallback callback = new AsyncCallback(ReadComplete); streamToSever.BeginRead(buffer, 0, bufferSize, callback, null); } } catch (Exception ex) { if (streamToSever != null) { streamToSever.Dispose(); } client.Close(); Console.WriteLine(ex.Message); } } } class Program { static void Main(string[] args) { SeverClient myClient = new SeverClient(); myClient.sendMessage(); Console.ReadLine(); } } }
在客户端,另建立了一个线程read,用来同步接受服务器返回的代码。关于多线程的知识,请看我之前写的一个多线程监听小程序。
转载请标明出处。