客户端发送数据到服务端时可能有以下三种情况:
1.服务端完整接收客户端发送的一条消息;
2.客户端发送的一条消息被当成两条消息处理;
3.客户端发送的两条消息(甚至更多)被合并成一条消息接收;
原因是NetWrokStream写入数据时,数据并没有立即发往远程主机,而是保存在了TCP缓存(TCP Buffer)中,经过一段时间之后再进行发送,对于传输二进制文件并没什么影响,但对于文本来说,就需要明确发送文本的边界。
// 协议:[length=XX]..... 其中XX是字符串的长度(注意不是字节数组buffer的长度)
如:"[length=12]Hello World!"
-
处理逻辑
服务端接收字符串后可能有下面两种情况:
1."[""]"中括号是完整的,可以读取到字符串的length长度,然后根据这个数值与后面的字符串长度相比,如果相等则说明接收到的数据是完整的;如果多了,则说明接收的字节数多了,取出合适的长度,并将剩余的数据进行缓存,等待下一次接收数据的时候进行合并;如果少了,说明接收数据不完整,将数据进行缓存,等待下一次接收的户数的时候进行合并。
2."[""]"中括号本身不完整,此时读取不到字符串的length长度,将接收的数据进行缓存,等待读取下次接收的数据,然后将两次合并字后的数据按上面的方式进行处理。
-
编码实现
定义RequestHandler类用于服务端解析客户端发送的字符串数据:
public class RequestHandler { private string temp = string.Empty; public string[] GetActualString(string input) { return GetActualString(input, null); } public string[] GetActualString(string input, List<string> outputList) { if (outputList == null) { outputList = new List<string>(); } if (!string.IsNullOrEmpty(temp)) { input = temp + input; } string output; // (?<=patten) 反向肯定预查询 (?=patten) 正向肯定预查询 string patten = @"(?<=^[length=)(d+)(?=])"; int length; if (Regex.IsMatch(input, patten)) { Match m = Regex.Match(input, patten); // 获取消息字符串实际长度 length = Convert.ToInt32(m.Groups[0].Value); // 获取需要进行截取的位置 int startIndex = input.IndexOf(']') + 1; // 获取从截取位置开始后所有字符的长度 output = input.Substring(startIndex); if (output.Length == length) { // output长度与消息字符串长度相等,说明刚好是完整的一条消息 outputList.Add(output); temp = ""; } else if (output.Length < length) { // output长度小于消息字符串长度,说明消息没有发完整 // 应将整条消息,包括元数据全部缓存,与下一条数据合并起来再进行处理 temp = input; } if (output.Length > length) { // output长度大于消息字符串长度,说明消息发完整了,但是有多余的数据 // 多余的数据可能是截断消息,也可能是多条完整的消息 // 截取字符串 output = output.Substring(0, length); outputList.Add(output); temp = ""; // 缩短input的长度 input = input.Substring(startIndex + length); // 递归调用 GetActualString(input, outputList); } } else { // "[","]"不完整 temp = input; } return outputList.ToArray(); } }