Socket編程 (異步通訊,解決Tcp粘包)
從上一章的通訊中,我們發現如果使用Tcp連續發送消息會出現消息一起發送過來的情況,這樣給我們編程造成一定的問題,給我們的信息解析造成一定的問題。那么這篇文章就將針對以上問題給出解決方案......
問題一般會出現的情況如下,假設我們連續發送兩條兩天記錄("我是liger_zql"):
模擬發送示例:
#region 測試消息發送,並匹配協議 TcpClient client = new TcpClient(); client.AsynConnect(); Console.WriteLine("下面將連續發送2條測試消息..."); Console.ReadKey(); MessageProtocol msgPro; for (int i = 0; i < 2; i++) { msgPro = new MessageProtocol("我是liger_zql"); Console.WriteLine("第{0}條:{1}", i + 1, msgPro.MessageInfo.Content); client.AsynSend(msgPro); } #endregion
接收端接受兩條信息會出現如下三種情況:
1.(1)我是liger_zql(2)我是liger_zql
2.(1)我是liger_zql我是(2)liger_zql
3.(1)我是liger_zql我是liger_zql
通過以上三種情況,顯然2、3都不是我們想要的結果。那么如何處理這中情況呢?
解決方案:通過自定義協議...
我們可以以將信息以xml的格式發送出去,列入<protocol>content</protocol>通過正則匹配信息是否完整,如果不完整,我們可以先將本次接受信息緩存接受下一次信息,再次匹配得到相應的結果。
將信息對象轉換成一定格式的xml字符串:
/// <summary> /// 文本信息 /// </summary> public class MessageInfo { public string Content { get; set; }//信息內容 public MessageInfo(string content) { this.Content = content; } public override string ToString() { return String.Format("<message Content=\"{0}\" />", this.Content); } } /// <summary> /// 文件信息 /// </summary> public class RequestFile { public string Address { get; set; }//發送端Ip public int Port { get; set; }//端口號 public RequestMode Mode { get; set; }//請求類 public FileObject FileObject { get; set; }//文件詳細參數 public RequestFile() { } public RequestFile(RequestMode mode, FileObject fileobject) { this.Mode = mode; this.FileObject = fileobject; } public RequestFile(string address, int port, RequestMode mode, FileObject fileobject) { this.Address = address; this.Port = port; this.Mode = mode; this.FileObject = fileobject; } public RequestFile(string address, int port, RequestMode mode, string filename, long filelength, int packetsize, long packetcount) { this.Address = address; this.Port = port; this.Mode = mode; this.FileObject = new FileObject(filename, filelength, packetsize, packetcount); } public override string ToString() { StringBuilder sbString = new StringBuilder(); sbString.Append("<message "); sbString.Append(String.Format("Address=\"{0}\" ", Address)); sbString.Append(String.Format("Port=\"{0}\" ", Port)); sbString.Append(String.Format("Mode=\"{0}\" ", Mode)); sbString.Append(String.Format("FileName=\"{0}\" ", FileObject.FileName)); sbString.Append(String.Format("FileLength=\"{0}\" ", FileObject.FileLength)); sbString.Append(String.Format("PacketSize=\"{0}\" ", FileObject.PacketSize)); sbString.Append(String.Format("PacketCount=\"{0}\" ", FileObject.PacketCount)); sbString.Append("/>"); return sbString.ToString(); } } /// <summary> /// 訂立信息協議 /// </summary> public class MessageProtocol { public MessageType MessageType { get; set; } public MessageInfo MessageInfo { get; set; } public RequestFile RequestFile { get; set; } public MessageProtocol() { } public MessageProtocol(string msg) { MessageType = MessageType.text; MessageInfo = new MessageInfo(msg); } public MessageProtocol(RequestMode mode, FileObject fileobject) { MessageType = MessageType.file; RequestFile = new RequestFile(mode, fileobject); } public MessageProtocol(string address, int port, RequestMode mode, FileObject fileobject) { MessageType = MessageType.file; RequestFile = new RequestFile(address, port, mode, fileobject); } public override string ToString() { StringBuilder sbString = new StringBuilder(); sbString.Append(String.Format("<protocol Type=\"{0}\">", MessageType)); if (MessageType == MessageType.text) { sbString.Append(MessageInfo.ToString()); } else { sbString.Append(RequestFile.ToString()); } sbString.Append("</protocol>"); return sbString.ToString(); } public byte[] ToBytes() { return Encoding.UTF8.GetBytes(this.ToString()); } }
對接收的信息通過正則進行匹配處理:
//臨時緩存 public string temp = string.Empty; //匹配協議獲取信息 public List<MessageProtocol> HandlerString(string msg) { List<MessageProtocol> msgProList = new List<MessageProtocol>(); if (!String.IsNullOrEmpty(temp)) { msg = temp + msg; } string pattern = "(^<protocol Type=.*?>.*?</protocol>)"; if (Regex.IsMatch(msg, pattern)) { //匹配協議內容 string match = Regex.Match(msg, pattern).Groups[0].Value; //將匹配的內容添加到集合 msgProList.Add(HandlerObject(match)); temp = string.Empty; //截取未匹配字符串,進行下一次匹配 msg = msg.Substring(match.Length); if (!String.IsNullOrEmpty(msg)) { msgProList.AddRange(HandlerString(msg)); } } else { temp = msg; } return msgProList; }
然后將該定義的協議換換成信息對象,通過對象獲取自己想要的信息。
//將已轉成協議信息轉成對象信息 public MessageProtocol HandlerObject(string protocol) { XmlDocument xmldoc = new XmlDocument(); xmldoc.LoadXml(protocol); XmlNode root = xmldoc.DocumentElement; XmlNode msgnode = root.SelectSingleNode("message"); MessageProtocol msgPro = new MessageProtocol(); if (root.Attributes["Type"].Value == MessageType.text.ToString()) { msgPro.MessageType = MessageType.text; msgPro.MessageInfo = new MessageInfo(msgnode.Attributes["Content"].Value); } else { msgPro.MessageType = MessageType.file; RequestMode mode = (RequestMode)Enum.Parse(typeof(RequestMode), msgnode.Attributes["Mode"].Value); FileObject fileobject = new FileObject(); fileobject.FileName = msgnode.Attributes["FileName"].Value; fileobject.FileLength = Convert.ToInt64(msgnode.Attributes["FileLength"].Value); fileobject.PacketSize = Convert.ToInt32(msgnode.Attributes["PacketSize"].Value); fileobject.PacketCount = Convert.ToInt64(msgnode.Attributes["PacketCount"].Value); msgPro.RequestFile = new RequestFile( msgnode.Attributes["Address"].Value, Convert.ToInt32(msgnode.Attributes["Port"].Value), mode, fileobject); } return msgPro; }
最后運行結果如下:
好了Tcp粘包的問題我們解決了。下一章我們將解決Udp丟包的個人解決方案!
附上源碼:SocketProQuests.zip
作者:曾慶雷
出處:http://www.cnblogs.com/zengqinglei
本頁版權歸作者和博客園所有,歡迎轉載,但未經作者同意必須保留此段聲明, 且在文章頁面明顯位置給出原文鏈接,否則保留追究法律責任的權利